From 931494c1f9764452ad1f19caef49b033ecce31e3 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Sun, 18 Apr 2021 19:54:28 -0400 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20la=20v=C3=A9rification=20des=20ing?= =?UTF-8?q?r=C3=A9dients=20des=20m=C3=A9langes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../service/MixMaterialService.kt | 97 ++++++++++++++++++- .../service/RecipeStepService.kt | 25 +++-- .../service/utils/Collections.kt | 14 +++ .../service/RecipeServiceTest.kt | 3 +- .../service/RecipeStepServiceTest.kt | 8 +- 6 files changed, 129 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 9faf0f9..a57ac6a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ logs/ data/ dokka/ dist/ +out/ /src/main/resources/angular/static/* diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt index f65a7a6..636196d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt @@ -1,8 +1,12 @@ package dev.fyloz.colorrecipesexplorer.service +import dev.fyloz.colorrecipesexplorer.exception.RestException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository +import dev.fyloz.colorrecipesexplorer.service.utils.findDuplicated +import dev.fyloz.colorrecipesexplorer.service.utils.hasGaps import org.springframework.context.annotation.Lazy +import org.springframework.http.HttpStatus import org.springframework.stereotype.Service interface MixMaterialService : ModelService { @@ -18,7 +22,12 @@ interface MixMaterialService : ModelService /** Updates the [quantity] of the given [mixMaterial]. */ fun updateQuantity(mixMaterial: MixMaterial, quantity: Float): MixMaterial - fun validateMixMaterials(mixMaterials: Set) + /** + * Validates if the given [mixMaterials]. To be valid, the position of each mix material must be greater or equals to 1 and unique in the set. + * There must also be no gap between the positions. Also, the quantity of the first mix material in the set must not be expressed in percentages. + * If any of those criteria are not met, an [InvalidGroupStepsPositionsException] will be thrown. + */ + fun validateMixMaterials(mixMaterials: Set) } @Service @@ -44,4 +53,90 @@ class MixMaterialServiceImpl( update(mixMaterial.apply { this.quantity = quantity }) + + override fun validateMixMaterials(mixMaterials: Set) { + if (mixMaterials.isEmpty()) return + + val sortedMixMaterials = mixMaterials.sortedBy { it.position } + val firstMixMaterial = sortedMixMaterials[0] + val errors = mutableSetOf() + + // Check if the first mix material position is 1 + fun isFirstMixMaterialPositionInvalid() = + sortedMixMaterials[0].position != 1 + + // Check if the first mix material is expressed in percents + fun isFirstMixMaterialPercentages() = + sortedMixMaterials[0].material.materialType!!.usePercentages + + // Check if any positions is duplicated + fun getDuplicatedPositionsErrors() = + sortedMixMaterials + .findDuplicated { it.position } + .map { duplicatedMixMaterialsPositions(it) } + + // Find all errors and throw if there is any + if (isFirstMixMaterialPositionInvalid()) errors += invalidFirstMixMaterialPosition(sortedMixMaterials[0]) + errors += getDuplicatedPositionsErrors() + if (errors.isEmpty() && mixMaterials.hasGaps { it.position }) errors += gapBetweenStepsPositions() + if (errors.isNotEmpty()) { + throw InvalidMixMaterialsPositionsException(errors) + } + + if (isFirstMixMaterialPercentages()) { + throw InvalidFirstMixMaterial(firstMixMaterial) + } + } } + +class InvalidMixMaterialsPositionsError( + val type: String, + val details: String +) + +class InvalidMixMaterialsPositionsException( + val errors: Set +) : RestException( + "invalid-mixmaterial-position", + "Invalid mix materials positions", + HttpStatus.BAD_REQUEST, + "The position of mix materials are invalid", + mapOf( + "invalidMixMaterials" to errors + ) +) + +class InvalidFirstMixMaterial( + val mixMaterial: MixMaterial +) : RestException( + "invalid-mixmaterial-first", + "Invalid first mix material", + HttpStatus.BAD_REQUEST, + "The first mix material is invalid because its material must not be expressed in percents", + mapOf( + "mixMaterial" to mixMaterial + ) +) + +const val INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE = "first" +const val DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE = "duplicated" +const val GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE = "gap" + +private fun invalidFirstMixMaterialPosition(mixMaterial: MixMaterial) = + InvalidMixMaterialsPositionsError( + INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE, + "The position ${mixMaterial.position} is under the minimum of 1" + ) + +private fun duplicatedMixMaterialsPositions(position: Int) = + InvalidMixMaterialsPositionsError( + DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE, + "The position $position is duplicated" + ) + +private fun gapBetweenStepsPositions() = + InvalidMixMaterialsPositionsError( + GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE, + "There is a gap between mix materials positions" + ) + diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index fadae3c..8fc0121 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -3,6 +3,8 @@ package dev.fyloz.colorrecipesexplorer.service import dev.fyloz.colorrecipesexplorer.exception.RestException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository +import dev.fyloz.colorrecipesexplorer.service.utils.findDuplicated +import dev.fyloz.colorrecipesexplorer.service.utils.hasGaps import org.springframework.http.HttpStatus import org.springframework.stereotype.Service @@ -36,6 +38,8 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : } override fun validateSteps(steps: Set) { + if (steps.isEmpty()) return + val sortedSteps = steps.sortedBy { it.position } val errors = mutableSetOf() @@ -46,21 +50,13 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : // Check if any position is duplicated fun getDuplicatedPositionsErrors() = sortedSteps - .groupBy { it.position } - .filter { it.value.count() > 1 } - .map { duplicatedStepsPositions(it.key) } - - // Check if there is any gap between steps positions - // Knowing that steps are sorted by position, if the position of the step is not index + 1, there is a gap between them - fun hasGapBetweenPositions() = - sortedSteps - .filterIndexed { index, step -> step.position != index + 1 } - .isNotEmpty() + .findDuplicated { it.position } + .map { duplicatedStepsPositions(it) } // Find all errors and throw if there is any if (isFirstStepPositionInvalid()) errors += invalidFirstStepPosition(sortedSteps[0]) errors += getDuplicatedPositionsErrors() - if (errors.isEmpty() && hasGapBetweenPositions()) errors += gapBetweenStepsPositions() + if (errors.isEmpty() && steps.hasGaps { it.position }) errors += gapBetweenStepsPositions() if (errors.isNotEmpty()) { throw InvalidStepsPositionsException(errors) } @@ -89,7 +85,7 @@ class InvalidGroupStepsPositionsException( val exception: InvalidStepsPositionsException ) : RestException( "invalid-groupinformation-recipestep-position", - "Invalid steps position", + "Invalid steps positions", HttpStatus.BAD_REQUEST, "The position of steps for the group ${group.name} are invalid", mapOf( @@ -97,7 +93,10 @@ class InvalidGroupStepsPositionsException( "groupId" to group.id!!, "invalidSteps" to exception.errors ) -) +) { + val errors: Set + get() = exception.errors +} const val INVALID_FIRST_STEP_POSITION_ERROR_CODE = "first" const val DUPLICATED_STEPS_POSITIONS_ERROR_CODE = "duplicated" diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/utils/Collections.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/utils/Collections.kt index ada1c04..f461138 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/utils/Collections.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/utils/Collections.kt @@ -17,6 +17,20 @@ inline fun Iterable.mapMayThrow( } } +/** Find duplicated in the given [Iterable] from keys obtained from the given [keySelector]. */ +inline fun Iterable.findDuplicated(keySelector: (T) -> K) = + this.groupBy(keySelector) + .filter { it.value.count() > 1 } + .map { it.key } + +/** Check if the given [Iterable] has gaps between each items, using keys obtained from the given [keySelector]. */ +inline fun Iterable.hasGaps(keySelector: (T) -> Int) = + this.map(keySelector) + .toIntArray() + .sorted() + .filterIndexed { index, it -> it != index + 1 } + .isNotEmpty() + /** Clears and fills the given [MutableCollection] with the given [elements]. */ fun MutableCollection.setAll(elements: Collection) { this.clear() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index 72f60d2..e42820e 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -20,7 +20,8 @@ class RecipeServiceTest : private val companyService: CompanyService = mock() private val mixService: MixService = mock() private val groupService: EmployeeGroupService = mock() - override val service: RecipeService = spy(RecipeServiceImpl(repository, companyService, mixService, groupService)) + private val recipeStepService: RecipeStepService = mock() + override val service: RecipeService = spy(RecipeServiceImpl(repository, companyService, mixService, recipeStepService, groupService)) private val company: Company = company(id = 0L) override val entity: Recipe = recipe(id = 0L, name = "recipe", company = company) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index 5f6b43b..ef8d213 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -16,10 +16,10 @@ class RecipeStepServiceTest : override val entity: RecipeStep = recipeStep(id = 0L, message = "message") override val anotherEntity: RecipeStep = recipeStep(id = 1L, message = "another message") - // validateStepsCollection() + // validateGroupInformationSteps() @Test - fun `validateStepsCollection() throws an InvalidStepsPositionsException when the position of the first step of the given groupInformation is not 1`() { + fun `validateGroupInformationSteps() throws an InvalidGroupStepsPositionsException when the position of the first step of the given groupInformation is not 1`() { withSteps( mutableSetOf( recipeStep(id = 0L, position = 0), @@ -38,7 +38,7 @@ class RecipeStepServiceTest : } @Test - fun `validateStepsCollection() throws an InvalidStepsPositionsException when steps positions are duplicated in the given groupInformation`() { + fun `validateGroupInformationSteps() throws an InvalidGroupStepsPositionsException when steps positions are duplicated in the given groupInformation`() { withSteps( mutableSetOf( recipeStep(id = 0L, position = 1), @@ -57,7 +57,7 @@ class RecipeStepServiceTest : } @Test - fun `validateStepsCollection() throws an InvalidStepsPositionsException when there is a gap between steps positions in the given groupInformation`() { + fun `validateGroupInformationSteps() throws an InvalidGroupStepsPositionsException when there is a gap between steps positions in the given groupInformation`() { withSteps( mutableSetOf( recipeStep(id = 0L, position = 1),