mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
remove shop provider, shop controller and non-database models
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.github.dannecron.demo.models
|
||||
|
||||
data class OrderLocal(val products: List<Product>, val isDelivered: Boolean)
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.github.dannecron.demo.providers
|
||||
|
||||
import com.github.dannecron.demo.models.Shop
|
||||
|
||||
interface ShopProvider {
|
||||
fun getRandomShop(): Shop?
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user