diff --git a/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt b/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt index 24538ee..0cfe468 100644 --- a/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt +++ b/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt @@ -5,7 +5,9 @@ import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.github.dannecron.demo.config.properties.KafkaProperties import com.github.dannecron.demo.config.properties.ValidationProperties -import com.github.dannecron.demo.providers.* +import com.github.dannecron.demo.providers.CityRepository +import com.github.dannecron.demo.providers.CustomerRepository +import com.github.dannecron.demo.providers.ProductRepository import com.github.dannecron.demo.services.database.city.CityService import com.github.dannecron.demo.services.database.city.CityServiceImpl import com.github.dannecron.demo.services.database.customer.CustomerService @@ -35,9 +37,6 @@ class AppConfig( configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) } - @Bean - fun shopProvider(): ShopProvider = MockedShopProvider() - @Bean fun productService( @Autowired productRepository: ProductRepository, diff --git a/src/main/kotlin/com/github/dannecron/demo/http/controllers/ShopController.kt b/src/main/kotlin/com/github/dannecron/demo/http/controllers/ShopController.kt deleted file mode 100644 index 58116d0..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/http/controllers/ShopController.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.http.exceptions.NotFoundException -import com.github.dannecron.demo.providers.ShopProvider -import jakarta.servlet.http.HttpServletResponse -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.encodeToJsonElement -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.ResponseBody -import org.springframework.web.bind.annotation.RestController - -@RestController -class ShopController ( - @field:Autowired private val shopProvider: ShopProvider, -) { - @GetMapping(value = ["/shop/common-info"], produces = ["application/json"]) - @ResponseBody - @Throws(NotFoundException::class) - fun commonInfo(response: HttpServletResponse): String { - val shop = shopProvider.getRandomShop() ?: throw NotFoundException() - - return Json.encodeToJsonElement(value = mapOf( - "customers" to mapOf( - "withMoreUndeliveredOrdersThanDelivered" to shop.getCustomersWithMoreUndeliveredOrdersThanDelivered().map { cus -> cus.name } - ), - "products" to mapOf( - "orderedByAllCustomers" to shop.getSetOfProductsOrderedByEveryCustomer().map { pr -> pr.name }, - ) - )).toString() - } -} diff --git a/src/main/kotlin/com/github/dannecron/demo/models/CustomerLocal.kt b/src/main/kotlin/com/github/dannecron/demo/models/CustomerLocal.kt deleted file mode 100644 index 1d8140b..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/models/CustomerLocal.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.dannecron.demo.models - -data class CustomerLocal(val name: String, val city: City, val orders: List) { - /** - * Return the most expensive product among all delivered products - */ - fun getMostExpensiveDeliveredProduct(): Product? = orders.filter { ord -> ord.isDelivered } - .flatMap { ord -> ord.products } - .maxByOrNull { pr -> pr.price } - - fun getMostExpensiveOrderedProduct(): Product? = orders.flatMap { ord -> ord.products }.maxByOrNull { pr -> pr.price } - - fun getOrderedProducts(): Set = orders.flatMap { order -> order.products }.toSet() - - fun getTotalOrderPrice(): Double = orders.flatMap { ord -> ord.products }.sumOf { pr -> pr.getPriceDouble()} -} diff --git a/src/main/kotlin/com/github/dannecron/demo/models/OrderLocal.kt b/src/main/kotlin/com/github/dannecron/demo/models/OrderLocal.kt deleted file mode 100644 index 164fd93..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/models/OrderLocal.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.dannecron.demo.models - -data class OrderLocal(val products: List, val isDelivered: Boolean) diff --git a/src/main/kotlin/com/github/dannecron/demo/models/Shop.kt b/src/main/kotlin/com/github/dannecron/demo/models/Shop.kt deleted file mode 100644 index a374db6..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/models/Shop.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.dannecron.demo.models - -data class Shop(val name: String, val customers: List) { - fun checkAllCustomersAreFrom(city: City): Boolean = customers.count { cus -> cus.city == city } == customers.count() - - fun countCustomersFrom(city: City): Int = customers.count { cus -> cus.city == city } - - fun getCitiesCustomersAreFrom(): Set = customers.map { cus -> cus.city }.toSet() - - fun findAnyCustomerFrom(city: City): CustomerLocal? = customers.firstOrNull { cus -> cus.city == city } - - fun getAllOrderedProducts(): Set = customers.flatMap { cus -> cus.getOrderedProducts() }.toSet() - - fun getCustomersFrom(city: City): List = customers.filter { cus -> cus.city == city } - - fun getCustomersSortedByNumberOfOrders(): List = customers.sortedBy { cus -> cus.orders.count() } - - fun getCustomerWithMaximumNumberOfOrders(): CustomerLocal? = customers.maxByOrNull { cus -> cus.orders.count() } - - /** - * Return customers who have more undelivered orders than delivered - */ - fun getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set = customers.partition(predicate = fun (cus): Boolean { - val (del, undel) = cus.orders.partition { ord -> ord.isDelivered } - - return del.count() < undel.count() - }).first.toSet() - - fun getNumberOfTimesProductWasOrdered(product: Product): Int = customers - .flatMap { cus -> cus.orders.flatMap { ord -> ord.products } } - .count { pr -> pr.name == product.name } - - /** - * Return the set of products that were ordered by every customer. - * Note: a customer may order the same product for several times. - */ - fun getSetOfProductsOrderedByEveryCustomer(): Set { - val products = customers.flatMap { cus -> cus.orders.flatMap { ord -> ord.products } }.toSet() - - return customers.fold(products) { orderedProducts, cus -> - orderedProducts.intersect(cus.orders.flatMap { ord -> ord.products }.toSet()) - }.toSet() - } - - fun groupCustomersByCity(): Map> = customers.groupBy { cus -> cus.city } - - fun hasCustomerFrom(city: City): Boolean = customers.any { cus -> cus.city == city } -} diff --git a/src/main/kotlin/com/github/dannecron/demo/models/Order.kt b/src/main/kotlin/com/github/dannecron/demo/models/order/Order.kt similarity index 95% rename from src/main/kotlin/com/github/dannecron/demo/models/Order.kt rename to src/main/kotlin/com/github/dannecron/demo/models/order/Order.kt index 05a153a..dde54c3 100644 --- a/src/main/kotlin/com/github/dannecron/demo/models/Order.kt +++ b/src/main/kotlin/com/github/dannecron/demo/models/order/Order.kt @@ -1,4 +1,4 @@ -package com.github.dannecron.demo.models +package com.github.dannecron.demo.models.order import com.github.dannecron.demo.services.serializables.OffsetDateTimeSerialization import com.github.dannecron.demo.services.serializables.UuidSerialization diff --git a/src/main/kotlin/com/github/dannecron/demo/models/OrderProduct.kt b/src/main/kotlin/com/github/dannecron/demo/models/order/OrderProduct.kt similarity index 96% rename from src/main/kotlin/com/github/dannecron/demo/models/OrderProduct.kt rename to src/main/kotlin/com/github/dannecron/demo/models/order/OrderProduct.kt index c5b3ee3..30df89c 100644 --- a/src/main/kotlin/com/github/dannecron/demo/models/OrderProduct.kt +++ b/src/main/kotlin/com/github/dannecron/demo/models/order/OrderProduct.kt @@ -1,4 +1,4 @@ -package com.github.dannecron.demo.models +package com.github.dannecron.demo.models.order import com.github.dannecron.demo.services.serializables.OffsetDateTimeSerialization import com.github.dannecron.demo.services.serializables.UuidSerialization diff --git a/src/main/kotlin/com/github/dannecron/demo/models/order/OrderWithProducts.kt b/src/main/kotlin/com/github/dannecron/demo/models/order/OrderWithProducts.kt new file mode 100644 index 0000000..c38cf38 --- /dev/null +++ b/src/main/kotlin/com/github/dannecron/demo/models/order/OrderWithProducts.kt @@ -0,0 +1,12 @@ +package com.github.dannecron.demo.models.order + +import com.github.dannecron.demo.models.Product + +data class OrderWithProducts( + val order: Order, + val products: List, +) { + fun getMostExpensiveOrderedProduct(): Product? = products.maxByOrNull { pr -> pr.price } + + fun getTotalOrderPrice(): Double = products.sumOf { pr -> pr.getPriceDouble() } +} diff --git a/src/main/kotlin/com/github/dannecron/demo/providers/MockedShopProvider.kt b/src/main/kotlin/com/github/dannecron/demo/providers/MockedShopProvider.kt deleted file mode 100644 index 54de1ab..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/providers/MockedShopProvider.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.github.dannecron.demo.providers - -import com.github.dannecron.demo.models.* -import java.time.OffsetDateTime -import java.util.* - -class MockedShopProvider: com.github.dannecron.demo.providers.ShopProvider { - override fun getRandomShop(): Shop? { - val productOne = makeProduct(id = 1, name = "one", price = 11.2) - val productTwo = makeProduct(id = 2, name = "two", price = 13.2) - val productThree = makeProduct(id = 3, name = "three", price = 15.2) - val productFour = makeProduct(id = 4, name = "four", price = 14.2) - - return Shop(name="shop", customers= listOf( - CustomerLocal( - name = "Foo-1", - city = makeCity(id = 1, name = "Foo"), - orders = listOf( - OrderLocal(products = listOf(productOne, productTwo), isDelivered = true), - OrderLocal(products = listOf(productThree), isDelivered = false), - ) - ), - CustomerLocal( - name = "Foo-2", - city = makeCity(id = 2, name = "Bar"), - orders = listOf( - OrderLocal(products = listOf(productOne), isDelivered = false), - OrderLocal(products = listOf(productTwo), isDelivered = true), - OrderLocal(products = listOf(productFour), isDelivered = true), - ) - ), - )) - } - - private fun makeProduct(id: Long, name: String, price: Double): Product = Product( - id = id, - guid = UUID.randomUUID(), - name = name, - description = null, - price = (price * 100).toLong(), - createdAt = OffsetDateTime.now(), - updatedAt = null, - deletedAt = null, - ) - - private fun makeCity(id: Long, name: String): City = City( - id = id, - guid = UUID.randomUUID(), - name = name, - createdAt = OffsetDateTime.now(), - updatedAt = null, - deletedAt = null, - ) -} diff --git a/src/main/kotlin/com/github/dannecron/demo/providers/OrderProductRepository.kt b/src/main/kotlin/com/github/dannecron/demo/providers/OrderProductRepository.kt index 9b64987..7a8994e 100644 --- a/src/main/kotlin/com/github/dannecron/demo/providers/OrderProductRepository.kt +++ b/src/main/kotlin/com/github/dannecron/demo/providers/OrderProductRepository.kt @@ -1,7 +1,9 @@ package com.github.dannecron.demo.providers -import com.github.dannecron.demo.models.OrderProduct +import com.github.dannecron.demo.models.order.OrderProduct import org.springframework.data.repository.CrudRepository import java.util.* -interface OrderProductRepository: CrudRepository +interface OrderProductRepository: CrudRepository { + fun findByOrderId(orderId: Long): List +} diff --git a/src/main/kotlin/com/github/dannecron/demo/providers/OrderRepository.kt b/src/main/kotlin/com/github/dannecron/demo/providers/OrderRepository.kt index 2006b18..b99d1b5 100644 --- a/src/main/kotlin/com/github/dannecron/demo/providers/OrderRepository.kt +++ b/src/main/kotlin/com/github/dannecron/demo/providers/OrderRepository.kt @@ -1,8 +1,10 @@ package com.github.dannecron.demo.providers -import com.github.dannecron.demo.models.Order +import com.github.dannecron.demo.models.order.Order import org.springframework.data.repository.CrudRepository import org.springframework.stereotype.Repository @Repository -interface OrderRepository: CrudRepository +interface OrderRepository: CrudRepository { + fun findByCustomerId(customerId: Long): List +} diff --git a/src/main/kotlin/com/github/dannecron/demo/providers/ShopProvider.kt b/src/main/kotlin/com/github/dannecron/demo/providers/ShopProvider.kt deleted file mode 100644 index ea7e179..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/providers/ShopProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.dannecron.demo.providers - -import com.github.dannecron.demo.models.Shop - -interface ShopProvider { - fun getRandomShop(): Shop? -} diff --git a/src/main/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImpl.kt b/src/main/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImpl.kt index 83190be..a54cab9 100644 --- a/src/main/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImpl.kt +++ b/src/main/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImpl.kt @@ -1,11 +1,13 @@ package com.github.dannecron.demo.services.database.order import com.github.dannecron.demo.models.Customer -import com.github.dannecron.demo.models.Order -import com.github.dannecron.demo.models.OrderProduct import com.github.dannecron.demo.models.Product +import com.github.dannecron.demo.models.order.Order +import com.github.dannecron.demo.models.order.OrderProduct +import com.github.dannecron.demo.models.order.OrderWithProducts import com.github.dannecron.demo.providers.OrderProductRepository import com.github.dannecron.demo.providers.OrderRepository +import com.github.dannecron.demo.providers.ProductRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -14,9 +16,18 @@ import java.util.* @Service class OrderServiceImpl( - @Autowired val orderRepository: OrderRepository, - @Autowired val orderProductRepository: OrderProductRepository, + @Autowired private val orderRepository: OrderRepository, + @Autowired private val orderProductRepository: OrderProductRepository, + @Autowired private val productRepository: ProductRepository, ) { + fun getByCustomerId(customerId: Long): List = orderRepository.findByCustomerId(customerId) + .let { orders -> orders.map { order -> OrderWithProducts( + order = order, + products = orderProductRepository.findByOrderId(orderId = order.id!!) + .map { orderProduct -> orderProduct.productId } + .let { productIds -> productRepository.findAllById(productIds).toList() } + ) } } + @Transactional fun createOrder(customer: Customer, products: Set): Order { val order = Order( @@ -27,16 +38,18 @@ class OrderServiceImpl( createdAt = OffsetDateTime.now(), updatedAt = null, ) - return orderRepository.save(order).also { - savedOrder -> products.toList().forEach { - product -> orderProductRepository.save(OrderProduct( - guid = UUID.randomUUID(), - orderId = savedOrder.id!!, - productId = product.id!!, - createdAt = OffsetDateTime.now(), - updatedAt = null - )) + + return orderRepository.save(order) + .also { + savedOrder -> products.toList() + .map { product -> OrderProduct( + guid = UUID.randomUUID(), + orderId = savedOrder.id!!, + productId = product.id!!, + createdAt = OffsetDateTime.now(), + updatedAt = null + ) } + .also { orderProductRepository.saveAll(it) } } - } } } diff --git a/src/test/kotlin/com/github/dannecron/demo/http/controllers/ShopControllerTest.kt b/src/test/kotlin/com/github/dannecron/demo/http/controllers/ShopControllerTest.kt deleted file mode 100644 index 051f24e..0000000 --- a/src/test/kotlin/com/github/dannecron/demo/http/controllers/ShopControllerTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.dannecron.demo.http.controllers - -import com.github.dannecron.demo.BaseUnitTest -import com.github.dannecron.demo.models.* -import com.github.dannecron.demo.providers.ShopProvider -import org.mockito.kotlin.doReturn -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(ShopController::class) -class ShopControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() { - @MockBean - private lateinit var shopProvider: ShopProvider - - @Test - fun commonInfo_shouldSeeSuccessResponseJson() { - val productOne = makeProduct(id = 1, name = "one", price = 11.2) - val productTwo = makeProduct(id = 2, name = "two", price = 13.2) - val productThree = makeProduct(id = 3, name = "three", price = 15.2) - val productFour = makeProduct(id = 4, name = "four", price = 14.2) - - val shopMock = Shop(name="shop", customers= listOf( - CustomerLocal( - name = "cus-one", - city = makeCity(id = 1, name = "city-one"), - orders = listOf( - OrderLocal(products = listOf(productOne), isDelivered = false), - OrderLocal(products = listOf(productTwo), isDelivered = false), - OrderLocal(products = listOf(productThree), isDelivered = true), - ) - ), - CustomerLocal( - name = "cus-two", - city = makeCity(id = 2, name = "city-two"), - orders = listOf( - OrderLocal(products = listOf(productOne), isDelivered = false), - OrderLocal(products = listOf(productTwo), isDelivered = true), - OrderLocal(products = listOf(productFour), isDelivered = true), - ) - ), - )) - - val expectedJson: String = """{ - |"customers": {"withMoreUndeliveredOrdersThanDelivered": ["cus-one"]}, - |"products": {"orderedByAllCustomers": ["one", "two"]} - |}""".trimMargin() - - whenever( - shopProvider.getRandomShop() - ) doReturn shopMock - - mockMvc.get("/shop/common-info") - .andExpect{ status { status { isOk() } } } - .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { content { json(expectedJson) } } - } - - @Test - fun commonInfo_shouldSeeNotFoundResponse() { - whenever( - shopProvider.getRandomShop() - ) doReturn null - - mockMvc.get("/shop/common-info") - .andExpect { status { isNotFound() } } - .andExpect { content { contentType(MediaType.APPLICATION_JSON) } } - .andExpect { content { json("""{"status":"not found"}""") } } - } - - private fun makeProduct(id: Long, name: String, price: Double): Product = Product( - id = id, - guid = UUID.randomUUID(), - name = name, - description = null, - price = (price * 100).toLong(), - createdAt = OffsetDateTime.now(), - updatedAt = null, - deletedAt = null, - ) - - private fun makeCity(id: Long, name: String): City = City( - id = id, - guid = UUID.randomUUID(), - name = name, - createdAt = OffsetDateTime.now(), - updatedAt = null, - deletedAt = null, - ) -} diff --git a/src/test/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImplTest.kt b/src/test/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImplTest.kt index 850d1bc..c035241 100644 --- a/src/test/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImplTest.kt +++ b/src/test/kotlin/com/github/dannecron/demo/services/database/order/OrderServiceImplTest.kt @@ -2,8 +2,8 @@ package com.github.dannecron.demo.services.database.order import com.github.dannecron.demo.BaseDbTest import com.github.dannecron.demo.models.Customer -import com.github.dannecron.demo.models.Order import com.github.dannecron.demo.models.Product +import com.github.dannecron.demo.models.order.Order import com.github.dannecron.demo.providers.CustomerRepository import com.github.dannecron.demo.providers.OrderRepository import com.github.dannecron.demo.providers.ProductRepository @@ -12,6 +12,7 @@ import org.springframework.test.context.ContextConfiguration import java.time.OffsetDateTime import java.util.* import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertNotNull @ContextConfiguration(classes = [OrderServiceImpl::class]) @@ -26,33 +27,46 @@ class OrderServiceImplTest: BaseDbTest() { private lateinit var orderServiceImpl: OrderServiceImpl @Test - fun create_success() { - var productOne: Product? = null - var productTwo: Product? = null - var customer: Customer? = null + fun createAndFind_success() { + var productOneId: Long? = null + var productTwoId: Long? = null + var customerId: Long? = null var order: Order? = null try { - productOne = makeProduct().let { productRepository.save(it) } - productTwo = makeProduct().let { productRepository.save(it) } - customer = makeCustomer().let { customerRepository.save(it) } + val productOne = makeProduct().let { productRepository.save(it) }.also { productOneId = it.id!! } + val productTwo = makeProduct(price = 20000L) + .let { productRepository.save(it) } + .also { productTwoId = it.id!! } + val customer = makeCustomer().let { customerRepository.save(it) }.also { customerId = it.id!! } order = orderServiceImpl.createOrder(customer, setOf(productOne, productTwo)) assertNotNull(order.id) + + val orderWithProducts = orderServiceImpl.getByCustomerId(customerId = customerId!!) + assertEquals(1, orderWithProducts.count()) + orderWithProducts.first().also { + assertEquals(order.id, it.order.id) + assertEquals(2, it.products.count()) + assertEquals(productTwo.id, it.getMostExpensiveOrderedProduct()?.id) + assertEquals(300.00, it.getTotalOrderPrice()) + } + + } finally { order?.id?.also { orderRepository.deleteById(it) } - customer?.id?.also { customerRepository.deleteById(it) } - productOne?.id?.also { productRepository.deleteById(it) } - productTwo?.id?.also { productRepository.deleteById(it) } + customerId?.also { customerRepository.deleteById(it) } + productOneId?.also { productRepository.deleteById(it) } + productTwoId?.also { productRepository.deleteById(it) } } } - private fun makeProduct(): Product = Product( + private fun makeProduct(price: Long = 10000L): Product = Product( id = null, guid = UUID.randomUUID(), name = "name" + UUID.randomUUID(), description = null, - price = 10000, + price = price, createdAt = OffsetDateTime.now(), updatedAt = null, deletedAt = null, @@ -63,7 +77,7 @@ class OrderServiceImplTest: BaseDbTest() { guid = UUID.randomUUID(), name = "client", cityId = null, - createdAt =OffsetDateTime.now(), + createdAt = OffsetDateTime.now(), updatedAt = null, ) }