Skip to content

Commit 8dc19e2

Browse files
authored
Multipart 전용 테스트 환경을 구성한다. (#94)
* feat: implement create exam API * feat: change the type of `file` argument of `uploadFile()` to `ByteArray` * feat: change to allow saving images with multiple extensions * chore: add multipart test configuration * fix: modify invalid status code
1 parent 43cb358 commit 8dc19e2

File tree

20 files changed

+216
-141
lines changed

20 files changed

+216
-141
lines changed

api/src/main/kotlin/com/gotchai/api/global/config/SecurityConfig.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
44
import com.gotchai.api.global.jwt.JwtAuthenticationFilter
55
import com.gotchai.api.global.security.CustomAccessDeniedHandler
66
import com.gotchai.api.global.security.CustomAuthenticationEntryPoint
7+
import com.gotchai.domain.global.provider.TokenProvider
78
import com.gotchai.domain.user.entity.Role
89
import org.springframework.context.annotation.Bean
910
import org.springframework.context.annotation.Configuration
@@ -60,6 +61,9 @@ class SecurityConfig {
6061
@Bean
6162
fun customAccessDeniedHandler(objectMapper: ObjectMapper): CustomAccessDeniedHandler = CustomAccessDeniedHandler(objectMapper)
6263

64+
@Bean
65+
fun jwtAuthenticationFilter(tokenProvider: TokenProvider): JwtAuthenticationFilter = JwtAuthenticationFilter(tokenProvider)
66+
6367
@Bean
6468
fun passwordEncoder(): PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
6569
}

api/src/main/kotlin/com/gotchai/api/global/security/CustomAuthenticationEntryPoint.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class CustomAuthenticationEntryPoint(
1919
authenticationException: AuthenticationException
2020
) {
2121
with(response) {
22-
status = HttpStatus.FORBIDDEN.value()
22+
status = HttpStatus.UNAUTHORIZED.value()
2323
contentType = MediaType.APPLICATION_JSON_VALUE
2424
writer.write(
2525
objectMapper.writeValueAsString(

api/src/main/kotlin/com/gotchai/api/global/util/WebUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import org.springframework.web.multipart.MultipartFile
77

88
internal fun MultipartFile.toStorageObject(): StorageObject =
99
StorageObject(
10-
extension = StringUtils.getFilenameExtension(originalFilename)?.let { Extension.valueOf(it) },
10+
extension = StringUtils.getFilenameExtension(originalFilename)?.let { Extension.valueOf(it.uppercase()) },
1111
size = size,
1212
stream = inputStream
1313
)

api/src/test/kotlin/com/gotchai/api/presentation/v1/badge/BadgeControllerTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import com.gotchai.api.docs.errorResponseFields
77
import com.gotchai.api.global.dto.ApiResponse
88
import com.gotchai.api.presentation.v1.badge.response.BadgeListResponse
99
import com.gotchai.api.presentation.v1.badge.response.BadgeResponse
10+
import com.gotchai.api.util.desc
1011
import com.gotchai.api.util.document
1112
import com.gotchai.api.util.expectError
12-
import com.gotchai.api.util.paramDesc
1313
import com.gotchai.domain.badge.exception.BadgeNotFoundException
1414
import com.gotchai.domain.badge.port.`in`.BadgeQueryUseCase
1515
import com.gotchai.domain.fixture.ID
@@ -38,7 +38,7 @@ class BadgeControllerTest : ControllerTest() {
3838
.isOk
3939
.expectBody<ApiResponse<BadgeResponse>>()
4040
.document("식별자 기반 뱃지 단일 조회 성공(200)") {
41-
pathParams("badgeId" paramDesc "뱃지 식별자")
41+
pathParams("badgeId" desc "뱃지 식별자")
4242
responseBody(badgeResponseFields)
4343
}
4444
}
@@ -56,7 +56,7 @@ class BadgeControllerTest : ControllerTest() {
5656
.isNotFound
5757
.expectError()
5858
.document("식별자 기반 뱃지 단일 조회 실패(404)") {
59-
pathParams("badgeId" paramDesc "뱃지 식별자")
59+
pathParams("badgeId" desc "뱃지 식별자")
6060
responseBody(errorResponseFields)
6161
}
6262
}

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

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,18 @@ package com.gotchai.api.presentation.v1.exam
22

33
import com.gotchai.api.common.ControllerTest
44
import com.gotchai.api.docs.*
5-
import com.gotchai.api.fixture.PARTICIPANT_COUNT
65
import com.gotchai.api.global.dto.ApiResponse
76
import com.gotchai.api.presentation.v1.exam.response.*
7+
import com.gotchai.api.util.desc
88
import com.gotchai.api.util.document
99
import com.gotchai.api.util.expectError
10-
import com.gotchai.api.util.paramDesc
1110
import com.gotchai.domain.badge.exception.BadgeNotFoundException
1211
import com.gotchai.domain.exam.exception.ExamAlreadySolvedException
1312
import com.gotchai.domain.exam.exception.ExamHistoryNotFoundException
1413
import com.gotchai.domain.exam.exception.ExamNotFoundException
1514
import com.gotchai.domain.exam.port.`in`.ExamCommandUseCase
1615
import com.gotchai.domain.exam.port.`in`.ExamQueryUseCase
17-
import com.gotchai.domain.fixture.ID
18-
import com.gotchai.domain.fixture.createExamResult
19-
import com.gotchai.domain.fixture.createStartExamResult
20-
import com.gotchai.domain.fixture.createSubmitExamResult
16+
import com.gotchai.domain.fixture.*
2117
import com.ninjasquad.springmockk.MockkBean
2218
import io.mockk.every
2319
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
@@ -79,7 +75,7 @@ class ExamControllerTest : ControllerTest() {
7975
.isOk
8076
.expectBody<ApiResponse<ExamResponse>>()
8177
.document("테스트 단일 조회 성공(200)") {
82-
pathParams("examId" paramDesc "테스트 식별자")
78+
pathParams("examId" desc "테스트 식별자")
8379
responseBody(examResponseFields)
8480
}
8581
}
@@ -97,7 +93,7 @@ class ExamControllerTest : ControllerTest() {
9793
.isNotFound
9894
.expectError()
9995
.document("테스트 단일 조회 실패(404)") {
100-
pathParams("examId" paramDesc "테스트 식별자")
96+
pathParams("examId" desc "테스트 식별자")
10197
responseBody(errorResponseFields)
10298
}
10399
}
@@ -116,7 +112,7 @@ class ExamControllerTest : ControllerTest() {
116112
.isOk
117113
.expectBody<ApiResponse<GetExamParticipantCountResponse>>()
118114
.document("테스트 참여자 수 조회 성공(200)") {
119-
pathParams("examId" paramDesc "테스트 식별자")
115+
pathParams("examId" desc "테스트 식별자")
120116
responseBody(getExamParticipantCountResponseFields)
121117
}
122118
}
@@ -135,7 +131,7 @@ class ExamControllerTest : ControllerTest() {
135131
.isOk
136132
.expectBody<ApiResponse<StartExamResponse>>()
137133
.document("테스트 시작 성공(200)") {
138-
pathParams("examId" paramDesc "테스트 식별자")
134+
pathParams("examId" desc "테스트 식별자")
139135
responseBody(startExamResponseFields)
140136
}
141137
}
@@ -153,7 +149,7 @@ class ExamControllerTest : ControllerTest() {
153149
.isBadRequest
154150
.expectError()
155151
.document("테스트 시작 실패(400)") {
156-
pathParams("examId" paramDesc "테스트 식별자")
152+
pathParams("examId" desc "테스트 식별자")
157153
responseBody(errorResponseFields)
158154
}
159155
}
@@ -173,7 +169,7 @@ class ExamControllerTest : ControllerTest() {
173169
.isOk
174170
.expectBody<ApiResponse<SubmitExamResponse>>()
175171
.document("테스트 제출 성공(200)") {
176-
pathParams("examId" paramDesc "테스트 식별자")
172+
pathParams("examId" desc "테스트 식별자")
177173
responseBody(submitExamResponseFields)
178174
}
179175
}
@@ -191,7 +187,7 @@ class ExamControllerTest : ControllerTest() {
191187
.isNotFound
192188
.expectError()
193189
.document("테스트 제출 실패(404 - 1)") {
194-
pathParams("examId" paramDesc "테스트 식별자")
190+
pathParams("examId" desc "테스트 식별자")
195191
responseBody(errorResponseFields)
196192
}
197193
}
@@ -209,7 +205,7 @@ class ExamControllerTest : ControllerTest() {
209205
.isNotFound
210206
.expectError()
211207
.document("테스트 제출 실패(404 - 2)") {
212-
pathParams("examId" paramDesc "테스트 식별자")
208+
pathParams("examId" desc "테스트 식별자")
213209
responseBody(errorResponseFields)
214210
}
215211
}
@@ -227,7 +223,7 @@ class ExamControllerTest : ControllerTest() {
227223
.isBadRequest
228224
.expectError()
229225
.document("테스트 제출 실패(400)") {
230-
pathParams("examId" paramDesc "테스트 식별자")
226+
pathParams("examId" desc "테스트 식별자")
231227
responseBody(errorResponseFields)
232228
}
233229
}
@@ -245,7 +241,7 @@ class ExamControllerTest : ControllerTest() {
245241
.isNotFound
246242
.expectError()
247243
.document("테스트 제출 실패(404 - 3)") {
248-
pathParams("examId" paramDesc "테스트 식별자")
244+
pathParams("examId" desc "테스트 식별자")
249245
responseBody(errorResponseFields)
250246
}
251247
}

api/src/test/kotlin/com/gotchai/api/presentation/v1/quiz/QuizControllerTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import com.gotchai.api.fixture.createGradeQuizRequest
99
import com.gotchai.api.global.dto.ApiResponse
1010
import com.gotchai.api.presentation.v1.quiz.response.GradeQuizResponse
1111
import com.gotchai.api.presentation.v1.quiz.response.QuizDetailResponse
12+
import com.gotchai.api.util.desc
1213
import com.gotchai.api.util.document
1314
import com.gotchai.api.util.expectError
14-
import com.gotchai.api.util.paramDesc
1515
import com.gotchai.domain.exam.exception.ExamAlreadySolvedException
1616
import com.gotchai.domain.exam.exception.ExamHistoryNotFoundException
1717
import com.gotchai.domain.fixture.ID
@@ -49,7 +49,7 @@ class QuizControllerTest : ControllerTest() {
4949
.isOk
5050
.expectBody<ApiResponse<QuizDetailResponse>>()
5151
.document("퀴즈 단일 조회 성공(200)") {
52-
pathParams("quizId" paramDesc "퀴즈 식별자")
52+
pathParams("quizId" desc "퀴즈 식별자")
5353
responseBody(quizDetailResponseFields)
5454
}
5555
}
@@ -67,7 +67,7 @@ class QuizControllerTest : ControllerTest() {
6767
.isNotFound
6868
.expectError()
6969
.document("퀴즈 단일 조회 실패(404)") {
70-
pathParams("quizId" paramDesc "퀴즈 식별자")
70+
pathParams("quizId" desc "퀴즈 식별자")
7171
responseBody(errorResponseFields)
7272
}
7373
}

api/src/testFixtures/kotlin/com/gotchai/api/common/ControllerTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ import org.springframework.web.context.WebApplicationContext
1919

2020
@AutoConfigureRestDocs
2121
abstract class ControllerTest : DescribeSpec() {
22-
override fun extensions(): List<Extension> = listOf(SpringExtension)
23-
2422
@Autowired
2523
private lateinit var webApplicationContext: WebApplicationContext
2624

2725
@Autowired
2826
private lateinit var restDocumentationContextProvider: RestDocumentationContextProvider
2927

28+
override fun extensions(): List<Extension> = listOf(SpringExtension)
29+
3030
override suspend fun beforeSpec(spec: Spec) {
3131
SecurityContextHolder.getContext().authentication = GotchaiAuthentication.from(createUser())
3232
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.gotchai.api.docs
2+
3+
import com.gotchai.api.presentation.v1.admin.request.CreateExamRequest
4+
import com.gotchai.api.util.desc
5+
import com.gotchai.api.util.fieldsOf
6+
7+
val createExamRequestFields =
8+
fieldsOf(
9+
CreateExamRequest::title desc "제목",
10+
CreateExamRequest::subTitle desc "부제목",
11+
CreateExamRequest::description desc "테스트 설명",
12+
CreateExamRequest::prompt desc "테스트 프롬프트",
13+
CreateExamRequest::backgroundImage desc "배경 이미지",
14+
CreateExamRequest::iconImage desc "아이콘 이미지",
15+
CreateExamRequest::coverImage desc "커버 이미지",
16+
CreateExamRequest::theme desc "테마"
17+
)

api/src/testFixtures/kotlin/com/gotchai/api/docs/AuthDocs.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@ import com.gotchai.api.presentation.v1.auth.request.KakaoLoginRequest
55
import com.gotchai.api.presentation.v1.auth.request.RefreshRequest
66
import com.gotchai.api.presentation.v1.auth.response.RefreshResponse
77
import com.gotchai.api.presentation.v1.auth.response.SocialLoginResponse
8-
import com.gotchai.api.util.bodyDesc
8+
import com.gotchai.api.util.desc
99
import com.gotchai.api.util.fieldsOf
1010

1111
val appleLoginRequestFields =
12-
fieldsOf(AppleLoginRequest::idToken bodyDesc "ID 토큰")
12+
fieldsOf(AppleLoginRequest::idToken desc "ID 토큰")
1313

1414
val kakaoLoginRequestFields =
15-
fieldsOf(KakaoLoginRequest::accessToken bodyDesc "카카오 액세스 토큰")
15+
fieldsOf(KakaoLoginRequest::accessToken desc "카카오 액세스 토큰")
1616

1717
val refreshRequestFields =
18-
fieldsOf(RefreshRequest::refreshToken bodyDesc "리프레시 토큰")
18+
fieldsOf(RefreshRequest::refreshToken desc "리프레시 토큰")
1919

2020
val refreshResponseFields =
2121
fieldsOf(
22-
RefreshResponse::accessToken bodyDesc "액세스 토큰",
23-
RefreshResponse::refreshToken bodyDesc "리프레시 토큰"
22+
RefreshResponse::accessToken desc "액세스 토큰",
23+
RefreshResponse::refreshToken desc "리프레시 토큰"
2424
)
2525

2626
val socialLoginResponseFields =
2727
fieldsOf(
28-
SocialLoginResponse::accessToken bodyDesc "액세스 토큰",
29-
SocialLoginResponse::refreshToken bodyDesc "리프레시 토큰"
28+
SocialLoginResponse::accessToken desc "액세스 토큰",
29+
SocialLoginResponse::refreshToken desc "리프레시 토큰"
3030
)
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
package com.gotchai.api.docs
22

33
import com.gotchai.api.presentation.v1.badge.response.BadgeResponse
4-
import com.gotchai.api.util.bodyDesc
4+
import com.gotchai.api.util.desc
55
import com.gotchai.api.util.fieldsOf
66
import com.gotchai.api.util.listFieldsOf
77

88
val badgeResponseFields =
99
fieldsOf(
10-
BadgeResponse::id bodyDesc "식별자",
11-
BadgeResponse::examId bodyDesc "테스트 식별자",
12-
BadgeResponse::name bodyDesc "이름",
13-
BadgeResponse::description bodyDesc "설명",
14-
BadgeResponse::image bodyDesc "이미지 URI",
15-
BadgeResponse::tier bodyDesc "등급",
16-
BadgeResponse::createdAt bodyDesc "생성 날짜"
10+
BadgeResponse::id desc "식별자",
11+
BadgeResponse::examId desc "테스트 식별자",
12+
BadgeResponse::name desc "이름",
13+
BadgeResponse::description desc "설명",
14+
BadgeResponse::image desc "이미지 URI",
15+
BadgeResponse::tier desc "등급",
16+
BadgeResponse::createdAt desc "생성 날짜"
1717
)
1818

1919
val badgeListResponseFields =
2020
listFieldsOf(
21-
"list" bodyDesc "뱃지 리스트",
21+
"list" desc "뱃지 리스트",
2222
*badgeResponseFields.toTypedArray()
2323
)

0 commit comments

Comments
 (0)