La mise à jour d'un mélange crée, met à jour et supprime les ingrédients au lieu de tout recréer.

This commit is contained in:
FyloZ 2021-03-09 11:45:00 -05:00
parent 08c6a9db49
commit 50442d7ebc
5 changed files with 189 additions and 49 deletions

View File

@ -20,7 +20,7 @@ data class MixMaterial(
@JoinColumn(name = "material_id")
val material: Material,
val quantity: Float
var quantity: Float
) : Model {
constructor(mix: Mix, material: Material, quantity: Float) : this(null, mix, material, quantity)

View File

@ -15,8 +15,11 @@ interface MixMaterialService : ModelService<MixMaterial, MixMaterialRepository>
/** Creates [MixMaterial]s from the given [map]. The [map] must have the format <Material ID, Quantity>. */
fun createFromMap(mix: Mix, map: Map<Long, Float>): Collection<MixMaterial>
/** Creates a [MixMaterial] from the given [pair]. The [pair] must have the format <Material ID, Quantity>. */
fun createFromPair(mix: Mix, pair: Pair<Long, Float>): MixMaterial
/** Creates a [MixMaterial] with the material with the given [materialId] and the given [quantity]. */
fun create(mix: Mix, materialId: Long, quantity: Float): MixMaterial
/** Updates the [quantity] of the given [mixMaterial]. */
fun updateQuantity(mixMaterial: MixMaterial, quantity: Float): MixMaterial
}
@Service
@ -26,10 +29,13 @@ class MixMaterialServiceImpl(
) : AbstractModelService<MixMaterial, MixMaterialRepository>(mixMaterialRepository), MixMaterialService {
override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material)
override fun createFromMap(mix: Mix, map: Map<Long, Float>): Collection<MixMaterial> =
map.map { createFromPair(mix, it.toPair()) }
map.map { create(mix, it.key, it.value) }
override fun createFromPair(mix: Mix, pair: Pair<Long, Float>): MixMaterial {
val material = materialService.getById(pair.first)
return mixMaterial(mix = mix, material = material, quantity = pair.second)
}
override fun create(mix: Mix, materialId: Long, quantity: Float): MixMaterial =
mixMaterial(mix = mix, material = materialService.getById(materialId), quantity = quantity)
override fun updateQuantity(mixMaterial: MixMaterial, quantity: Float) =
update(mixMaterial.apply {
this.quantity = quantity
})
}

View File

