Ajout de la vérification de l'ordre des étapes des recettes
This commit is contained in:
parent
2b1c8c2555
commit
26314af635
|
@ -1,3 +1,5 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
group = "dev.fyloz.colorrecipesexplorer"
|
||||
|
||||
plugins {
|
||||
|
@ -79,15 +81,17 @@ tasks.test {
|
|||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
tasks.withType<JavaCompile>() {
|
||||
options.compilerArgs.addAll(arrayOf("--release", "11"))
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
tasks.withType<KotlinCompile>().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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(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<RecipeGroupInformation>()
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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<RecipeStep, RecipeStepRepository> {
|
||||
|
||||
/**
|
||||
* 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<RecipeStep>): 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<RecipeStep>): 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<InvalidStepsPositionsError>()
|
||||
|
||||
// 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<InvalidStepsPositionsError>
|
||||
) :
|
||||
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"
|
||||
)
|
||||
|
|
|
@ -306,6 +306,10 @@ fun RestException.assertErrorCode(type: String, identifierName: String) {
|
|||
}
|
||||
}
|
||||
|
||||
fun RestException.assertErrorCode(errorCode: String) {
|
||||
assertEquals(errorCode, this.errorCode)
|
||||
}
|
||||
|
||||
fun <E : Model, N : EntityDto<E>> withBaseSaveDtoTest(
|
||||
entity: E,
|
||||
entitySaveDto: N,
|
||||
|
|
|
@ -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<RecipeStep, RecipeStepService, RecipeStepRepository>() {
|
||||
|
@ -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<InvalidStepsPositionsException> {
|
||||
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<InvalidStepsPositionsException> {
|
||||
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<InvalidStepsPositionsException> {
|
||||
service.validateGroupInformationSteps(this)
|
||||
}
|
||||
|
||||
assertTrue { exception.errors.count() == 1 }
|
||||
assertTrue { exception.errors.first().type == GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE }
|
||||
}
|
||||
}
|
||||
|
||||
private fun withSteps(steps: MutableSet<RecipeStep>, test: RecipeGroupInformation.() -> Unit) {
|
||||
recipeGroupInformation(
|
||||
group = employeeGroup(id = 0L),
|
||||
steps = steps
|
||||
) {
|
||||
test()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue