diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/dtos/RecipeStepDto.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/dtos/RecipeStepDto.kt new file mode 100644 index 0000000..f1fe4eb --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/dtos/RecipeStepDto.kt @@ -0,0 +1,15 @@ +package dev.fyloz.colorrecipesexplorer.dtos + +data class RecipeStepDto( + override val id: Long = 0L, + + val position: Int, + + val message: String +) : EntityDto { + companion object { + const val VALIDATION_ERROR_CODE_INVALID_FIRST_STEP = "first" + const val VALIDATION_ERROR_CODE_DUPLICATED_STEPS_POSITION = "duplicated" + const val VALIDATION_ERROR_CODE_GAP_BETWEEN_STEPS_POSITIONS = "gap" + } +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/Logic.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/Logic.kt index 647d6de..3578c0f 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/Logic.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/Logic.kt @@ -61,7 +61,7 @@ abstract class BaseLogic>( override fun deleteById(id: Long) = service.deleteById(id) - protected fun notFoundException(identifierName: String = idIdentifierName, value: Any) = + protected fun notFoundException(identifierName: String = ID_IDENTIFIER_NAME, value: Any) = NotFoundException( typeNameLowerCase, "$typeName not found", @@ -70,7 +70,7 @@ abstract class BaseLogic>( identifierName ) - protected fun alreadyExistsException(identifierName: String = nameIdentifierName, value: Any) = + protected fun alreadyExistsException(identifierName: String = NAME_IDENTIFIER_NAME, value: Any) = AlreadyExistsException( typeNameLowerCase, "$typeName already exists", @@ -87,7 +87,7 @@ abstract class BaseLogic>( ) companion object { - const val idIdentifierName = "id" - const val nameIdentifierName = "name" + const val ID_IDENTIFIER_NAME = "id" + const val NAME_IDENTIFIER_NAME = "name" } } \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogic.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogic.kt index 3bc0d2c..3323ff8 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogic.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogic.kt @@ -1,19 +1,18 @@ package dev.fyloz.colorrecipesexplorer.logic -import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent +import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto import dev.fyloz.colorrecipesexplorer.exception.RestException import dev.fyloz.colorrecipesexplorer.model.RecipeGroupInformation import dev.fyloz.colorrecipesexplorer.model.RecipeStep import dev.fyloz.colorrecipesexplorer.model.account.Group -import dev.fyloz.colorrecipesexplorer.model.recipeStepIdAlreadyExistsException -import dev.fyloz.colorrecipesexplorer.model.recipeStepIdNotFoundException -import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository +import dev.fyloz.colorrecipesexplorer.model.recipeStepDto +import dev.fyloz.colorrecipesexplorer.service.RecipeStepService import dev.fyloz.colorrecipesexplorer.utils.findDuplicated import dev.fyloz.colorrecipesexplorer.utils.hasGaps import org.springframework.http.HttpStatus -import org.springframework.stereotype.Service -interface RecipeStepLogic : ModelService { +interface RecipeStepLogic : Logic { /** Validates the steps of the given [groupInformation], according to the criteria of [validateSteps]. */ fun validateGroupInformationSteps(groupInformation: RecipeGroupInformation) @@ -22,106 +21,112 @@ interface RecipeStepLogic : ModelService { * 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) + fun validateSteps(steps: Set) } -@Service -@RequireDatabase -class DefaultRecipeStepLogic(recipeStepRepository: RecipeStepRepository) : - AbstractModelService(recipeStepRepository), - RecipeStepLogic { - override fun idNotFoundException(id: Long) = recipeStepIdNotFoundException(id) - override fun idAlreadyExistsException(id: Long) = recipeStepIdAlreadyExistsException(id) - +@LogicComponent +class DefaultRecipeStepLogic(recipeStepService: RecipeStepService) : + BaseLogic(recipeStepService, RecipeStep::class.simpleName!!), RecipeStepLogic { override fun validateGroupInformationSteps(groupInformation: RecipeGroupInformation) { if (groupInformation.steps == null) return try { - validateSteps(groupInformation.steps!!) + validateSteps(groupInformation.steps!!.map { recipeStepDto(it) }.toSet()) } catch (validationException: InvalidStepsPositionsException) { throw InvalidGroupStepsPositionsException(groupInformation.group, validationException) } } - override fun validateSteps(steps: Set) { + override fun validateSteps(steps: Set) { if (steps.isEmpty()) return val sortedSteps = steps.sortedBy { it.position } val errors = mutableSetOf() // Check if the first step position is 1 - fun isFirstStepPositionInvalid() = - sortedSteps[0].position != 1 + validateFirstStepPosition(sortedSteps, errors) // Check if any position is duplicated - fun getDuplicatedPositionsErrors() = - sortedSteps - .findDuplicated { it.position } - .map { duplicatedStepsPositions(it) } + validateDuplicatedStepsPositions(sortedSteps, errors) + + // Check for gaps between positions + validateGapsInStepsPositions(sortedSteps, errors) - // Find all errors and throw if there is any - if (isFirstStepPositionInvalid()) errors += invalidFirstStepPosition(sortedSteps[0]) - errors += getDuplicatedPositionsErrors() - if (errors.isEmpty() && steps.hasGaps { it.position }) errors += gapBetweenStepsPositions() if (errors.isNotEmpty()) { throw InvalidStepsPositionsException(errors) } } + + private fun validateFirstStepPosition( + steps: List, + errors: MutableSet + ) { + if (steps[0].position != 1) { + errors += InvalidStepsPositionsError( + RecipeStepDto.VALIDATION_ERROR_CODE_INVALID_FIRST_STEP, + "The first step must be at position 1" + ) + } + } + + private fun validateDuplicatedStepsPositions( + steps: List, + errors: MutableSet + ) { + errors += steps + .findDuplicated { it.position } + .map { + InvalidStepsPositionsError( + RecipeStepDto.VALIDATION_ERROR_CODE_DUPLICATED_STEPS_POSITION, + "The position $it is duplicated" + ) + } + } + + private fun validateGapsInStepsPositions( + steps: List, + errors: MutableSet + ) { + if (errors.isEmpty() && steps.hasGaps { it.position }) { + errors += InvalidStepsPositionsError( + RecipeStepDto.VALIDATION_ERROR_CODE_GAP_BETWEEN_STEPS_POSITIONS, + "There is a gap between steps positions" + ) + } + } } data class InvalidStepsPositionsError( - val type: String, - val details: String + val type: String, + val details: String ) class InvalidStepsPositionsException( - val errors: Set + val errors: Set ) : RestException( - "invalid-recipestep-position", - "Invalid steps positions", - HttpStatus.BAD_REQUEST, - "The position of steps are invalid", - mapOf( - "invalidSteps" to errors - ) + "invalid-recipestep-position", + "Invalid steps positions", + HttpStatus.BAD_REQUEST, + "The position of steps are invalid", + mapOf( + "invalidSteps" to errors + ) ) class InvalidGroupStepsPositionsException( val group: Group, val exception: InvalidStepsPositionsException ) : RestException( - "invalid-groupinformation-recipestep-position", - "Invalid steps positions", - 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 - ) + "invalid-groupinformation-recipestep-position", + "Invalid steps positions", + 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 + ) ) { val errors: Set get() = 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 duplicatedStepsPositions(position: Int) = - InvalidStepsPositionsError( - DUPLICATED_STEPS_POSITIONS_ERROR_CODE, - "The position $position is duplicated" - ) - -private fun gapBetweenStepsPositions() = - InvalidStepsPositionsError( - GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE, - "There is a gap between steps positions" - ) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/RecipeStep.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/RecipeStep.kt index 51f9377..ddc6eac 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/RecipeStep.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/RecipeStep.kt @@ -1,7 +1,6 @@ package dev.fyloz.colorrecipesexplorer.model -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.exception.NotFoundException +import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto import javax.persistence.* @Entity @@ -16,31 +15,7 @@ data class RecipeStep( val message: String ) : ModelEntity -// ==== DSL ==== -fun recipeStep( - id: Long? = null, - position: Int = 0, - message: String = "message", - op: RecipeStep.() -> Unit = {} -) = RecipeStep(id, position, message).apply(op) - -// ==== Exceptions ==== -private const val RECIPE_STEP_NOT_FOUND_EXCEPTION_TITLE = "Recipe step not found" -private const val RECIPE_STEP_ALREADY_EXISTS_EXCEPTION_TITLE = "Recipe step already exists" -private const val RECIPE_STEP_EXCEPTION_ERROR_CODE = "recipestep" - -fun recipeStepIdNotFoundException(id: Long) = - NotFoundException( - RECIPE_STEP_EXCEPTION_ERROR_CODE, - RECIPE_STEP_NOT_FOUND_EXCEPTION_TITLE, - "A recipe step with the id $id could not be found", - id - ) - -fun recipeStepIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - RECIPE_STEP_EXCEPTION_ERROR_CODE, - RECIPE_STEP_ALREADY_EXISTS_EXCEPTION_TITLE, - "A recipe step with the id $id already exists", - id - ) +@Deprecated("Temporary DSL for transition") +fun recipeStepDto( + entity: RecipeStep +) = RecipeStepDto(entity.id!!, entity.position, entity.message) \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt new file mode 100644 index 0000000..57a8777 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeStepService.kt @@ -0,0 +1,18 @@ +package dev.fyloz.colorrecipesexplorer.service + +import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent +import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto +import dev.fyloz.colorrecipesexplorer.model.RecipeStep +import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository + +interface RecipeStepService : Service + +@ServiceComponent +class DefaultRecipeStepService(repository: RecipeStepRepository) : + BaseService(repository), RecipeStepService { + override fun toDto(entity: RecipeStep) = + RecipeStepDto(entity.id!!, entity.position, entity.message) + + override fun toEntity(dto: RecipeStepDto) = + RecipeStep(dto.id, dto.position, dto.message) +} \ No newline at end of file diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/DefaultRecipeStepLogicTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/DefaultRecipeStepLogicTest.kt new file mode 100644 index 0000000..f606abd --- /dev/null +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/DefaultRecipeStepLogicTest.kt @@ -0,0 +1,257 @@ +package dev.fyloz.colorrecipesexplorer.logic + +import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto +import dev.fyloz.colorrecipesexplorer.model.RecipeGroupInformation +import dev.fyloz.colorrecipesexplorer.model.RecipeStep +import dev.fyloz.colorrecipesexplorer.model.account.Group +import dev.fyloz.colorrecipesexplorer.service.RecipeStepService +import io.mockk.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertTrue + +class DefaultRecipeStepLogicTest { + private val recipeStepServiceMock = mockk() + + private val recipeStepLogic = spyk(DefaultRecipeStepLogic(recipeStepServiceMock)) + + @AfterEach + internal fun afterEach() { + clearAllMocks() + } + + @Test + fun validateGroupInformationSteps_normalBehavior_callsValidateSteps() { + // Arrange + every { recipeStepLogic.validateSteps(any()) } just runs + + val group = Group(1L, "Unit test group") + val steps = mutableSetOf(RecipeStep(1L, 1, "A message")) + val groupInfo = RecipeGroupInformation(1L, group, "A note", steps) + + // Act + recipeStepLogic.validateGroupInformationSteps(groupInfo) + + // Assert + verify { + recipeStepLogic.validateSteps(any()) // TODO replace with actual steps dtos when RecipeGroupInformation updated + } + } + + @Test + fun validateGroupInformationSteps_stepSetIsNull_doesNothing() { + // Arrange + every { recipeStepLogic.validateSteps(any()) } just runs + + val group = Group(1L, "Unit test group") + val groupInfo = RecipeGroupInformation(1L, group, "A note", null) + + // Act + recipeStepLogic.validateGroupInformationSteps(groupInfo) + + // Assert + verify(exactly = 0) { + recipeStepLogic.validateSteps(any()) // TODO replace with actual steps dtos when RecipeGroupInformation updated + } + } + + @Test + fun validateGroupInformationSteps_invalidSteps_throwsInvalidGroupStepsPositionsException() { + // Arrange + val errors = setOf(InvalidStepsPositionsError("error", "An unit test error")) + every { recipeStepLogic.validateSteps(any()) } throws InvalidStepsPositionsException(errors) + + val group = Group(1L, "Unit test group") + val steps = mutableSetOf(RecipeStep(1L, 1, "A message")) + val groupInfo = RecipeGroupInformation(1L, group, "A note", steps) + + // Act + // Assert + assertThrows { recipeStepLogic.validateGroupInformationSteps(groupInfo) } + } + + @Test + fun validateSteps_normalBehavior_doesNothing() { + // Arrange + val recipeSteps = setOf( + RecipeStepDto(1L, 1, "A message"), + RecipeStepDto(2L, 2, "Another message") + ) + + // Act + // Assert + assertDoesNotThrow { recipeStepLogic.validateSteps(recipeSteps) } + } + + @Test + fun validateSteps_emptyStepSet_doesNothing() { + // Arrange + val recipeSteps = setOf() + + // Act + // Assert + assertDoesNotThrow { recipeStepLogic.validateSteps(recipeSteps) } + } + + @Test + fun validateSteps_hasInvalidPositions_throwsInvalidStepsPositionsException() { + // Arrange + val recipeSteps = setOf( + RecipeStepDto(1L, 2, "A message"), + RecipeStepDto(2L, 3, "Another message") + ) + + // Act + // Assert + assertThrows { recipeStepLogic.validateSteps(recipeSteps) } + } + + @Test + fun validateSteps_firstStepPositionInvalid_returnsInvalidStepValidationError() { + // Arrange + val recipeSteps = setOf( + RecipeStepDto(1L, 2, "A message"), + RecipeStepDto(2L, 3, "Another message") + ) + + // Act + val exception = assertThrows { recipeStepLogic.validateSteps(recipeSteps) } + + // Assert + assertTrue { + exception.errors.any { it.type == RecipeStepDto.VALIDATION_ERROR_CODE_INVALID_FIRST_STEP } + } + } + + @Test + fun validateSteps_duplicatedPositions_returnsInvalidStepValidationError() { + // Arrange + val recipeSteps = setOf( + RecipeStepDto(1L, 1, "A message"), + RecipeStepDto(2L, 1, "Another message") + ) + + // Act + val exception = assertThrows { recipeStepLogic.validateSteps(recipeSteps) } + + // Assert + assertTrue { + exception.errors.any { it.type == RecipeStepDto.VALIDATION_ERROR_CODE_DUPLICATED_STEPS_POSITION } + } + } + + @Test + fun validateSteps_gapsInPositions_returnsInvalidStepValidationError() { + // Arrange + val recipeSteps = setOf( + RecipeStepDto(1L, 1, "A message"), + RecipeStepDto(2L, 3, "Another message") + ) + + // Act + val exception = assertThrows { recipeStepLogic.validateSteps(recipeSteps) } + + // Assert + assertTrue { + exception.errors.any { it.type == RecipeStepDto.VALIDATION_ERROR_CODE_GAP_BETWEEN_STEPS_POSITIONS } + } + } +} + +//@TestInstance(TestInstance.Lifecycle.PER_CLASS) +//class RecipeStepLogicTest : +// AbstractModelServiceTest() { +// override val repository: RecipeStepRepository = mock() +// override val logic: RecipeStepLogic = spy(DefaultRecipeStepLogic(repository)) +// +// override val entity: RecipeStep = recipeStep(id = 0L, message = "message") +// override val anotherEntity: RecipeStep = recipeStep(id = 1L, message = "another message") +// +// // validateGroupInformationSteps() +// +// @Test +// fun `validateGroupInformationSteps() calls validateSteps() with the given RecipeGroupInformation steps`() { +// withGroupInformation { +// logic.validateGroupInformationSteps(this) +// +// verify(logic).validateSteps(this.steps!!) +// } +// } +// +// @Test +// fun `validateGroupInformationSteps() throws InvalidGroupStepsPositionsException when validateSteps() throws an InvalidStepsPositionsException`() { +// withGroupInformation { +// doAnswer { throw InvalidStepsPositionsException(setOf()) }.whenever(logic).validateSteps(this.steps!!) +// +// assertThrows { +// logic.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) +// ), +// INVALID_FIRST_STEP_POSITION_ERROR_CODE +// ) +// } +// +// @Test +// 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) +// ), +// DUPLICATED_STEPS_POSITIONS_ERROR_CODE +// ) +// } +// +// @Test +// 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) +// ), +// GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE +// ) +// } +// +// private fun withGroupInformation(steps: MutableSet? = null, test: RecipeGroupInformation.() -> Unit) { +// recipeGroupInformation( +// group = group(id = 0L), +// 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 { +// logic.validateSteps(steps) +// } +// +// assertTrue { exception.errors.size == 1 } +// assertTrue { exception.errors.first().type == errorType } +// } +//} diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogicTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogicTest.kt deleted file mode 100644 index 43b2767..0000000 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/RecipeStepLogicTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.logic - -import com.nhaarman.mockitokotlin2.* -import dev.fyloz.colorrecipesexplorer.model.RecipeGroupInformation -import dev.fyloz.colorrecipesexplorer.model.RecipeStep -import dev.fyloz.colorrecipesexplorer.model.account.group -import dev.fyloz.colorrecipesexplorer.model.recipeGroupInformation -import dev.fyloz.colorrecipesexplorer.model.recipeStep -import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertTrue - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class RecipeStepLogicTest : - AbstractModelServiceTest() { - override val repository: RecipeStepRepository = mock() - override val logic: RecipeStepLogic = spy(DefaultRecipeStepLogic(repository)) - - override val entity: RecipeStep = recipeStep(id = 0L, message = "message") - override val anotherEntity: RecipeStep = recipeStep(id = 1L, message = "another message") - - // validateGroupInformationSteps() - - @Test - fun `validateGroupInformationSteps() calls validateSteps() with the given RecipeGroupInformation steps`() { - withGroupInformation { - logic.validateGroupInformationSteps(this) - - verify(logic).validateSteps(this.steps!!) - } - } - - @Test - fun `validateGroupInformationSteps() throws InvalidGroupStepsPositionsException when validateSteps() throws an InvalidStepsPositionsException`() { - withGroupInformation { - doAnswer { throw InvalidStepsPositionsException(setOf()) }.whenever(logic).validateSteps(this.steps!!) - - assertThrows { - logic.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) - ), - INVALID_FIRST_STEP_POSITION_ERROR_CODE - ) - } - - @Test - 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) - ), - DUPLICATED_STEPS_POSITIONS_ERROR_CODE - ) - } - - @Test - 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) - ), - GAP_BETWEEN_STEPS_POSITIONS_ERROR_CODE - ) - } - - private fun withGroupInformation(steps: MutableSet? = null, test: RecipeGroupInformation.() -> Unit) { - recipeGroupInformation( - group = group(id = 0L), - 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 { - logic.validateSteps(steps) - } - - assertTrue { exception.errors.size == 1 } - assertTrue { exception.errors.first().type == errorType } - } -}