add customer controller with find by guid method

This commit is contained in:
Denis Savosin
2024-10-14 12:25:30 +07:00
parent fecbee8b28
commit 0a38f7fdd8
10 changed files with 189 additions and 32 deletions

View File

@@ -0,0 +1,30 @@
package com.example.demo.http.controllers
import com.example.demo.http.exceptions.NotFoundException
import com.example.demo.services.database.customer.CustomerService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.util.*
@RestController
@RequestMapping(value = ["/api/customer"], produces = [MediaType.APPLICATION_JSON_VALUE])
class CustomerController(
@Autowired
private val customerService: CustomerService,
) {
@GetMapping("/{guid}")
@Throws(NotFoundException::class)
fun getCustomer(
@PathVariable guid: UUID,
): ResponseEntity<Any> {
val customer = customerService.findByGuid(guid) ?: throw NotFoundException()
return ResponseEntity(customer, HttpStatus.OK)
}
}

View File

@@ -10,6 +10,7 @@ import java.time.OffsetDateTime
import java.util.* import java.util.*
@Table("customer") @Table("customer")
@Serializable
data class Customer( data class Customer(
@Id @Id
val id: Long?, val id: Long?,

View File

@@ -0,0 +1,9 @@
package com.example.demo.models
import kotlinx.serialization.Serializable
@Serializable
data class CustomerExtended(
val customer: Customer,
val city: City?,
)

View File

@@ -1,11 +1,12 @@
package com.example.demo.services.database.customer package com.example.demo.services.database.customer
import com.example.demo.models.Customer import com.example.demo.models.Customer
import com.example.demo.models.CustomerExtended
import com.example.demo.services.database.exceptions.CityNotFoundException import com.example.demo.services.database.exceptions.CityNotFoundException
import java.util.* import java.util.*
interface CustomerService { interface CustomerService {
fun findByGuid(guid: UUID): Customer? fun findByGuid(guid: UUID): CustomerExtended?
@Throws(CityNotFoundException::class) @Throws(CityNotFoundException::class)
fun create(name: String, cityGuid: UUID?): Customer fun create(name: String, cityGuid: UUID?): Customer

View File

@@ -1,6 +1,7 @@
package com.example.demo.services.database.customer package com.example.demo.services.database.customer
import com.example.demo.models.Customer import com.example.demo.models.Customer
import com.example.demo.models.CustomerExtended
import com.example.demo.providers.CityRepository import com.example.demo.providers.CityRepository
import com.example.demo.providers.CustomerRepository import com.example.demo.providers.CustomerRepository
import com.example.demo.services.database.exceptions.CityNotFoundException import com.example.demo.services.database.exceptions.CityNotFoundException
@@ -11,7 +12,17 @@ class CustomerServiceImpl(
private val customerRepository: CustomerRepository, private val customerRepository: CustomerRepository,
private val cityRepository: CityRepository private val cityRepository: CityRepository
): CustomerService { ): CustomerService {
override fun findByGuid(guid: UUID): Customer? = customerRepository.findByGuid(guid) override fun findByGuid(guid: UUID): CustomerExtended? {
val customer = customerRepository.findByGuid(guid) ?: return null
if (customer.cityId == null) {
return CustomerExtended(customer, null)
}
val city = cityRepository.findById(customer.cityId)
return CustomerExtended(customer, city.orElse(null))
}
override fun create(name: String, cityGuid: UUID?): Customer { override fun create(name: String, cityGuid: UUID?): Customer {
val cityId: Long? = cityGuid?.let { val cityId: Long? = cityGuid?.let {

View File

@@ -0,0 +1,106 @@
package com.example.demo.http.controllers
import com.example.demo.BaseUnitTest
import com.example.demo.http.responses.ResponseStatus
import com.example.demo.models.City
import com.example.demo.models.Customer
import com.example.demo.models.CustomerExtended
import com.example.demo.services.database.customer.CustomerService
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.time.OffsetDateTime
import java.util.*
import kotlin.test.Test
@WebMvcTest(CustomerController::class)
class CustomerControllerTest(
@Autowired val mockMvc: MockMvc,
): BaseUnitTest() {
@MockBean
private lateinit var customerService: CustomerService
@Test
fun getCustomer_successWithCity() {
val customerId = 22.toLong()
val cityId = 11.toLong()
val customerGuid = UUID.randomUUID()
val customerExtended = CustomerExtended(
customer = Customer(
id = customerId,
guid = customerGuid,
name = "Test Person",
cityId = cityId,
createdAt = OffsetDateTime.now().minusHours(1),
updatedAt = OffsetDateTime.now(),
),
city = City(
id = cityId,
guid = UUID.randomUUID(),
name = "Test City",
createdAt = OffsetDateTime.now().minusWeeks(1),
updatedAt = null,
deletedAt = null
)
)
whenever(customerService.findByGuid(
eq(customerGuid),
)) doReturn customerExtended
mockMvc.get("/api/customer/$customerGuid")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.customer.id") { value(customerId) } }
.andExpect { jsonPath("\$.customer.cityId") { value(cityId) } }
.andExpect { jsonPath("\$.city.id") { value(cityId) } }
}
@Test
fun getCustomer_successNoCity() {
val customerId = 22.toLong()
val customerGuid = UUID.randomUUID()
val customerExtended = CustomerExtended(
customer = Customer(
id = customerId,
guid = customerGuid,
name = "Test Person",
cityId = null,
createdAt = OffsetDateTime.now().minusHours(1),
updatedAt = OffsetDateTime.now(),
),
city = null,
)
whenever(customerService.findByGuid(
eq(customerGuid),
)) doReturn customerExtended
mockMvc.get("/api/customer/$customerGuid")
.andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.customer.id") { value(customerId) } }
.andExpect { jsonPath("\$.customer.cityId") { value(null) } }
.andExpect { jsonPath("\$.city") { value(null) } }
}
@Test
fun getCustomer_successNotFound() {
val customerGuid = UUID.randomUUID()
whenever(customerService.findByGuid(
eq(customerGuid),
)) doReturn null
mockMvc.get("/api/customer/$customerGuid")
.andExpect { status { isNotFound() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } }
}
}

View File

@@ -5,26 +5,24 @@ import org.hamcrest.core.StringContains
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import kotlin.test.Test import kotlin.test.Test
@WebMvcTest(GreetingController::class) @WebMvcTest(GreetingController::class)
class GreetingControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { class GreetingControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
@Test @Test
fun greetings_shouldSeeGreetingMessage() { fun greetings_shouldSeeGreetingMessage() {
mockMvc.perform(get("/greeting")) mockMvc.get("/greeting")
.andExpect(status().isOk) .andExpect { status { isOk() } }
.andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect { content { contentType("text/plain;charset=UTF-8") } }
.andExpect(content().string("Hello World!")) .andExpect { content { string("Hello World!") } }
} }
@Test @Test
fun exampleHtml_shouldSeeRenderedHtml() { fun exampleHtml_shouldSeeRenderedHtml() {
mockMvc.perform(get("/example/html")) mockMvc.get("/example/html")
.andExpect(status().isOk) .andExpect { status { isOk() } }
.andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect { content { contentType("text/html;charset=UTF-8") } }
.andExpect(content().string(StringContains("Product"))) .andExpect { content { string(StringContains("Product")) } }
} }
} }

View File

@@ -52,7 +52,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
)) doReturn product )) doReturn product
mockMvc.get("/api/product/$guid") mockMvc.get("/api/product/$guid")
.andExpect { status { status { isOk() } } } .andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.id") { value(product.id.toString()) } } .andExpect { jsonPath("\$.id") { value(product.id.toString()) } }
.andExpect { jsonPath("\$.guid") { value(guid.toString()) } } .andExpect { jsonPath("\$.guid") { value(guid.toString()) } }
@@ -70,7 +70,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
)) doReturn null )) doReturn null
mockMvc.get("/api/product/$guid") mockMvc.get("/api/product/$guid")
.andExpect { status { status { isNotFound() } } } .andExpect { status { isNotFound() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } }
} }
@@ -94,7 +94,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
))) )))
mockMvc.get("/api/product?page=1&size=2&sort=createdAt,desc") mockMvc.get("/api/product?page=1&size=2&sort=createdAt,desc")
.andExpect { status { status { isOk() } } } .andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.meta.total") { value(1) } } .andExpect { jsonPath("\$.meta.total") { value(1) } }
.andExpect { jsonPath("\$.meta.pages") { value(1) } } .andExpect { jsonPath("\$.meta.pages") { value(1) } }
@@ -135,7 +135,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
contentType = MediaType.APPLICATION_JSON contentType = MediaType.APPLICATION_JSON
content = reqBody content = reqBody
} }
.andExpect { status { status { isCreated() } } } .andExpect { status { isCreated() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.id") { value(productId) } } .andExpect { jsonPath("\$.id") { value(productId) } }
} }
@@ -150,7 +150,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
contentType = MediaType.APPLICATION_JSON contentType = MediaType.APPLICATION_JSON
content = reqBody content = reqBody
} }
.andExpect { status { status { isBadRequest() } } } .andExpect { status { isBadRequest() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatus.BAD_REQUEST.status) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.BAD_REQUEST.status) } }
.andExpect { jsonPath("\$.cause") { contains("name") } } .andExpect { jsonPath("\$.cause") { contains("name") } }
@@ -168,7 +168,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
contentType = MediaType.APPLICATION_JSON contentType = MediaType.APPLICATION_JSON
content = reqBody content = reqBody
} }
.andExpect { status { status { isUnprocessableEntity() } } } .andExpect { status { isUnprocessableEntity() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatus.UNPROCESSABLE.status) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.UNPROCESSABLE.status) } }
.andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } } .andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } }
@@ -194,8 +194,8 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
) )
mockMvc.delete("/api/product/${guid}") mockMvc.delete("/api/product/${guid}")
.andExpect { status { status { isOk() } } } .andExpect { status { isOk() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { jsonPath("\$.status") { value(ResponseStatus.OK.status) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.OK.status) } }
} }
} }