@ -53,7 +53,33 @@ class MixServiceImpl(
return mix
}
@Transactional
override fun update(entity: MixUpdateDto): Mix {
fun updateMixMaterials(mix: Mix, mixMaterialsMap: Map<Long, Float>) {
val existingMixMaterialsMaterialIds = mix.mixMaterials.map { it.material.id }
val toDelete = mix.mixMaterials
mix.mixMaterials = mutableListOf(
// update existing mix materials
*mixMaterialsMap
.filter { it.key in existingMixMaterialsMaterialIds }
.map { (materialId, quantity) ->
val existingMixMaterial = mix.mixMaterials.first { it.material.id == materialId }
toDelete.remove(existingMixMaterial)
mixMaterialService.updateQuantity(existingMixMaterial, quantity)
}
.toTypedArray(),
// create new mix materials
*mixMaterialsMap
.filter { it.key !in existingMixMaterialsMaterialIds }
.map { (materialId, quantity) -> mixMaterialService.create(mix, materialId, quantity) }
.toTypedArray()
)
// delete unused mix materials
toDelete.forEach { mixMaterialService.delete(it) }
}
val mix = getById(entity.id)
if (entity.name != null || entity.materialTypeId != null) {
mix.mixType = if (mixTypeIsShared(mix.mixType)) {
@ -70,7 +96,7 @@ class MixServiceImpl(
}
}
if (entity.mixMaterials != null) {
mix.mixMaterials = mixMaterialService.createFromMap(mix, entity.mixMaterials).toMutableList()
updateMixMaterials(mix, entity.mixMaterials)
}
return update(mix)
}

View File

@ -1,16 +1,12 @@
package dev.fyloz.trial.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.whenever
import dev.fyloz.trial.colorrecipesexplorer.model.Material
import dev.fyloz.trial.colorrecipesexplorer.model.MixMaterial
import dev.fyloz.trial.colorrecipesexplorer.model.material
import dev.fyloz.trial.colorrecipesexplorer.model.mixMaterial
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.repository.MixMaterialRepository
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterialService, MixMaterialRepository>() {
@ -19,11 +15,12 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
override val service: MixMaterialService = spy(MixMaterialServiceImpl(repository, materialService))
private val material: Material = material(id = 0L)
override val entity: MixMaterial = mixMaterial(id = 0L, material = material)
override val entity: MixMaterial = mixMaterial(id = 0L, material = material, quantity = 1000f)
override val anotherEntity: MixMaterial = mixMaterial(id = 1L, material = material)
// existsByMaterial()
@Test
fun `existsByMaterial() returns true when a mix material with the given material exists`() {
whenever(repository.existsByMaterial(material)).doReturn(true)
@ -32,6 +29,7 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
assertTrue(found)
}
@Test
fun `existsByMaterial() returns false when no mix material with the given material exists`() {
whenever(repository.existsByMaterial(material)).doReturn(false)
@ -39,4 +37,54 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
assertFalse(found)
}
// createFromMap()
@Test
fun `createFromMap() calls create() for each map entry`() {
val mix = mix()
val map = mapOf(
1L to 1000f,
2L to 2000f,
5L to 5000f
)
val mixMaterials = map.map { mixMaterial(material = material(id = it.key), quantity = it.value) }
doAnswer { mixMaterials.first { mixMaterial -> mixMaterial.material.id == it.arguments[1] } }
.whenever(service).create(eq(mix), any(), any())
val found = service.createFromMap(mix, map)
assertEquals(mixMaterials, found)
}
// create()
@Test
fun `create() creates a mix material with the given mix, material and quantity`() {
val mix = mix()
val material = material(id = 0L)
val quantity = 1000f
val mixMaterial = mixMaterial(mix = mix, material = material, quantity = quantity)
whenever(materialService.getById(material.id!!)).doReturn(material)
val found = service.create(mix, material.id!!, quantity)
assertEquals(mixMaterial, found)
}
// updateQuantity()
@Test
fun `updateQuantity() updates the given mix material with the given quantity`() {
val quantity = 5000f
assertNotEquals(quantity, entity.quantity, message = "Quantities must not be equals for this test to works")
doAnswer { it.arguments[0] }.whenever(service).update(any())
val found = service.updateQuantity(entity, quantity)
assertEquals(found.quantity, quantity)
}
}

View File

@ -3,9 +3,9 @@ package dev.fyloz.trial.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.repository.MixRepository
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixService, MixRepository>() {
override val repository: MixRepository = mock()
@ -75,6 +75,31 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
// update()
private fun mixUpdateDtoTest(
scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
sharedMixType: Boolean = false,
op: MixUpdateDtoTestScope.() -> Unit
) {
with(scope) {
doReturn(true).whenever(service).existsById(mix.id!!)
doReturn(mix).whenever(service).getById(mix.id!!)
doReturn(sharedMixType).whenever(service).mixTypeIsShared(mix.mixType)
doAnswer { it.arguments[0] }.whenever(service).update(any<Mix>())
if (mixUpdateDto.materialTypeId != null) {
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialType)
}
op()
}
}
private fun mixUpdateDtoMixTypeTest(sharedMixType: Boolean = false, op: MixUpdateDtoTestScope.() -> Unit) {
with(MixUpdateDtoTestScope(mixUpdateDto = mixUpdateDto(id = 0L, name = "name", materialTypeId = 0L))) {
mixUpdateDtoTest(this, sharedMixType, op)
}
}
@Test
override fun `update(dto) calls and returns update() with the created entity`() {
val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = null, materialTypeId = null))
@ -91,47 +116,67 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
@Test
fun `update(dto) calls MixTypeService createForNameAndMaterialType() when mix type is shared`() {
val newMixType = mixType(name = "another mix type")
val mix = mix(id = 0L, mixType = mixType(name = "mix type"))
val materialType = materialType(id = 0L)
val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = "mix", materialTypeId = 0L, mixMaterials = null))
mixUpdateDtoMixTypeTest(sharedMixType = true) {
whenever(mixTypeService.createForNameAndMaterialType(mixUpdateDto.name!!, materialType))
.doReturn(newMixType)
doReturn(true).whenever(service).existsById(mix.id!!)
doReturn(mix).whenever(service).getById(mix.id!!)
doReturn(true).whenever(service).mixTypeIsShared(mix.mixType)
doAnswer { it.arguments[0] }.whenever(service).update(any<Mix>())
val found = service.update(mixUpdateDto)
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialType)
whenever(mixTypeService.createForNameAndMaterialType(mixUpdateDto.name!!, materialType)).doReturn(newMixType)
verify(mixTypeService).createForNameAndMaterialType(mixUpdateDto.name!!, materialType)
val found = service.update(mixUpdateDto)
verify(mixTypeService).createForNameAndMaterialType(mixUpdateDto.name!!, materialType)
assertEquals(newMixType, found.mixType)
assertEquals(newMixType, found.mixType)
}
}
@Test
fun `update(dto) calls MixTypeService updateForNameAndMaterialType() when mix type is not shared`() {
val mixType = mixType(name = "mix type")
val newMixType = mixType(name = "another mix type")
val mix = mix(id = 0L, mixType = mixType)
val materialType = materialType(id = 0L)
val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = "mix", materialTypeId = 0L, mixMaterials = null))
mixUpdateDtoMixTypeTest {
whenever(mixTypeService.updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType))
.doReturn(newMixType)
doReturn(true).whenever(service).existsById(mix.id!!)
doReturn(mix).whenever(service).getById(mix.id!!)
doReturn(false).whenever(service).mixTypeIsShared(mix.mixType)
doAnswer { it.arguments[0] }.whenever(service).update(any<Mix>())
val found = service.update(mixUpdateDto)
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialType)
whenever(mixTypeService.updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType)).doReturn(newMixType)
verify(mixTypeService).updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType)
val found = service.update(mixUpdateDto)
assertEquals(newMixType, found.mixType)
}
}
verify(mixTypeService).updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType)
@Test
fun `update(dto) update, create and delete mix materials according to the given mix materials map`() {
mixUpdateDtoTest {
// Pairs exists, impairs don't
val materials = listOf(
material(id = 1L),
material(id = 2L),
material(id = 3L),
material(id = 4L),
material(id = 5L),
material(id = 6L),
)
val toDelete = mixMaterial(material = material(id = 7L), quantity = 7000f)
val mixMaterialsMap = mapOf(*materials.map { it.id!! to it.id!! * 1000f }.toTypedArray())
val allMixMaterials: Collection<MixMaterial> = materials
.map { mixMaterial(mix = mix, material = it, quantity = mixMaterialsMap[it.id]!!) }
val existingMixMaterials = allMixMaterials.filter { it.material.id!! % 2 == 0L }.toMutableList()
existingMixMaterials += toDelete
assertEquals(newMixType, found.mixType)
mix.mixMaterials = existingMixMaterials
(mixUpdateDto.mixMaterials as MutableMap<Long, Float>).putAll(mixMaterialsMap)
doAnswer { allMixMaterials.first { mixMaterial -> mixMaterial.material.id == (it.arguments[0] as MixMaterial).material.id } }
.whenever(mixMaterialService).updateQuantity(any(), any())
doAnswer { allMixMaterials.first { mixMaterial -> mixMaterial.material.id == it.arguments[1] } }
.whenever(mixMaterialService).create(eq(mix), any(), any())
val found: Mix = service.update(mixUpdateDto)
assertTrue { found.mixMaterials.containsAll(allMixMaterials) }
verify(mixMaterialService, times(3)).updateQuantity(argThat { material.id!! % 2 == 0L }, any())
verify(mixMaterialService, times(3)).create(eq(mix), any(), any())
verify(mixMaterialService).delete(toDelete)
}
}
// updateLocation()
@ -147,3 +192,18 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
verify(service).update(expected)
}
}
data class MixUpdateDtoTestScope(
val mixType: MixType = mixType(name = "mix type"),
val newMixType: MixType = mixType(name = "another mix type"),
val materialType: MaterialType = materialType(id = 0L),
val mix: Mix = mix(id = 0L, mixType = mixType),
val mixUpdateDto: MixUpdateDto = spy(
mixUpdateDto(
id = 0L,
name = null,
materialTypeId = null,
mixMaterials = mutableMapOf()
)
)
)