test: 매장 API 테스트 추가

This commit is contained in:
이상진 2025-09-18 20:28:21 +09:00
parent 6a7e1906d2
commit bea544d0fc
2 changed files with 623 additions and 0 deletions

View File

@ -0,0 +1,519 @@
package roomescape.store
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.date.shouldBeAfter
import io.kotest.matchers.shouldBe
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import roomescape.admin.infrastructure.persistence.AdminEntity
import roomescape.admin.infrastructure.persistence.AdminPermissionLevel
import roomescape.admin.infrastructure.persistence.AdminType
import roomescape.auth.exception.AuthErrorCode
import roomescape.store.exception.StoreErrorCode
import roomescape.store.infrastructure.persistence.StoreEntity
import roomescape.store.infrastructure.persistence.StoreRepository
import roomescape.store.infrastructure.persistence.StoreStatus
import roomescape.store.web.StoreUpdateRequest
import roomescape.supports.*
class AdminStoreApiTest(
private val storeRepository: StoreRepository,
) : FunSpecSpringbootTest() {
init {
context("매장 상세 정보를 조회한다.") {
context("권한이 없으면 접근할 수 없다.") {
test("비회원") {
runExceptionTest(
method = HttpMethod.GET,
endpoint = "/admin/stores/${INVALID_PK}/detail",
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin().second,
method = HttpMethod.GET,
endpoint = "/admin/stores/${INVALID_PK}/detail",
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
context("관리자") {
AdminPermissionLevel.entries.flatMap {
if (it == AdminPermissionLevel.READ_SUMMARY) {
listOf(AdminType.HQ to it, AdminType.STORE to it)
} else {
listOf(AdminType.STORE to it)
}
}.forEach {
test("type: ${it.first} / permission: ${it.second}") {
val admin = AdminFixture.create(
type = it.first, permissionLevel = it.second
)
runExceptionTest(
token = testAuthUtil.adminLogin(admin).second,
method = HttpMethod.GET,
endpoint = "/admin/stores/${admin.storeId}/detail",
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
test("정상 응답") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val store = initialize("상세 조회를 위한 매장 생성") {
dummyInitializer.createStore()
}
runTest(
token = token,
on = {
get("/admin/stores/${store.id}/detail")
},
expect = {
statusCode(HttpStatus.OK.value())
assertProperties(
props = setOf("id", "name", "address", "contact", "businessRegNum", "region", "audit")
)
assertProperties(
props = setOf("code", "sidoName", "sigunguName"),
propsNameIfList = "region"
)
assertProperties(
props = setOf("createdAt", "createdBy", "updatedAt", "updatedBy"),
propsNameIfList = "audit"
)
}
)
}
test("매장 정보가 없으면 실패한다.") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.GET,
endpoint = "/admin/stores/${INVALID_PK}/detail",
expectedErrorCode = StoreErrorCode.STORE_NOT_FOUND
)
}
}
context("매장을 등록한다.") {
context("권한이 없으면 접근할 수 없다.") {
test("비회원") {
runExceptionTest(
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin().second,
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
context("관리자") {
AdminPermissionLevel.entries.flatMap {
if (it == AdminPermissionLevel.READ_SUMMARY || it == AdminPermissionLevel.READ_ALL) {
listOf(AdminType.HQ to it, AdminType.STORE to it)
} else {
listOf(AdminType.STORE to it)
}
}.forEach {
test("type: ${it.first} / permission: ${it.second}") {
val admin = AdminFixture.create(
type = it.first, permissionLevel = it.second
)
runExceptionTest(
token = testAuthUtil.adminLogin(admin).second,
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
test("정상 응답") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val registerRequest = StoreFixture.registerRequest
runTest(
token = token,
using = {
body(registerRequest)
},
on = {
post("/admin/stores")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
assertSoftly(storeRepository.findByIdOrNull(it.extract().path("data.id"))!!) {
this.name shouldBe registerRequest.name
this.createdBy shouldBe admin.id
this.updatedBy shouldBe admin.id
}
}
}
test("이름이 같은 매장이 있으면 실패한다.") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val store = initialize("중복 테스트를 위한 매장 등록") {
dummyInitializer.createStore()
}
runExceptionTest(
token = token,
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest.copy(name = store.name),
expectedErrorCode = StoreErrorCode.STORE_NAME_DUPLICATED
)
}
test("연락처가 같은 매장이 있으면 실패한다.") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val store = initialize("중복 테스트를 위한 매장 등록") {
dummyInitializer.createStore()
}
runExceptionTest(
token = token,
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest.copy(contact = store.contact),
expectedErrorCode = StoreErrorCode.STORE_CONTACT_DUPLICATED
)
}
test("주소가 같은 매장이 있으면 실패한다.") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val store = initialize("중복 테스트를 위한 매장 등록") {
dummyInitializer.createStore()
}
runExceptionTest(
token = token,
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest.copy(address = store.address),
expectedErrorCode = StoreErrorCode.STORE_ADDRESS_DUPLICATED
)
}
test("사업자번호가 같은 매장이 있으면 실패한다.") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val store = initialize("중복 테스트를 위한 매장 등록") {
dummyInitializer.createStore()
}
runExceptionTest(
token = token,
method = HttpMethod.POST,
endpoint = "/admin/stores",
requestBody = StoreFixture.registerRequest.copy(businessRegNum = store.businessRegNum),
expectedErrorCode = StoreErrorCode.STORE_BUSINESS_REG_NUM_DUPLICATED
)
}
}
context("매장 정보를 수정한다.") {
context("권한이 없으면 접근할 수 없다.") {
test("비회원") {
runExceptionTest(
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${INVALID_PK}",
requestBody = StoreUpdateRequest(),
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin().second,
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${INVALID_PK}",
requestBody = StoreUpdateRequest(),
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
context("관리자") {
AdminPermissionLevel.entries.flatMap {
if (it == AdminPermissionLevel.READ_SUMMARY || it == AdminPermissionLevel.READ_ALL) {
listOf(AdminType.HQ to it, AdminType.STORE to it)
} else {
listOf(AdminType.HQ to it)
}
}.forEach {
test("type: ${it.first} / permission: ${it.second}") {
val admin = AdminFixture.create(
type = it.first, permissionLevel = it.second
)
runExceptionTest(
token = testAuthUtil.adminLogin(admin).second,
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${INVALID_PK}",
requestBody = StoreUpdateRequest(),
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
context("정상 응답") {
lateinit var admin: AdminEntity
lateinit var token: String
lateinit var store: StoreEntity
beforeTest {
val adminTokenPair = testAuthUtil.defaultStoreAdminLogin()
admin = adminTokenPair.first
token = adminTokenPair.second
store = dummyInitializer.createStore()
}
test("이름만 수정한다.") {
runTest(
token = token,
using = {
body(StoreUpdateRequest(name = "안녕하세요"))
},
on = {
patch("/admin/stores/${store.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
assertSoftly(storeRepository.findByIdOrNull(store.id)!!) {
this.name shouldBe "안녕하세요"
this.address shouldBe store.address
this.contact shouldBe store.contact
this.updatedAt shouldBeAfter store.updatedAt
}
}
}
test("연락처만 수정한다.") {
val newContact = randomPhoneNumber()
runTest(
token = token,
using = {
body(StoreUpdateRequest(contact = newContact))
},
on = {
patch("/admin/stores/${store.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
assertSoftly(storeRepository.findByIdOrNull(store.id)!!) {
this.name shouldBe store.name
this.address shouldBe store.address
this.contact shouldBe newContact
this.updatedAt shouldBeAfter store.updatedAt
}
}
}
test("주소만 수정한다.") {
val newAddress = randomString()
runTest(
token = token,
using = {
body(StoreUpdateRequest(address = newAddress))
},
on = {
patch("/admin/stores/${store.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
assertSoftly(storeRepository.findByIdOrNull(store.id)!!) {
this.name shouldBe store.name
this.address shouldBe newAddress
this.contact shouldBe store.contact
this.updatedAt shouldBeAfter store.updatedAt
}
}
}
test("아무 값도 입력하지 않으면 바뀌지 않는다.") {
runTest(
token = token,
using = {
body(StoreUpdateRequest())
},
on = {
patch("/admin/stores/${store.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
assertSoftly(storeRepository.findByIdOrNull(store.id)!!) {
this.name shouldBe store.name
this.address shouldBe store.address
this.contact shouldBe store.contact
this.updatedAt shouldBe store.updatedAt
}
}
}
}
context("실패 응답") {
lateinit var admin: AdminEntity
lateinit var token: String
lateinit var store: StoreEntity
beforeTest {
val adminTokenPair = testAuthUtil.defaultStoreAdminLogin()
admin = adminTokenPair.first
token = adminTokenPair.second
store = dummyInitializer.createStore()
}
test("매장이 없으면 실패한다.") {
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${INVALID_PK}",
requestBody = StoreUpdateRequest(),
expectedErrorCode = StoreErrorCode.STORE_NOT_FOUND
)
}
test("이름이 같은 매장이 있으면 실패한다.") {
val newStore = dummyInitializer.createStore()
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${store.id}",
requestBody = StoreUpdateRequest(name = newStore.name),
expectedErrorCode = StoreErrorCode.STORE_NAME_DUPLICATED
)
}
test("연락처가 같은 매장이 있으면 실패한다.") {
val newStore = dummyInitializer.createStore()
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${store.id}",
requestBody = StoreUpdateRequest(contact = newStore.contact),
expectedErrorCode = StoreErrorCode.STORE_CONTACT_DUPLICATED
)
}
test("주소가 같은 매장이 있으면 실패한다.") {
val newStore = dummyInitializer.createStore()
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/stores/${store.id}",
requestBody = StoreUpdateRequest(address = newStore.address),
expectedErrorCode = StoreErrorCode.STORE_ADDRESS_DUPLICATED
)
}
}
}
context("매장을 비활성화한다.") {
context("권한이 없으면 접근할 수 없다.") {
test("비회원") {
runExceptionTest(
method = HttpMethod.POST,
endpoint = "/admin/stores/${INVALID_PK}/disable",
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin().second,
method = HttpMethod.POST,
endpoint = "/admin/stores/${INVALID_PK}/disable",
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
context("관리자") {
AdminPermissionLevel.entries.flatMap {
if (it == AdminPermissionLevel.READ_SUMMARY || it == AdminPermissionLevel.READ_ALL) {
listOf(AdminType.HQ to it, AdminType.STORE to it)
} else {
listOf(AdminType.STORE to it)
}
}.forEach {
test("type: ${it.first} / permission: ${it.second}") {
val admin = AdminFixture.create(
type = it.first, permissionLevel = it.second
)
runExceptionTest(
token = testAuthUtil.adminLogin(admin).second,
method = HttpMethod.POST,
endpoint = "/admin/stores/${INVALID_PK}/disable",
requestBody = StoreUpdateRequest(),
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
test("정상 응답") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
val storeId = dummyInitializer.createStore().id
runTest(
token = token,
on = {
post("/admin/stores/${storeId}/disable")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
assertSoftly(storeRepository.findByIdOrNull(storeId)!!) {
this.status shouldBe StoreStatus.DISABLED
this.updatedBy shouldBe admin.id
}
}
}
test("매장이 없으면 실패한다.") {
val (admin, token) = testAuthUtil.defaultHqAdminLogin()
runExceptionTest(
token = token,
method = HttpMethod.POST,
endpoint = "/admin/stores/${INVALID_PK}/disable",
expectedErrorCode = StoreErrorCode.STORE_NOT_FOUND
)
}
}
}
}

View File

@ -0,0 +1,104 @@
package roomescape.store
import org.hamcrest.CoreMatchers.equalTo
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import roomescape.store.exception.StoreErrorCode
import roomescape.store.infrastructure.persistence.StoreEntity
import roomescape.supports.*
class StoreApiTest: FunSpecSpringbootTest() {
init {
context("모든 매장의 id / 이름을 조회한다.") {
context("정상 응답") {
lateinit var stores: List<StoreEntity>
beforeTest {
stores = initialize("서울에 2개 매장, 부산에 1개 매장 생성") {
listOf(
dummyInitializer.createStore(regionCode = "1111000000"),
dummyInitializer.createStore(regionCode = "1114000000"),
dummyInitializer.createStore(regionCode = "2611000000")
)
}
}
test("지역 정보를 입력하지 않으면 전체 매장을 조회한다.") {
runTest(
on = {
get("/stores")
},
expect = {
statusCode(HttpStatus.OK.value())
body("data.stores.size()", equalTo(stores.size))
}
)
}
test("시/도 로만 조회한다.") {
val sidoCode = "11"
runTest(
on = {
get("/stores?sido=${sidoCode}")
},
expect = {
statusCode(HttpStatus.OK.value())
body("data.stores.size()", equalTo(2))
}
)
}
test("시/도 + 시/군/구로 조회한다.") {
val sidoCode = "11"
val sigunguCode = "110"
runTest(
on = {
get("/stores?sido=${sidoCode}&sigungu=${sigunguCode}")
},
expect = {
statusCode(HttpStatus.OK.value())
body("data.stores.size()", equalTo(1))
}
)
}
}
test("시/도 입력 없이 시/군/구 만 입력하면 실패한다.") {
runExceptionTest(
method = HttpMethod.GET,
endpoint = "/stores?sigungu=110",
expectedErrorCode = StoreErrorCode.SIDO_CODE_REQUIRED
)
}
}
context("개별 매장 정보를 조회한다.") {
test("정상 응답") {
val store = initialize("조회용 매장 생성") {
dummyInitializer.createStore()
}
runTest(
on = {
get("/stores/${store.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
assertProperties(
props = setOf("id", "name", "address", "contact", "businessRegNum")
)
}
)
}
test("매장이 없으면 실패한다.") {
runExceptionTest(
method = HttpMethod.GET,
endpoint = "/stores/${INVALID_PK}",
expectedErrorCode = StoreErrorCode.STORE_NOT_FOUND
)
}
}
}
}