View File

@@ -70,7 +70,7 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
) doReturn null ) doReturn null
mockMvc.get("/shop/common-info") mockMvc.get("/shop/common-info")
.andExpect { status { status { isNotFound() } } } .andExpect { status { isNotFound() } }
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
.andExpect { content { json("""{"status":"not found"}""") } } .andExpect { content { json("""{"status":"not found"}""") } }
} }

View File

@@ -41,22 +41,23 @@ class CustomerServiceImplDbTest: BaseDbTest() {
try { try {
city = cityRepository.save(city) city = cityRepository.save(city)
customerServiceImpl.create(nameOne, city.guid).let { customerServiceImpl.create(nameTwo, null).let {
customerIds += it.id ?: fail("customerWithCity id is null") customerIds += it.id ?: fail("customerWithNoCity id is null")
assertEquals(city.id, it.cityId) assertNull(it.cityId)
assertNotNull(it.createdAt) assertNotNull(it.createdAt)
assertNull(it.updatedAt) assertNull(it.updatedAt)
} }
val customerWithNoCity = customerServiceImpl.create(nameTwo, null) val customerWithCity = customerServiceImpl.create(nameOne, city.guid)
customerIds += customerWithNoCity.id ?: fail("customerWithNoCity id is null") customerIds += customerWithCity.id ?: fail("customerWithCity id is null")
assertNull(customerWithNoCity.cityId) assertEquals(city.id, customerWithCity.cityId)
assertNotNull(customerWithNoCity.createdAt) assertNotNull(customerWithCity.createdAt)
assertNull(customerWithNoCity.updatedAt) assertNull(customerWithCity.updatedAt)
val existedCustomer = customerServiceImpl.findByGuid(customerWithNoCity.guid) val existedCustomer = customerServiceImpl.findByGuid(customerWithCity.guid)
assertNotNull(existedCustomer) assertNotNull(existedCustomer)
assertEquals(customerWithNoCity.id, existedCustomer.id) assertEquals(customerWithCity.id, existedCustomer.customer.id)
assertEquals(city.id, existedCustomer.city?.id)
assertThrows<CityNotFoundException> { assertThrows<CityNotFoundException> {
customerServiceImpl.create(nameThree, UUID.randomUUID()) customerServiceImpl.create(nameThree, UUID.randomUUID())