diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt index cbad684..95a7660 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt @@ -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) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialService.kt index 925000d..d280eab 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialService.kt @@ -15,8 +15,11 @@ interface MixMaterialService : ModelService /** Creates [MixMaterial]s from the given [map]. The [map] must have the format . */ fun createFromMap(mix: Mix, map: Map): Collection - /** Creates a [MixMaterial] from the given [pair]. The [pair] must have the format . */ - fun createFromPair(mix: Mix, pair: Pair): 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(mixMaterialRepository), MixMaterialService { override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material) override fun createFromMap(mix: Mix, map: Map): Collection = - map.map { createFromPair(mix, it.toPair()) } + map.map { create(mix, it.key, it.value) } - override fun createFromPair(mix: Mix, pair: Pair): 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 + }) } diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt index 98fbb08..386e65b 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt @@ -53,7 +53,33 @@ class MixServiceImpl( return mix } + @Transactional override fun update(entity: MixUpdateDto): Mix { + fun updateMixMaterials(mix: Mix, mixMaterialsMap: Map) { + 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) } diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialServiceTest.kt index 698a0b5..fb42bb2 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixMaterialServiceTest.kt @@ -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() { @@ -19,11 +15,12 @@ class MixMaterialServiceTest : AbstractModelServiceTest 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) + } } diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt index 30e38e0..728f344 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt @@ -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() { override val repository: MixRepository = mock() @@ -75,6 +75,31 @@ class MixServiceTest : AbstractExternalModelServiceTest 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()) + + 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()) + 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()) + 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 = 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).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