remove shop provider, shop controller and non-database models

This commit is contained in:
Denis Savosin
2024-10-16 15:54:52 +07:00
parent 08d3445ac4
commit 52cadf74d7
15 changed files with 80 additions and 295 deletions

View File

@@ -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,

View File

@@ -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()
}
}

View File

@@ -1,16 +0,0 @@
package com.github.dannecron.demo.models
data class CustomerLocal(val name: String, val city: City, val orders: List<OrderLocal>) {
/**
* 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<Product> = orders.flatMap { order -> order.products }.toSet()
fun getTotalOrderPrice(): Double = orders.flatMap { ord -> ord.products }.sumOf { pr -> pr.getPriceDouble()}
}

View File

@@ -1,3 +0,0 @@
package com.github.dannecron.demo.models
data class OrderLocal(val products: List<Product>, val isDelivered: Boolean)

View File

@@ -1,48 +0,0 @@
package com.github.dannecron.demo.models
data class Shop(val name: String, val customers: List<CustomerLocal>) {
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<City> = customers.map { cus -> cus.city }.toSet()
fun findAnyCustomerFrom(city: City): CustomerLocal? = customers.firstOrNull { cus -> cus.city == city }
fun getAllOrderedProducts(): Set<Product> = customers.flatMap { cus -> cus.getOrderedProducts() }.toSet()
fun getCustomersFrom(city: City): List<CustomerLocal> = customers.filter { cus -> cus.city == city }
fun getCustomersSortedByNumberOfOrders(): List<CustomerLocal> = 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<CustomerLocal> = 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<Product> {
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<City, List<CustomerLocal>> = customers.groupBy { cus -> cus.city }
fun hasCustomerFrom(city: City): Boolean = customers.any { cus -> cus.city == city }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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<Product>,
) {
fun getMostExpensiveOrderedProduct(): Product? = products.maxByOrNull { pr -> pr.price }
fun getTotalOrderPrice(): Double = products.sumOf { pr -> pr.getPriceDouble() }
}

View File

@@ -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,
)
}

View File

@@ -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<OrderProduct, UUID>
interface OrderProductRepository: CrudRepository<OrderProduct, UUID> {
fun findByOrderId(orderId: Long): List<OrderProduct>
}

View File

@@ -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<Order, Long>
interface OrderRepository: CrudRepository<Order, Long> {
fun findByCustomerId(customerId: Long): List<Order>
}

View File

@@ -1,7 +0,0 @@
package com.github.dannecron.demo.providers
import com.github.dannecron.demo.models.Shop
interface ShopProvider {
fun getRandomShop(): Shop?
}

View File

@@ -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<OrderWithProducts> = 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<Product>): 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) }
}
}
}
}

View File

@@ -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,
)
}

View File

@@ -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,
)
}