From 0a38f7fdd883de7f502bb3815e8a8a8d2cfc3da5 Mon Sep 17 00:00:00 2001 From: Denis Savosin Date: Mon, 14 Oct 2024 12:25:30 +0700 Subject: [PATCH] add customer controller with find by guid method --- .../http/controllers/CustomerController.kt | 30 +++++ .../com/example/demo/models/Customer.kt | 1 + .../example/demo/models/CustomerExtended.kt | 9 ++ .../database/customer/CustomerService.kt | 3 +- .../database/customer/CustomerServiceImpl.kt | 13 ++- .../controllers/CustomerControllerTest.kt | 106 ++++++++++++++++++ .../controllers/GreetingControllerTest.kt | 20 ++-- .../http/controllers/ProductControllerTest.kt | 16 +-- .../http/controllers/ShopControllerTest.kt | 2 +- .../customer/CustomerServiceImplDbTest.kt | 21 ++-- 10 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/com/example/demo/http/controllers/CustomerController.kt create mode 100644 src/main/kotlin/com/example/demo/models/CustomerExtended.kt create mode 100644 src/test/kotlin/com/example/demo/http/controllers/CustomerControllerTest.kt diff --git a/src/main/kotlin/com/example/demo/http/controllers/CustomerController.kt b/src/main/kotlin/com/example/demo/http/controllers/CustomerController.kt new file mode 100644 index 0000000..9c4fadf --- /dev/null +++ b/src/main/kotlin/com/example/demo/http/controllers/CustomerController.kt @@ -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 { + val customer = customerService.findByGuid(guid) ?: throw NotFoundException() + + return ResponseEntity(customer, HttpStatus.OK) + } +} diff --git a/src/main/kotlin/com/example/demo/models/Customer.kt b/src/main/kotlin/com/example/demo/models/Customer.kt index 4e21ae2..13f55c7 100644 --- a/src/main/kotlin/com/example/demo/models/Customer.kt +++ b/src/main/kotlin/com/example/demo/models/Customer.kt @@ -10,6 +10,7 @@ import java.time.OffsetDateTime import java.util.* @Table("customer") +@Serializable data class Customer( @Id val id: Long?, diff --git a/src/main/kotlin/com/example/demo/models/CustomerExtended.kt b/src/main/kotlin/com/example/demo/models/CustomerExtended.kt new file mode 100644 index 0000000..467c838 --- /dev/null +++ b/src/main/kotlin/com/example/demo/models/CustomerExtended.kt @@ -0,0 +1,9 @@ +package com.example.demo.models + +import kotlinx.serialization.Serializable + +@Serializable +data class CustomerExtended( + val customer: Customer, + val city: City?, +) diff --git a/src/main/kotlin/com/example/demo/services/database/customer/CustomerService.kt b/src/main/kotlin/com/example/demo/services/database/customer/CustomerService.kt index 379c860..7894928 100644 --- a/src/main/kotlin/com/example/demo/services/database/customer/CustomerService.kt +++ b/src/main/kotlin/com/example/demo/services/database/customer/CustomerService.kt @@ -1,11 +1,12 @@ package com.example.demo.services.database.customer import com.example.demo.models.Customer +import com.example.demo.models.CustomerExtended import com.example.demo.services.database.exceptions.CityNotFoundException import java.util.* interface CustomerService { - fun findByGuid(guid: UUID): Customer? + fun findByGuid(guid: UUID): CustomerExtended? @Throws(CityNotFoundException::class) fun create(name: String, cityGuid: UUID?): Customer diff --git a/src/main/kotlin/com/example/demo/services/database/customer/CustomerServiceImpl.kt b/src/main/kotlin/com/example/demo/services/database/customer/CustomerServiceImpl.kt index dbe78fa..b913c4b 100644 --- a/src/main/kotlin/com/example/demo/services/database/customer/CustomerServiceImpl.kt +++ b/src/main/kotlin/com/example/demo/services/database/customer/CustomerServiceImpl.kt @@ -1,6 +1,7 @@ package com.example.demo.services.database.customer import com.example.demo.models.Customer +import com.example.demo.models.CustomerExtended import com.example.demo.providers.CityRepository import com.example.demo.providers.CustomerRepository import com.example.demo.services.database.exceptions.CityNotFoundException @@ -11,7 +12,17 @@ class CustomerServiceImpl( private val customerRepository: CustomerRepository, private val cityRepository: CityRepository ): 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 { val cityId: Long? = cityGuid?.let { diff --git a/src/test/kotlin/com/example/demo/http/controllers/CustomerControllerTest.kt b/src/test/kotlin/com/example/demo/http/controllers/CustomerControllerTest.kt new file mode 100644 index 0000000..ae18fac --- /dev/null +++ b/src/test/kotlin/com/example/demo/http/controllers/CustomerControllerTest.kt @@ -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) } } + } +} diff --git a/src/test/kotlin/com/example/demo/http/controllers/GreetingControllerTest.kt b/src/test/kotlin/com/example/demo/http/controllers/GreetingControllerTest.kt index 584c0f4..9d62d97 100644 --- a/src/test/kotlin/com/example/demo/http/controllers/GreetingControllerTest.kt +++ b/src/test/kotlin/com/example/demo/http/controllers/GreetingControllerTest.kt @@ -5,26 +5,24 @@ import org.hamcrest.core.StringContains import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.test.web.servlet.get import kotlin.test.Test @WebMvcTest(GreetingController::class) class GreetingControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { @Test fun greetings_shouldSeeGreetingMessage() { - mockMvc.perform(get("/greeting")) - .andExpect(status().isOk) - .andExpect(content().contentType("text/plain;charset=UTF-8")) - .andExpect(content().string("Hello World!")) + mockMvc.get("/greeting") + .andExpect { status { isOk() } } + .andExpect { content { contentType("text/plain;charset=UTF-8") } } + .andExpect { content { string("Hello World!") } } } @Test fun exampleHtml_shouldSeeRenderedHtml() { - mockMvc.perform(get("/example/html")) - .andExpect(status().isOk) - .andExpect(content().contentType("text/html;charset=UTF-8")) - .andExpect(content().string(StringContains("Product"))) + mockMvc.get("/example/html") + .andExpect { status { isOk() } } + .andExpect { content { contentType("text/html;charset=UTF-8") } } + .andExpect { content { string(StringContains("Product")) } } } } diff --git a/src/test/kotlin/com/example/demo/http/controllers/ProductControllerTest.kt b/src/test/kotlin/com/example/demo/http/controllers/ProductControllerTest.kt index 4c76b2f..81953a6 100644 --- a/src/test/kotlin/com/example/demo/http/controllers/ProductControllerTest.kt +++ b/src/test/kotlin/com/example/demo/http/controllers/ProductControllerTest.kt @@ -52,7 +52,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { )) doReturn product mockMvc.get("/api/product/$guid") - .andExpect { status { status { isOk() } } } + .andExpect { status { isOk() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { jsonPath("\$.id") { value(product.id.toString()) } } .andExpect { jsonPath("\$.guid") { value(guid.toString()) } } @@ -70,7 +70,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { )) doReturn null mockMvc.get("/api/product/$guid") - .andExpect { status { status { isNotFound() } } } + .andExpect { status { isNotFound() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .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") - .andExpect { status { status { isOk() } } } + .andExpect { status { isOk() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { jsonPath("\$.meta.total") { value(1) } } .andExpect { jsonPath("\$.meta.pages") { value(1) } } @@ -135,7 +135,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { contentType = MediaType.APPLICATION_JSON content = reqBody } - .andExpect { status { status { isCreated() } } } + .andExpect { status { isCreated() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { jsonPath("\$.id") { value(productId) } } } @@ -150,7 +150,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { contentType = MediaType.APPLICATION_JSON content = reqBody } - .andExpect { status { status { isBadRequest() } } } + .andExpect { status { isBadRequest() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.BAD_REQUEST.status) } } .andExpect { jsonPath("\$.cause") { contains("name") } } @@ -168,7 +168,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { contentType = MediaType.APPLICATION_JSON content = reqBody } - .andExpect { status { status { isUnprocessableEntity() } } } + .andExpect { status { isUnprocessableEntity() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.UNPROCESSABLE.status) } } .andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } } @@ -194,8 +194,8 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { ) mockMvc.delete("/api/product/${guid}") - .andExpect { status { status { isOk() } } } + .andExpect { status { isOk() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { jsonPath("\$.status") { value(ResponseStatus.OK.status) } } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/example/demo/http/controllers/ShopControllerTest.kt b/src/test/kotlin/com/example/demo/http/controllers/ShopControllerTest.kt index 7ec9f10..55f3a51 100644 --- a/src/test/kotlin/com/example/demo/http/controllers/ShopControllerTest.kt +++ b/src/test/kotlin/com/example/demo/http/controllers/ShopControllerTest.kt @@ -70,7 +70,7 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { ) doReturn null mockMvc.get("/shop/common-info") - .andExpect { status { status { isNotFound() } } } + .andExpect { status { isNotFound() } } .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } .andExpect { content { json("""{"status":"not found"}""") } } } diff --git a/src/test/kotlin/com/example/demo/services/database/customer/CustomerServiceImplDbTest.kt b/src/test/kotlin/com/example/demo/services/database/customer/CustomerServiceImplDbTest.kt index 486d003..b77d951 100644 --- a/src/test/kotlin/com/example/demo/services/database/customer/CustomerServiceImplDbTest.kt +++ b/src/test/kotlin/com/example/demo/services/database/customer/CustomerServiceImplDbTest.kt @@ -41,22 +41,23 @@ class CustomerServiceImplDbTest: BaseDbTest() { try { city = cityRepository.save(city) - customerServiceImpl.create(nameOne, city.guid).let { - customerIds += it.id ?: fail("customerWithCity id is null") - assertEquals(city.id, it.cityId) + customerServiceImpl.create(nameTwo, null).let { + customerIds += it.id ?: fail("customerWithNoCity id is null") + assertNull(it.cityId) assertNotNull(it.createdAt) assertNull(it.updatedAt) } - val customerWithNoCity = customerServiceImpl.create(nameTwo, null) - customerIds += customerWithNoCity.id ?: fail("customerWithNoCity id is null") - assertNull(customerWithNoCity.cityId) - assertNotNull(customerWithNoCity.createdAt) - assertNull(customerWithNoCity.updatedAt) + val customerWithCity = customerServiceImpl.create(nameOne, city.guid) + customerIds += customerWithCity.id ?: fail("customerWithCity id is null") + assertEquals(city.id, customerWithCity.cityId) + assertNotNull(customerWithCity.createdAt) + assertNull(customerWithCity.updatedAt) - val existedCustomer = customerServiceImpl.findByGuid(customerWithNoCity.guid) + val existedCustomer = customerServiceImpl.findByGuid(customerWithCity.guid) assertNotNull(existedCustomer) - assertEquals(customerWithNoCity.id, existedCustomer.id) + assertEquals(customerWithCity.id, existedCustomer.customer.id) + assertEquals(city.id, existedCustomer.city?.id) assertThrows { customerServiceImpl.create(nameThree, UUID.randomUUID())