From c313888f30d89966fca6da8baaf23d539f6a08ca Mon Sep 17 00:00:00 2001 From: FyloZ Date: Fri, 9 Apr 2021 13:17:48 -0400 Subject: [PATCH 1/5] =?UTF-8?q?Commencement=20de=20la=20v=C3=A9rification?= =?UTF-8?q?=20de=20la=20position=20des=20=C3=A9tapes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RecipeService.kt | 11 +++++----- .../service/RecipeStepService.kt | 22 +++++++++++++++++-- .../service/utils/Collections.kt | 6 +++++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index 1173456..3e357ef 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -5,6 +5,7 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.model.validation.or import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.files.FileService +import dev.fyloz.colorrecipesexplorer.service.utils.setAll import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile @@ -73,22 +74,22 @@ class RecipeServiceImpl( remark = remark or persistedRecipe.remark, company = persistedRecipe.company, mixes = persistedRecipe.mixes, - groupsInformation = updateGroupsInformationSteps(persistedRecipe, entity.steps) + groupsInformation = updateGroupsInformation(persistedRecipe, entity) ) }) } - private fun updateGroupsInformationSteps(recipe: Recipe, steps: Set?): Set { - if (steps == null) return recipe.groupsInformation + private fun updateGroupsInformation(recipe: Recipe, updateDto: RecipeUpdateDto): Set { + val steps = updateDto.steps ?: return recipe.groupsInformation val updatedGroupsInformation = mutableSetOf() steps.forEach { with(recipe.groupInformationForGroup(it.groupId)) { updatedGroupsInformation.add( + // Set steps for the existing RecipeGroupInformation or create a new one this?.apply { if (this.steps != null) { - this.steps!!.clear() - this.steps!!.addAll(it.steps) + this.steps!!.setAll(it.steps) } else { this.steps = it.steps.toMutableSet() } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index 1128929..b356cc0 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -4,9 +4,27 @@ import dev.fyloz.colorrecipesexplorer.model.RecipeStep import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import org.springframework.stereotype.Service -interface RecipeStepService : ModelService +interface RecipeStepService : ModelService { + + /** + * Validates if the given [steps] obey the following criteria: + * + * * The position of the steps is greater or equals to 1 + * * Each position is unique in the collection + * * There is no gap between positions + */ + fun validateStepsCollection(steps: Collection): Boolean +} @Service class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : AbstractModelService(recipeStepRepository), - RecipeStepService + RecipeStepService { +// override fun validateStepsCollection(steps: Collection): Boolean { +// val sortedSteps = steps.sortedBy { it.position } +// +// fun validateStepPosition(step: RecipeStep) = +// step.position >= 1 +// +// } +} 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 b28e138..abdf788 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/utils/Collections.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/utils/Collections.kt @@ -16,3 +16,9 @@ inline fun Iterable.mapMayThrow( } } } + +/** Clears and fills the given [MutableCollection] with the given [elements]. */ +fun MutableCollection.setAll(elements: Collection) { + this.clear() + this.addAll(elements) +} From 26314af635a8104fa2b5b832b7365fe129d52c3a Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sat, 17 Apr 2021 19:55:00 -0400 Subject: [PATCH 2/5] =?UTF-8?q?Ajout=20de=20la=20v=C3=A9rification=20de=20?= =?UTF-8?q?l'ordre=20des=20=C3=A9tapes=20des=20recettes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 14 ++- .../service/RecipeService.kt | 50 ++++----- .../service/RecipeStepService.kt | 102 +++++++++++++++--- .../service/AbstractServiceTest.kt | 4 + .../service/RecipeStepServiceTest.kt | 74 ++++++++++++- 5 files changed, 200 insertions(+), 44 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4544a44..cfa2953 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + group = "dev.fyloz.colorrecipesexplorer" plugins { @@ -79,15 +81,17 @@ tasks.test { } } -tasks.withType { +tasks.withType() { options.compilerArgs.addAll(arrayOf("--release", "11")) } - -tasks.withType { +tasks.withType().all { kotlinOptions { - jvmTarget = "11" + jvmTarget = JavaVersion.VERSION_11.toString() useIR = true - freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.contracts.ExperimentalContracts" + freeCompilerArgs = listOf( + "-Xopt-in=kotlin.contracts.ExperimentalContracts", + "-Xinline-classes" + ) } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index 9fd386e..7df7296 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -1,6 +1,5 @@ package dev.fyloz.colorrecipesexplorer.service -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.model.validation.or import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository @@ -35,6 +34,7 @@ class RecipeServiceImpl( recipeRepository: RecipeRepository, val companyService: CompanyService, val mixService: MixService, + val recipeStepService: RecipeStepService, @Lazy val groupService: EmployeeGroupService ) : AbstractExternalModelService(recipeRepository), @@ -67,17 +67,17 @@ class RecipeServiceImpl( return update(with(entity) { recipe( - id = id, - name = name or persistedRecipe.name, - description = description or persistedRecipe.description, - color = color or persistedRecipe.color, - gloss = gloss ?: persistedRecipe.gloss, - sample = sample ?: persistedRecipe.sample, - approbationDate = approbationDate ?: persistedRecipe.approbationDate, - remark = remark or persistedRecipe.remark, - company = persistedRecipe.company, - mixes = persistedRecipe.mixes, - groupsInformation = updateGroupsInformation(persistedRecipe, entity) + id = id, + name = name or persistedRecipe.name, + description = description or persistedRecipe.description, + color = color or persistedRecipe.color, + gloss = gloss ?: persistedRecipe.gloss, + sample = sample ?: persistedRecipe.sample, + approbationDate = approbationDate ?: persistedRecipe.approbationDate, + remark = remark or persistedRecipe.remark, + company = persistedRecipe.company, + mixes = persistedRecipe.mixes, + groupsInformation = updateGroupsInformation(persistedRecipe, entity) ) }) } @@ -88,21 +88,23 @@ class RecipeServiceImpl( val updatedGroupsInformation = mutableSetOf() steps.forEach { with(recipe.groupInformationForGroup(it.groupId)) { - updatedGroupsInformation.add( - // Set steps for the existing RecipeGroupInformation or create a new one - this?.apply { - if (this.steps != null) { - this.steps!!.setAll(it.steps) - } else { - this.steps = it.steps.toMutableSet() - } - } ?: recipeGroupInformation( - group = groupService.getById(it.groupId), - steps = it.steps.toMutableSet() - ) + // Set steps for the existing RecipeGroupInformation or create a new one + val updatedGroupInformation = this?.apply { + if (this.steps != null) { + this.steps!!.setAll(it.steps) + } else { + this.steps = it.steps.toMutableSet() + } + } ?: recipeGroupInformation( + group = groupService.getById(it.groupId), + steps = it.steps.toMutableSet() ) + + updatedGroupsInformation.add(updatedGroupInformation) + recipeStepService.validateGroupInformationSteps(updatedGroupInformation) } } + return updatedGroupsInformation } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index b58539c..4cae4c9 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -1,21 +1,22 @@ package dev.fyloz.colorrecipesexplorer.service -import dev.fyloz.colorrecipesexplorer.model.RecipeStep -import dev.fyloz.colorrecipesexplorer.model.recipeStepIdAlreadyExistsException -import dev.fyloz.colorrecipesexplorer.model.recipeStepIdNotFoundException +import dev.fyloz.colorrecipesexplorer.exception.RestException +import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository +import org.springframework.http.HttpStatus import org.springframework.stereotype.Service interface RecipeStepService : ModelService { - /** - * Validates if the given [steps] obey the following criteria: + * Validates if the steps of the given [groupInformation] obey the following criteria: * * * The position of the steps is greater or equals to 1 * * Each position is unique in the collection * * There is no gap between positions + * + * If any of those criteria are not met, an [InvalidStepsPositionsException] will be thrown. */ - fun validateStepsCollection(steps: Collection): Boolean + fun validateGroupInformationSteps(groupInformation: RecipeGroupInformation) } @Service @@ -25,11 +26,86 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : override fun idNotFoundException(id: Long) = recipeStepIdNotFoundException(id) override fun idAlreadyExistsException(id: Long) = recipeStepIdAlreadyExistsException(id) - // override fun validateStepsCollection(steps: Collection): Boolean { -// val sortedSteps = steps.sortedBy { it.position } -// -// fun validateStepPosition(step: RecipeStep) = -// step.position >= 1 -// -// } + override fun validateGroupInformationSteps(groupInformation: RecipeGroupInformation) { + val steps = groupInformation.steps + val group = groupInformation.group + + if (steps == null) return + + val sortedSteps = steps.sortedBy { it.position } + val errors = mutableSetOf() + + // Check if the first step position is 1 + fun isFirstStepPositionInvalid() = + sortedSteps[0].position != 1 + + // Check if any position is duplicated + fun getDuplicatedPositionsErrors() = + sortedSteps + .groupBy { it.position } + .filter { it.value.count() > 1 } + .map { duplicatedStepsPositions(it.key, group) } + + // 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() + + // Find all errors and throw if there is any + if (isFirstStepPositionInvalid()) errors += invalidFirstStepPosition(sortedSteps[0]) + errors += getDuplicatedPositionsErrors() + if (errors.isEmpty() && hasGapBetweenPositions()) errors += gapBetweenStepsPositions(group) + if (errors.isNotEmpty()) { + throw InvalidStepsPositionsException(group, errors) + } + } } + +data class InvalidStepsPositionsError( + val type: String, + val details: String +) + +class InvalidStepsPositionsException( + val group: EmployeeGroup, + val errors: Set +) : + RestException( + "invalid-recipestep-position", + "Invalid steps position", + HttpStatus.BAD_REQUEST, + "The position of steps for the group ${group.name} are invalid", + mapOf( + "group" to group.name, + "groupId" to group.id!!, + "invalidSteps" to errors + ) + ) + +const val INVALID_FIRST_STEP_POSITION_ERROR_CODE = "first" +const val DUPLICATED_STEPS_POSITIONS_ERROR_CODE = "duplicated" +const val GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE = "gap" + +private fun invalidFirstStepPosition( + step: RecipeStep +) = InvalidStepsPositionsError( + INVALID_FIRST_STEP_POSITION_ERROR_CODE, + "The position ${step.position} is under the minimum of 1" +) + +private fun duplicatedStepsPositions( + position: Int, + group: EmployeeGroup +) = InvalidStepsPositionsError( + DUPLICATED_STEPS_POSITIONS_ERROR_CODE, + "The position $position is duplicated in the group ${group.name}" +) + +private fun gapBetweenStepsPositions( + group: EmployeeGroup +) = InvalidStepsPositionsError( + GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE, + "The positions for the steps of the group ${group.name} have gaps between them" +) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt index 0710913..0574fee 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/AbstractServiceTest.kt @@ -306,6 +306,10 @@ fun RestException.assertErrorCode(type: String, identifierName: String) { } } +fun RestException.assertErrorCode(errorCode: String) { + assertEquals(errorCode, this.errorCode) +} + fun > withBaseSaveDtoTest( entity: E, entitySaveDto: N, diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index 714b4a7..acafb28 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -2,9 +2,11 @@ package dev.fyloz.colorrecipesexplorer.service import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.spy -import dev.fyloz.colorrecipesexplorer.model.RecipeStep -import dev.fyloz.colorrecipesexplorer.model.recipeStep +import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertTrue class RecipeStepServiceTest : AbstractModelServiceTest() { @@ -13,4 +15,72 @@ class RecipeStepServiceTest : override val entity: RecipeStep = recipeStep(id = 0L, message = "message") override val anotherEntity: RecipeStep = recipeStep(id = 1L, message = "another message") + + // validateStepsCollection() + + @Test + fun `validateStepsCollection() throws an InvalidStepsPositionsException when the position of the first step of the given groupInformation is not 1`() { + withSteps( + mutableSetOf( + recipeStep(id = 0L, position = 0), + recipeStep(id = 1L, position = 1), + recipeStep(id = 2L, position = 2), + recipeStep(id = 3L, position = 3) + ) + ) { + val exception = assertThrows { + service.validateGroupInformationSteps(this) + } + + assertTrue { exception.errors.count() == 1 } + assertTrue { exception.errors.first().type == INVALID_FIRST_STEP_POSITION_ERROR_CODE } + } + } + + @Test + fun `validateStepsCollection() throws an InvalidStepsPositionsException when steps positions are duplicated in the given groupInformation`() { + withSteps( + mutableSetOf( + recipeStep(id = 0L, position = 1), + recipeStep(id = 1L, position = 2), + recipeStep(id = 2L, position = 2), + recipeStep(id = 3L, position = 3) + ) + ) { + val exception = assertThrows { + service.validateGroupInformationSteps(this) + } + + assertTrue { exception.errors.count() == 1 } + assertTrue { exception.errors.first().type == DUPLICATED_STEPS_POSITIONS_ERROR_CODE } + } + } + + @Test + fun `validateStepsCollection() throws an InvalidStepsPositionsException when there is a gap between steps positions in the given groupInformation`() { + withSteps( + mutableSetOf( + recipeStep(id = 0L, position = 1), + recipeStep(id = 1L, position = 2), + recipeStep(id = 2L, position = 4), + recipeStep(id = 3L, position = 5) + ) + ) { + val exception = assertThrows { + service.validateGroupInformationSteps(this) + } + + assertTrue { exception.errors.count() == 1 } + assertTrue { exception.errors.first().type == GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE } + } + } + + private fun withSteps(steps: MutableSet, test: RecipeGroupInformation.() -> Unit) { + recipeGroupInformation( + group = employeeGroup(id = 0L), + steps = steps + ) { + test() + } + } } From 9502ae2220ecbad0a2d9cc612024af6ca97a7a6a Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 18 Apr 2021 14:57:16 -0400 Subject: [PATCH 3/5] =?UTF-8?q?Commen=C3=A7ement=20de=20la=20v=C3=A9rifica?= =?UTF-8?q?tion=20de=20la=20position=20des=20ingr=C3=A9dients=20des=20m?= =?UTF-8?q?=C3=A9langes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MixMaterialService.kt | 2 + .../service/RecipeStepService.kt | 103 ++++++++++-------- .../service/RecipeStepServiceTest.kt | 6 +- 3 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt index 3bcab80..f65a7a6 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialService.kt @@ -17,6 +17,8 @@ interface MixMaterialService : ModelService /** Updates the [quantity] of the given [mixMaterial]. */ fun updateQuantity(mixMaterial: MixMaterial, quantity: Float): MixMaterial + + fun validateMixMaterials(mixMaterials: Set) } @Service diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index 4cae4c9..fadae3c 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -7,16 +7,15 @@ import org.springframework.http.HttpStatus import org.springframework.stereotype.Service interface RecipeStepService : ModelService { - /** - * Validates if the steps of the given [groupInformation] obey the following criteria: - * - * * The position of the steps is greater or equals to 1 - * * Each position is unique in the collection - * * There is no gap between positions - * - * If any of those criteria are not met, an [InvalidStepsPositionsException] will be thrown. - */ + /** Validates the steps of the given [groupInformation], according to the criteria of [validateSteps]. */ fun validateGroupInformationSteps(groupInformation: RecipeGroupInformation) + + /** + * Validates if the given [steps]. To be valid, the position of each step must be greater or equals to 1 and unique in the set. + * There must also be no gap between the positions. + * If any of those criteria are not met, an [InvalidGroupStepsPositionsException] will be thrown. + */ + fun validateSteps(steps: Set) } @Service @@ -27,11 +26,16 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : override fun idAlreadyExistsException(id: Long) = recipeStepIdAlreadyExistsException(id) override fun validateGroupInformationSteps(groupInformation: RecipeGroupInformation) { - val steps = groupInformation.steps - val group = groupInformation.group + if (groupInformation.steps == null) return - if (steps == null) return + try { + validateSteps(groupInformation.steps!!.toSet()) + } catch (validationException: InvalidStepsPositionsException) { + throw InvalidGroupStepsPositionsException(groupInformation.group, validationException) + } + } + override fun validateSteps(steps: Set) { val sortedSteps = steps.sortedBy { it.position } val errors = mutableSetOf() @@ -44,7 +48,7 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : sortedSteps .groupBy { it.position } .filter { it.value.count() > 1 } - .map { duplicatedStepsPositions(it.key, group) } + .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 @@ -56,9 +60,9 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : // Find all errors and throw if there is any if (isFirstStepPositionInvalid()) errors += invalidFirstStepPosition(sortedSteps[0]) errors += getDuplicatedPositionsErrors() - if (errors.isEmpty() && hasGapBetweenPositions()) errors += gapBetweenStepsPositions(group) + if (errors.isEmpty() && hasGapBetweenPositions()) errors += gapBetweenStepsPositions() if (errors.isNotEmpty()) { - throw InvalidStepsPositionsException(group, errors) + throw InvalidStepsPositionsException(errors) } } } @@ -69,43 +73,50 @@ data class InvalidStepsPositionsError( ) class InvalidStepsPositionsException( - val group: EmployeeGroup, val errors: Set -) : - RestException( - "invalid-recipestep-position", - "Invalid steps position", - HttpStatus.BAD_REQUEST, - "The position of steps for the group ${group.name} are invalid", - mapOf( - "group" to group.name, - "groupId" to group.id!!, - "invalidSteps" to errors - ) +) : RestException( + "invalid-recipestep-position", + "Invalid steps positions", + HttpStatus.BAD_REQUEST, + "The position of steps are invalid", + mapOf( + "invalidSteps" to errors ) +) + +class InvalidGroupStepsPositionsException( + val group: EmployeeGroup, + val exception: InvalidStepsPositionsException +) : RestException( + "invalid-groupinformation-recipestep-position", + "Invalid steps position", + HttpStatus.BAD_REQUEST, + "The position of steps for the group ${group.name} are invalid", + mapOf( + "group" to group.name, + "groupId" to group.id!!, + "invalidSteps" to exception.errors + ) +) const val INVALID_FIRST_STEP_POSITION_ERROR_CODE = "first" const val DUPLICATED_STEPS_POSITIONS_ERROR_CODE = "duplicated" const val GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE = "gap" -private fun invalidFirstStepPosition( - step: RecipeStep -) = InvalidStepsPositionsError( - INVALID_FIRST_STEP_POSITION_ERROR_CODE, - "The position ${step.position} is under the minimum of 1" -) +private fun invalidFirstStepPosition(step: RecipeStep) = + InvalidStepsPositionsError( + INVALID_FIRST_STEP_POSITION_ERROR_CODE, + "The position ${step.position} is under the minimum of 1" + ) -private fun duplicatedStepsPositions( - position: Int, - group: EmployeeGroup -) = InvalidStepsPositionsError( - DUPLICATED_STEPS_POSITIONS_ERROR_CODE, - "The position $position is duplicated in the group ${group.name}" -) +private fun duplicatedStepsPositions(position: Int) = + InvalidStepsPositionsError( + DUPLICATED_STEPS_POSITIONS_ERROR_CODE, + "The position $position is duplicated" + ) -private fun gapBetweenStepsPositions( - group: EmployeeGroup -) = InvalidStepsPositionsError( - GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE, - "The positions for the steps of the group ${group.name} have gaps between them" -) +private fun gapBetweenStepsPositions() = + InvalidStepsPositionsError( + GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE, + "There is a gap between steps positions" + ) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index acafb28..5f6b43b 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -28,7 +28,7 @@ class RecipeStepServiceTest : recipeStep(id = 3L, position = 3) ) ) { - val exception = assertThrows { + val exception = assertThrows { service.validateGroupInformationSteps(this) } @@ -47,7 +47,7 @@ class RecipeStepServiceTest : recipeStep(id = 3L, position = 3) ) ) { - val exception = assertThrows { + val exception = assertThrows { service.validateGroupInformationSteps(this) } @@ -66,7 +66,7 @@ class RecipeStepServiceTest : recipeStep(id = 3L, position = 5) ) ) { - val exception = assertThrows { + val exception = assertThrows { service.validateGroupInformationSteps(this) } From 931494c1f9764452ad1f19caef49b033ecce31e3 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Sun, 18 Apr 2021 19:54:28 -0400 Subject: [PATCH 4/5] =?UTF-8?q?Ajout=20de=20la=20v=C3=A9rification=20des?= =?UTF-8?q?=20ingr=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), From 97c6e17b809acaa5e8db82f2229539a26a144433 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 18 Apr 2021 22:52:47 -0400 Subject: [PATCH 5/5] =?UTF-8?q?Ajout=20des=20tests=20de=20la=20v=C3=A9rifi?= =?UTF-8?q?cation=20des=20ingr=C3=A9dients=20des=20m=C3=A9langes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MixService.kt | 7 +- .../service/RecipeStepService.kt | 2 +- .../service/MixMaterialServiceTest.kt | 67 ++++++++++++++ .../service/RecipeStepServiceTest.kt | 91 +++++++++++-------- 4 files changed, 126 insertions(+), 41 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt index 65edbe3..8416417 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixService.kt @@ -1,8 +1,8 @@ package dev.fyloz.colorrecipesexplorer.service -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MixRepository +import dev.fyloz.colorrecipesexplorer.service.utils.setAll import org.springframework.context.annotation.Lazy import org.springframework.stereotype.Service import javax.transaction.Transactional @@ -43,6 +43,8 @@ class MixServiceImpl( val mixType = mixTypeService.getOrCreateForNameAndMaterialType(entity.name, materialType) val mixMaterials = if (entity.mixMaterials != null) mixMaterialService.create(entity.mixMaterials) else setOf() + mixMaterialService.validateMixMaterials(mixMaterials) + var mix = mix(recipe = recipe, mixType = mixType, mixMaterials = mixMaterials.toMutableSet()) mix = save(mix) @@ -68,8 +70,7 @@ class MixServiceImpl( } } if (entity.mixMaterials != null) { - mix.mixMaterials.clear() - mix.mixMaterials.addAll(mixMaterialService.create(entity.mixMaterials!!).toMutableSet()) + mix.mixMaterials.setAll(mixMaterialService.create(entity.mixMaterials!!).toMutableSet()) } return update(mix) } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt index 8fc0121..44ba176 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -31,7 +31,7 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) : if (groupInformation.steps == null) return try { - validateSteps(groupInformation.steps!!.toSet()) + validateSteps(groupInformation.steps!!) } catch (validationException: InvalidStepsPositionsException) { throw InvalidGroupStepsPositionsException(groupInformation.group, validationException) } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt index 35259e4..bffc6a4 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MixMaterialServiceTest.kt @@ -4,6 +4,7 @@ import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals @@ -99,4 +100,70 @@ class MixMaterialServiceTest : AbstractModelServiceTest { + service.validateMixMaterials(mixMaterials) + } + } + + private fun assertInvalidMixMaterialsPositionsException(mixMaterials: Set, errorType: String) { + val exception = assertThrows { + service.validateMixMaterials(mixMaterials) + } + + assertTrue { exception.errors.size == 1 } + assertTrue { exception.errors.first().type == errorType } + } } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt index ef8d213..48f476f 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepServiceTest.kt @@ -1,7 +1,6 @@ package dev.fyloz.colorrecipesexplorer.service -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.spy +import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository import org.junit.jupiter.api.Test @@ -19,68 +18,86 @@ class RecipeStepServiceTest : // validateGroupInformationSteps() @Test - fun `validateGroupInformationSteps() throws an InvalidGroupStepsPositionsException when the position of the first step of the given groupInformation is not 1`() { - withSteps( + fun `validateGroupInformationSteps() calls validateSteps() with the given RecipeGroupInformation steps`() { + withGroupInformation { + service.validateGroupInformationSteps(this) + + verify(service).validateSteps(this.steps!!) + } + } + + @Test + fun `validateGroupInformationSteps() throws InvalidGroupStepsPositionsException when validateSteps() throws an InvalidStepsPositionsException`() { + withGroupInformation { + doAnswer { throw InvalidStepsPositionsException(setOf()) }.whenever(service).validateSteps(this.steps!!) + + assertThrows { + service.validateGroupInformationSteps(this) + } + } + } + + // validateSteps() + + @Test + fun `validateSteps() throws an InvalidStepsPositionsException when the position of the first step of the given groupInformation is not 1`() { + assertInvalidStepsPositionsException( mutableSetOf( recipeStep(id = 0L, position = 0), recipeStep(id = 1L, position = 1), recipeStep(id = 2L, position = 2), recipeStep(id = 3L, position = 3) - ) - ) { - val exception = assertThrows { - service.validateGroupInformationSteps(this) - } - - assertTrue { exception.errors.count() == 1 } - assertTrue { exception.errors.first().type == INVALID_FIRST_STEP_POSITION_ERROR_CODE } - } + ), + INVALID_FIRST_STEP_POSITION_ERROR_CODE + ) } @Test - fun `validateGroupInformationSteps() throws an InvalidGroupStepsPositionsException when steps positions are duplicated in the given groupInformation`() { - withSteps( + fun `validateSteps() throws an InvalidStepsPositionsException when steps positions are duplicated in the given groupInformation`() { + assertInvalidStepsPositionsException( mutableSetOf( recipeStep(id = 0L, position = 1), recipeStep(id = 1L, position = 2), recipeStep(id = 2L, position = 2), recipeStep(id = 3L, position = 3) - ) - ) { - val exception = assertThrows { - service.validateGroupInformationSteps(this) - } - - assertTrue { exception.errors.count() == 1 } - assertTrue { exception.errors.first().type == DUPLICATED_STEPS_POSITIONS_ERROR_CODE } - } + ), + DUPLICATED_STEPS_POSITIONS_ERROR_CODE + ) } @Test - fun `validateGroupInformationSteps() throws an InvalidGroupStepsPositionsException when there is a gap between steps positions in the given groupInformation`() { - withSteps( + fun `validateSteps() throws an InvalidStepsPositionsException when there is a gap between steps positions in the given groupInformation`() { + assertInvalidStepsPositionsException( mutableSetOf( recipeStep(id = 0L, position = 1), recipeStep(id = 1L, position = 2), recipeStep(id = 2L, position = 4), recipeStep(id = 3L, position = 5) - ) - ) { - val exception = assertThrows { - service.validateGroupInformationSteps(this) - } - - assertTrue { exception.errors.count() == 1 } - assertTrue { exception.errors.first().type == GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE } - } + ), + GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE + ) } - private fun withSteps(steps: MutableSet, test: RecipeGroupInformation.() -> Unit) { + private fun withGroupInformation(steps: MutableSet? = null, test: RecipeGroupInformation.() -> Unit) { recipeGroupInformation( group = employeeGroup(id = 0L), - steps = steps + steps = steps ?: mutableSetOf( + recipeStep(id = 0L, position = 1), + recipeStep(id = 1L, position = 2), + recipeStep(id = 2L, position = 3), + recipeStep(id = 3L, position = 4) + ) ) { test() } } + + private fun assertInvalidStepsPositionsException(steps: MutableSet, errorType: String) { + val exception = assertThrows { + service.validateSteps(steps) + } + + assertTrue { exception.errors.size == 1 } + assertTrue { exception.errors.first().type == errorType } + } }