feature/#25-dtos #28
|
@ -12,4 +12,10 @@ object Constants {
|
|||
|
||||
const val SIMDUT = "$PDF/simdut"
|
||||
}
|
||||
|
||||
object ValidationMessages {
|
||||
const val SIZE_GREATER_OR_EQUALS_ZERO = "Must be greater or equals to 0"
|
||||
const val SIZE_GREATER_OR_EQUALS_ONE = "Must be greater or equals to 1"
|
||||
const val RANGE_OUTSIDE_PERCENTS = "Must be between 0 and 100"
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package dev.fyloz.colorrecipesexplorer.dtos
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import javax.validation.constraints.Min
|
||||
import javax.validation.constraints.NotBlank
|
||||
|
@ -24,7 +25,7 @@ data class MaterialSaveDto(
|
|||
@field:NotBlank
|
||||
val name: String,
|
||||
|
||||
@field:Min(0)
|
||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||
val inventoryQuantity: Float,
|
||||
|
||||
val materialTypeId: Long,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package dev.fyloz.colorrecipesexplorer.dtos
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import javax.validation.constraints.Min
|
||||
|
||||
data class MixMaterialDto(
|
||||
override val id: Long = 0L,
|
||||
|
||||
val material: MaterialDto,
|
||||
|
||||
val quantity: Float,
|
||||
|
||||
val position: Int
|
||||
) : EntityDto
|
||||
|
||||
data class MixMaterialSaveDto(
|
||||
override val id: Long = 0L,
|
||||
|
||||
val materialId: Long,
|
||||
|
||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||
val quantity: Float,
|
||||
|
||||
val position: Int
|
||||
) : EntityDto
|
|
@ -6,10 +6,4 @@ data class RecipeStepDto(
|
|||
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"
|
||||
}
|
||||
}
|
||||
) : EntityDto
|
|
@ -0,0 +1,15 @@
|
|||
package dev.fyloz.colorrecipesexplorer.exception
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
class InvalidPositionsException(val errors: Set<InvalidPositionError>) : RestException(
|
||||
"invalid-positions",
|
||||
"Invalid positions",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"The positions are invalid",
|
||||
mapOf(
|
||||
"errors" to errors
|
||||
)
|
||||
)
|
||||
|
||||
data class InvalidPositionError(val type: String, val details: String)
|
|
@ -25,6 +25,9 @@ interface Logic<D : EntityDto, S : Service<D, *, *>> {
|
|||
/** Saves the given [dto]. */
|
||||
fun save(dto: D): D
|
||||
|
||||
/** Saves all the given [dtos]. */
|
||||
fun saveAll(dtos: Collection<D>): Collection<D>
|
||||
|
||||
/** Updates the given [dto]. Throws if no DTO with the same id exists. */
|
||||
fun update(dto: D): D
|
||||
|
||||
|
@ -50,6 +53,9 @@ abstract class BaseLogic<D : EntityDto, S : Service<D, *, *>>(
|
|||
override fun save(dto: D) =
|
||||
service.save(dto)
|
||||
|
||||
override fun saveAll(dtos: Collection<D>) =
|
||||
dtos.map(::save)
|
||||
|
||||
override fun update(dto: D): D {
|
||||
if (!existsById(dto.id)) {
|
||||
throw notFoundException(value = dto.id)
|
||||
|
|
|
@ -42,11 +42,7 @@ class DefaultMixLogic(
|
|||
this.id!!,
|
||||
this.location,
|
||||
this.mixType,
|
||||
this.mixMaterials.map {
|
||||
with(mixMaterialLogic) {
|
||||
return@with it.toOutput()
|
||||
}
|
||||
}.toSet()
|
||||
this.mixMaterials.map { mixMaterialDto(it) }.toSet()
|
||||
)
|
||||
|
||||
@Transactional
|
||||
|
@ -55,10 +51,11 @@ class DefaultMixLogic(
|
|||
val materialType = materialTypeLogic.getById(entity.materialTypeId)
|
||||
val mixType = mixTypeLogic.getOrCreateForNameAndMaterialType(entity.name, materialType(materialType))
|
||||
|
||||
val mixMaterials = if (entity.mixMaterials != null) mixMaterialLogic.create(entity.mixMaterials) else setOf()
|
||||
val mixMaterials =
|
||||
if (entity.mixMaterials != null) mixMaterialLogic.saveAll(entity.mixMaterials).toSet() else setOf()
|
||||
mixMaterialLogic.validateMixMaterials(mixMaterials)
|
||||
|
||||
var mix = mix(recipe = recipe, mixType = mixType, mixMaterials = mixMaterials.toMutableSet())
|
||||
var mix = mix(recipe = recipe, mixType = mixType, mixMaterials = mixMaterials.map(::mixMaterial).toMutableSet())
|
||||
mix = save(mix)
|
||||
|
||||
recipeLogic.addMix(recipe, mix)
|
||||
|
@ -83,7 +80,7 @@ class DefaultMixLogic(
|
|||
}
|
||||
}
|
||||
if (entity.mixMaterials != null) {
|
||||
mix.mixMaterials.setAll(mixMaterialLogic.create(entity.mixMaterials!!).toMutableSet())
|
||||
mix.mixMaterials.setAll(mixMaterialLogic.saveAll(entity.mixMaterials!!).map(::mixMaterial).toMutableSet())
|
||||
}
|
||||
return update(mix)
|
||||
}
|
||||
|
|
|
@ -1,112 +1,47 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository
|
||||
import dev.fyloz.colorrecipesexplorer.utils.findDuplicated
|
||||
import dev.fyloz.colorrecipesexplorer.utils.hasGaps
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.context.annotation.Profile
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.service.MixMaterialService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
interface MixMaterialLogic : ModelService<MixMaterial, MixMaterialRepository> {
|
||||
/** Checks if one or more mix materials have the given [material]. */
|
||||
fun existsByMaterial(material: Material): Boolean
|
||||
|
||||
/** Creates [MixMaterial]s from the givens [MixMaterialDto]. */
|
||||
fun create(mixMaterials: Set<MixMaterialDto>): Set<MixMaterial>
|
||||
|
||||
/** Creates a [MixMaterial] from a given [MixMaterialDto]. */
|
||||
fun create(mixMaterial: MixMaterialDto): MixMaterial
|
||||
|
||||
/** Updates the [quantity] of the given [mixMaterial]. */
|
||||
fun updateQuantity(mixMaterial: MixMaterial, quantity: Float): MixMaterial
|
||||
|
||||
interface MixMaterialLogic : Logic<MixMaterialDto, MixMaterialService> {
|
||||
/**
|
||||
* 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<MixMaterial>)
|
||||
|
||||
fun MixMaterial.toOutput(): MixMaterialOutputDto
|
||||
fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>)
|
||||
}
|
||||
|
||||
@Service
|
||||
@Profile("!emergency")
|
||||
class DefaultMixMaterialLogic(
|
||||
mixMaterialRepository: MixMaterialRepository,
|
||||
@Lazy val materialLogic: MaterialLogic
|
||||
) : AbstractModelService<MixMaterial, MixMaterialRepository>(mixMaterialRepository), MixMaterialLogic {
|
||||
override fun idNotFoundException(id: Long) = mixMaterialIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = mixMaterialIdAlreadyExistsException(id)
|
||||
|
||||
override fun MixMaterial.toOutput() = MixMaterialOutputDto(
|
||||
this.id!!,
|
||||
this.material,
|
||||
this.quantity,
|
||||
this.position
|
||||
)
|
||||
|
||||
override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material)
|
||||
override fun create(mixMaterials: Set<MixMaterialDto>): Set<MixMaterial> =
|
||||
mixMaterials.map(::create).toSet()
|
||||
|
||||
override fun create(mixMaterial: MixMaterialDto): MixMaterial =
|
||||
mixMaterial(
|
||||
material = material(materialLogic.getById(mixMaterial.materialId)),
|
||||
quantity = mixMaterial.quantity,
|
||||
position = mixMaterial.position
|
||||
)
|
||||
|
||||
override fun updateQuantity(mixMaterial: MixMaterial, quantity: Float) =
|
||||
update(mixMaterial.apply {
|
||||
this.quantity = quantity
|
||||
})
|
||||
|
||||
override fun validateMixMaterials(mixMaterials: Set<MixMaterial>) {
|
||||
@LogicComponent
|
||||
class DefaultMixMaterialLogic(service: MixMaterialService) :
|
||||
BaseLogic<MixMaterialDto, MixMaterialService>(service, MixMaterial::class.simpleName!!), MixMaterialLogic {
|
||||
override fun validateMixMaterials(mixMaterials: Set<MixMaterialDto>) {
|
||||
if (mixMaterials.isEmpty()) return
|
||||
|
||||
val sortedMixMaterials = mixMaterials.sortedBy { it.position }
|
||||
val firstMixMaterial = sortedMixMaterials[0]
|
||||
val errors = mutableSetOf<InvalidMixMaterialsPositionsError>()
|
||||
|
||||
// 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)
|
||||
try {
|
||||
PositionUtils.validate(sortedMixMaterials.map { it.position })
|
||||
} catch (ex: InvalidPositionsException) {
|
||||
throw InvalidMixMaterialsPositionsException(ex.errors)
|
||||
}
|
||||
|
||||
if (isFirstMixMaterialPercentages()) {
|
||||
throw InvalidFirstMixMaterial(firstMixMaterial)
|
||||
if (sortedMixMaterials[0].material.materialType.usePercentages) {
|
||||
throw InvalidFirstMixMaterialException(sortedMixMaterials[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidMixMaterialsPositionsError(
|
||||
val type: String,
|
||||
val details: String
|
||||
)
|
||||
|
||||
// TODO check if required
|
||||
class InvalidMixMaterialsPositionsException(
|
||||
val errors: Set<InvalidMixMaterialsPositionsError>
|
||||
val errors: Set<InvalidPositionError>
|
||||
) : RestException(
|
||||
"invalid-mixmaterial-position",
|
||||
"Invalid mix materials positions",
|
||||
|
@ -117,8 +52,8 @@ class InvalidMixMaterialsPositionsException(
|
|||
)
|
||||
)
|
||||
|
||||
class InvalidFirstMixMaterial(
|
||||
val mixMaterial: MixMaterial
|
||||
class InvalidFirstMixMaterialException(
|
||||
val mixMaterial: MixMaterialDto
|
||||
) : RestException(
|
||||
"invalid-mixmaterial-first",
|
||||
"Invalid first mix material",
|
||||
|
@ -127,27 +62,4 @@ class InvalidFirstMixMaterial(
|
|||
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"
|
||||
)
|
||||
|
||||
)
|
|
@ -1,6 +1,7 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
|
||||
import org.springframework.context.annotation.Lazy
|
||||
|
@ -56,7 +57,7 @@ class DefaultMixTypeLogic(
|
|||
|
||||
override fun save(entity: MixType): MixType {
|
||||
if (materialLogic.existsByName(entity.name))
|
||||
throw materialNameAlreadyExistsException(entity.name)
|
||||
throw AlreadyExistsException("material", "material already exists", "material already exists details (TODO)", entity.name) // TODO
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,26 +2,19 @@ package dev.fyloz.colorrecipesexplorer.logic
|
|||
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
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.recipeStepDto
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeStepService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.findDuplicated
|
||||
import dev.fyloz.colorrecipesexplorer.utils.hasGaps
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
interface RecipeStepLogic : Logic<RecipeStepDto, RecipeStepService> {
|
||||
/** Validates the steps of the given [groupInformation], according to the criteria of [validateSteps]. */
|
||||
/** Validates the steps of the given [groupInformation], according to the criteria of [PositionUtils.validate]. */
|
||||
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<RecipeStepDto>)
|
||||
}
|
||||
|
||||
@LogicComponent
|
||||
|
@ -31,91 +24,16 @@ class DefaultRecipeStepLogic(recipeStepService: RecipeStepService) :
|
|||
if (groupInformation.steps == null) return
|
||||
|
||||
try {
|
||||
validateSteps(groupInformation.steps!!.map { recipeStepDto(it) }.toSet())
|
||||
} catch (validationException: InvalidStepsPositionsException) {
|
||||
throw InvalidGroupStepsPositionsException(groupInformation.group, validationException)
|
||||
}
|
||||
}
|
||||
|
||||
override fun validateSteps(steps: Set<RecipeStepDto>) {
|
||||
if (steps.isEmpty()) return
|
||||
|
||||
val sortedSteps = steps.sortedBy { it.position }
|
||||
val errors = mutableSetOf<InvalidStepsPositionsError>()
|
||||
|
||||
// Check if the first step position is 1
|
||||
validateFirstStepPosition(sortedSteps, errors)
|
||||
|
||||
// Check if any position is duplicated
|
||||
validateDuplicatedStepsPositions(sortedSteps, errors)
|
||||
|
||||
// Check for gaps between positions
|
||||
validateGapsInStepsPositions(sortedSteps, errors)
|
||||
|
||||
if (errors.isNotEmpty()) {
|
||||
throw InvalidStepsPositionsException(errors)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateFirstStepPosition(
|
||||
steps: List<RecipeStepDto>,
|
||||
errors: MutableSet<InvalidStepsPositionsError>
|
||||
) {
|
||||
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<RecipeStepDto>,
|
||||
errors: MutableSet<InvalidStepsPositionsError>
|
||||
) {
|
||||
errors += steps
|
||||
.findDuplicated { it.position }
|
||||
.map {
|
||||
InvalidStepsPositionsError(
|
||||
RecipeStepDto.VALIDATION_ERROR_CODE_DUPLICATED_STEPS_POSITION,
|
||||
"The position $it is duplicated"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateGapsInStepsPositions(
|
||||
steps: List<RecipeStepDto>,
|
||||
errors: MutableSet<InvalidStepsPositionsError>
|
||||
) {
|
||||
if (errors.isEmpty() && steps.hasGaps { it.position }) {
|
||||
errors += InvalidStepsPositionsError(
|
||||
RecipeStepDto.VALIDATION_ERROR_CODE_GAP_BETWEEN_STEPS_POSITIONS,
|
||||
"There is a gap between steps positions"
|
||||
)
|
||||
PositionUtils.validate(groupInformation.steps!!.map { it.position }.toList())
|
||||
} catch (ex: InvalidPositionsException) {
|
||||
throw InvalidGroupStepsPositionsException(groupInformation.group, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class InvalidStepsPositionsError(
|
||||
val type: String,
|
||||
val details: String
|
||||
)
|
||||
|
||||
class InvalidStepsPositionsException(
|
||||
val errors: Set<InvalidStepsPositionsError>
|
||||
) : RestException(
|
||||
"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
|
||||
val exception: InvalidPositionsException
|
||||
) : RestException(
|
||||
"invalid-groupinformation-recipestep-position",
|
||||
"Invalid steps positions",
|
||||
|
@ -127,6 +45,6 @@ class InvalidGroupStepsPositionsException(
|
|||
"invalidSteps" to exception.errors
|
||||
)
|
||||
) {
|
||||
val errors: Set<InvalidStepsPositionsError>
|
||||
val errors: Set<InvalidPositionError>
|
||||
get() = exception.errors
|
||||
}
|
|
@ -39,7 +39,7 @@ data class Material(
|
|||
data class MaterialQuantityDto(
|
||||
val material: Long,
|
||||
|
||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||
val quantity: Float
|
||||
)
|
||||
|
||||
|
@ -77,52 +77,4 @@ fun materialQuantityDto(
|
|||
materialId: Long,
|
||||
quantity: Float,
|
||||
op: MaterialQuantityDto.() -> Unit = {}
|
||||
) = MaterialQuantityDto(materialId, quantity).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const
|
||||
val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found"
|
||||
private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists"
|
||||
private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material"
|
||||
private const val MATERIAL_EXCEPTION_ERROR_CODE = "material"
|
||||
|
||||
fun materialIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A material with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun materialNameNotFoundException(name: String) =
|
||||
NotFoundException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A material with the name $name could not be found",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun materialIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun materialNameAlreadyExistsException(name: String) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material with the name $name already exists",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun cannotDeleteMaterialException(material: Material) =
|
||||
CannotDeleteException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE,
|
||||
"Cannot delete the material ${material.name} because one or more recipes depends on it"
|
||||
)
|
||||
) = MaterialQuantityDto(materialId, quantity).apply(op)
|
|
@ -1,6 +1,8 @@
|
|||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
|
@ -58,13 +60,13 @@ data class MixOutputDto(
|
|||
val id: Long,
|
||||
val location: String?,
|
||||
val mixType: MixType,
|
||||
val mixMaterials: Set<MixMaterialOutputDto>
|
||||
val mixMaterials: Set<MixMaterialDto>
|
||||
)
|
||||
|
||||
data class MixDeductDto(
|
||||
val id: Long,
|
||||
|
||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||
val ratio: Float
|
||||
)
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.Min
|
||||
|
||||
@Entity
|
||||
@Table(name = "mix_material")
|
||||
|
@ -21,22 +19,6 @@ data class MixMaterial(
|
|||
var position: Int
|
||||
) : ModelEntity
|
||||
|
||||
data class MixMaterialDto(
|
||||
val materialId: Long,
|
||||
|
||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||
val quantity: Float,
|
||||
|
||||
val position: Int
|
||||
)
|
||||
|
||||
data class MixMaterialOutputDto(
|
||||
val id: Long,
|
||||
val material: Material, // TODO move to MaterialDto
|
||||
val quantity: Float,
|
||||
val position: Int
|
||||
)
|
||||
|
||||
// ==== DSL ====
|
||||
fun mixMaterial(
|
||||
id: Long? = null,
|
||||
|
@ -46,30 +28,12 @@ fun mixMaterial(
|
|||
op: MixMaterial.() -> Unit = {}
|
||||
) = MixMaterial(id, material, quantity, position).apply(op)
|
||||
|
||||
@Deprecated("Temporary DSL for transition")
|
||||
fun mixMaterialDto(
|
||||
materialId: Long = 0L,
|
||||
quantity: Float = 0f,
|
||||
position: Int = 0,
|
||||
op: MixMaterialDto.() -> Unit = {}
|
||||
) = MixMaterialDto(materialId, quantity, position).apply(op)
|
||||
entity: MixMaterial
|
||||
) = MixMaterialDto(entity.id!!, materialDto(entity.material), entity.quantity, entity.position)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Mix material not found"
|
||||
private const val MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix material already exists"
|
||||
private const val MIX_MATERIAL_EXCEPTION_ERROR_CODE = "mixmaterial"
|
||||
|
||||
fun mixMaterialIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A mix material with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun mixMaterialIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A mix material with the id $id already exists",
|
||||
id
|
||||
)
|
||||
@Deprecated("Temporary DSL for transition")
|
||||
fun mixMaterial(
|
||||
dto: MixMaterialDto
|
||||
) = MixMaterial(dto.id, material(dto.material), dto.quantity, dto.position)
|
|
@ -14,9 +14,4 @@ interface EntityDto<out E> {
|
|||
fun toEntity(): E {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
// GENERAL VALIDATION MESSAGES
|
||||
const val VALIDATION_SIZE_GE_ZERO = "Must be greater or equals to 0"
|
||||
const val VALIDATION_SIZE_GE_ONE = "Must be greater or equals to 1"
|
||||
const val VALIDATION_RANGE_PERCENTS = "Must be between 0 and 100"
|
||||
}
|
|
@ -89,11 +89,11 @@ open class RecipeSaveDto(
|
|||
@field:Pattern(regexp = VALIDATION_COLOR_PATTERN)
|
||||
val color: String,
|
||||
|
||||
@field:Min(0, message = VALIDATION_RANGE_PERCENTS)
|
||||
@field:Max(100, message = VALIDATION_RANGE_PERCENTS)
|
||||
@field:Min(0, message = Constants.ValidationMessages.RANGE_OUTSIDE_PERCENTS)
|
||||
@field:Max(100, message = Constants.ValidationMessages.RANGE_OUTSIDE_PERCENTS)
|
||||
val gloss: Byte,
|
||||
|
||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||
val sample: Int?,
|
||||
|
||||
val approbationDate: LocalDate?,
|
||||
|
@ -125,11 +125,11 @@ open class RecipeUpdateDto(
|
|||
@field:Pattern(regexp = VALIDATION_COLOR_PATTERN)
|
||||
val color: String?,
|
||||
|
||||
@field:Min(0, message = VALIDATION_RANGE_PERCENTS)
|
||||
@field:Max(100, message = VALIDATION_RANGE_PERCENTS)
|
||||
@field:Min(0, message = Constants.ValidationMessages.RANGE_OUTSIDE_PERCENTS)
|
||||
@field:Max(100, message = Constants.ValidationMessages.RANGE_OUTSIDE_PERCENTS)
|
||||
val gloss: Byte?,
|
||||
|
||||
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
|
||||
@field:Min(0, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ZERO)
|
||||
val sample: Int?,
|
||||
|
||||
val approbationDate: LocalDate?,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package dev.fyloz.colorrecipesexplorer.model.touchupkit
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.Constants
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.EntityDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.ModelEntity
|
||||
import dev.fyloz.colorrecipesexplorer.model.VALIDATION_SIZE_GE_ONE
|
||||
import java.time.LocalDate
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.Min
|
||||
|
@ -80,7 +80,7 @@ data class TouchUpKitSaveDto(
|
|||
@field:NotBlank
|
||||
val company: String,
|
||||
|
||||
@field:Min(1, message = VALIDATION_SIZE_GE_ONE)
|
||||
@field:Min(1, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ONE)
|
||||
val quantity: Int,
|
||||
|
||||
val shippingDate: LocalDate,
|
||||
|
@ -109,7 +109,7 @@ data class TouchUpKitUpdateDto(
|
|||
@field:NotBlank
|
||||
val company: String?,
|
||||
|
||||
@field:Min(1, message = VALIDATION_SIZE_GE_ONE)
|
||||
@field:Min(1, message = Constants.ValidationMessages.SIZE_GREATER_OR_EQUALS_ONE)
|
||||
val quantity: Int?,
|
||||
|
||||
val shippingDate: LocalDate?,
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package dev.fyloz.colorrecipesexplorer.repository
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface MixMaterialRepository : JpaRepository<MixMaterial, Long> {
|
||||
/** Checks if one or more mix materials have the given [material]. */
|
||||
fun existsByMaterial(material: Material): Boolean
|
||||
/** Checks if one or more mix materials have the given [materialId]. */
|
||||
fun existsByMaterialId(materialId: Long): Boolean
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository
|
||||
|
||||
interface MixMaterialService : Service<MixMaterialDto, MixMaterial, MixMaterialRepository> {
|
||||
/** Checks if a mix material with the given [materialId] exists. */
|
||||
fun existsByMaterialId(materialId: Long): Boolean
|
||||
}
|
||||
|
||||
@ServiceComponent
|
||||
class DefaultMixMaterialService(repository: MixMaterialRepository, private val materialService: MaterialService) :
|
||||
BaseService<MixMaterialDto, MixMaterial, MixMaterialRepository>(repository), MixMaterialService {
|
||||
override fun existsByMaterialId(materialId: Long) = repository.existsByMaterialId(materialId)
|
||||
|
||||
override fun toDto(entity: MixMaterial) =
|
||||
MixMaterialDto(entity.id!!, materialService.toDto(entity.material), entity.quantity, entity.position)
|
||||
|
||||
override fun toEntity(dto: MixMaterialDto) =
|
||||
MixMaterial(dto.id, materialService.toEntity(dto.material), dto.quantity, dto.position)
|
||||
}
|
|
@ -19,13 +19,19 @@ inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
|||
}
|
||||
}
|
||||
|
||||
/** Find duplicated in the given [Iterable] from keys obtained from the given [keySelector]. */
|
||||
/** Find duplicated keys in the given [Iterable], using keys obtained from the given [keySelector]. */
|
||||
inline fun <T, K> Iterable<T>.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]. */
|
||||
/** Find duplicated elements in the given [Iterable]. */
|
||||
fun <T> Iterable<T>.findDuplicated() =
|
||||
this.groupBy { it }
|
||||
.filter { it.value.count() > 1 }
|
||||
.map { it.key }
|
||||
|
||||
/** Check if the given [Iterable] has gaps between each element, using keys obtained from the given [keySelector]. */
|
||||
inline fun <T> Iterable<T>.hasGaps(keySelector: (T) -> Int) =
|
||||
this.map(keySelector)
|
||||
.toIntArray()
|
||||
|
@ -33,6 +39,12 @@ inline fun <T> Iterable<T>.hasGaps(keySelector: (T) -> Int) =
|
|||
.filterIndexed { index, it -> it != index + 1 }
|
||||
.isNotEmpty()
|
||||
|
||||
/** Check if the given [Int] [Iterable] has gaps between each element. */
|
||||
fun Iterable<Int>.hasGaps() =
|
||||
this.sorted()
|
||||
.filterIndexed { index, it -> it != index + 1 }
|
||||
.isNotEmpty()
|
||||
|
||||
/** Clears and fills the given [MutableCollection] with the given [elements]. */
|
||||
fun <T> MutableCollection<T>.setAll(elements: Collection<T>) {
|
||||
this.clear()
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package dev.fyloz.colorrecipesexplorer.utils
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
|
||||
object PositionUtils {
|
||||
const val INVALID_FIRST_POSITION_ERROR_CODE = "first"
|
||||
const val DUPLICATED_POSITION_ERROR_CODE = "duplicated"
|
||||
const val GAP_BETWEEN_POSITIONS_ERROR_CODE = "gap"
|
||||
|
||||
private const val FIRST_POSITION = 1
|
||||
|
||||
fun validate(positions: List<Int>) {
|
||||
if (positions.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val sortedPositions = positions.sorted()
|
||||
val errors = mutableSetOf<InvalidPositionError>()
|
||||
|
||||
validateFirstPosition(sortedPositions[0], errors)
|
||||
validateDuplicatedPositions(sortedPositions, errors)
|
||||
validateGapsInPositions(sortedPositions, errors)
|
||||
|
||||
if (errors.isNotEmpty()) {
|
||||
throw InvalidPositionsException(errors)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateFirstPosition(position: Int, errors: MutableSet<InvalidPositionError>) {
|
||||
if (position == FIRST_POSITION) {
|
||||
return
|
||||
}
|
||||
|
||||
errors += InvalidPositionError(INVALID_FIRST_POSITION_ERROR_CODE, "The first position must be $FIRST_POSITION")
|
||||
}
|
||||
|
||||
private fun validateDuplicatedPositions(positions: List<Int>, errors: MutableSet<InvalidPositionError>) {
|
||||
errors += positions.findDuplicated()
|
||||
.map { InvalidPositionError(DUPLICATED_POSITION_ERROR_CODE, "The position $it is duplicated") }
|
||||
}
|
||||
|
||||
private fun validateGapsInPositions(positions: List<Int>, errors: MutableSet<InvalidPositionError>) {
|
||||
if (!positions.hasGaps()) {
|
||||
return
|
||||
}
|
||||
|
||||
errors += InvalidPositionError(GAP_BETWEEN_POSITIONS_ERROR_CODE, "There is a gap between the positions")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
import dev.fyloz.colorrecipesexplorer.service.MixMaterialService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||
import io.mockk.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
class DefaultMixMaterialLogicTest {
|
||||
private val mixMaterialServiceMock = mockk<MixMaterialService>()
|
||||
|
||||
private val mixMaterialLogic = DefaultMixMaterialLogic(mixMaterialServiceMock)
|
||||
|
||||
@AfterEach
|
||||
internal fun afterEach() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMixMaterials_normalBehavior_doesNothing() {
|
||||
// Arrange
|
||||
val materialType = MaterialTypeDto(1L, "Unit test material type", "UTMT", false)
|
||||
val material = MaterialDto(1L, "Unit test material", 1000f, false, materialType)
|
||||
val mixMaterial = MixMaterialDto(1L, material, 100f, 1)
|
||||
|
||||
mockkObject(PositionUtils)
|
||||
every { PositionUtils.validate(any()) } just runs
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
mixMaterialLogic.validateMixMaterials(setOf(mixMaterial))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMixMaterials_emptySet_doesNothing() {
|
||||
// Arrange
|
||||
// Act
|
||||
// Assert
|
||||
mixMaterialLogic.validateMixMaterials(setOf())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMixMaterials_firstUsesPercents_throwsInvalidFirstMixMaterialException() {
|
||||
// Arrange
|
||||
val materialType = MaterialTypeDto(1L, "Unit test material type", "UTMT", true)
|
||||
val material = MaterialDto(1L, "Unit test material", 1000f, false, materialType)
|
||||
val mixMaterial = MixMaterialDto(1L, material, 100f, 1)
|
||||
|
||||
mockkObject(PositionUtils)
|
||||
every { PositionUtils.validate(any()) } just runs
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
assertThrows<InvalidFirstMixMaterialException> { mixMaterialLogic.validateMixMaterials(setOf(mixMaterial)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMixMaterials_invalidPositions_throwsInvalidMixMaterialsPositionsException() {
|
||||
// Arrange
|
||||
val materialType = MaterialTypeDto(1L, "Unit test material type", "UTMT", false)
|
||||
val material = MaterialDto(1L, "Unit test material", 1000f, false, materialType)
|
||||
val mixMaterial = MixMaterialDto(1L, material, 100f, 1)
|
||||
|
||||
val errors = setOf(InvalidPositionError("error", "An unit test error"))
|
||||
|
||||
mockkObject(PositionUtils)
|
||||
every { PositionUtils.validate(any()) } throws InvalidPositionsException(errors)
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
assertThrows<InvalidMixMaterialsPositionsException> { mixMaterialLogic.validateMixMaterials(setOf(mixMaterial)) }
|
||||
}
|
||||
}
|
|
@ -1,21 +1,21 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.dtos.RecipeStepDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionError
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
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 dev.fyloz.colorrecipesexplorer.utils.PositionUtils
|
||||
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<RecipeStepService>()
|
||||
|
||||
private val recipeStepLogic = spyk(DefaultRecipeStepLogic(recipeStepServiceMock))
|
||||
private val recipeStepLogic = DefaultRecipeStepLogic(recipeStepServiceMock)
|
||||
|
||||
@AfterEach
|
||||
internal fun afterEach() {
|
||||
|
@ -25,7 +25,8 @@ class DefaultRecipeStepLogicTest {
|
|||
@Test
|
||||
fun validateGroupInformationSteps_normalBehavior_callsValidateSteps() {
|
||||
// Arrange
|
||||
every { recipeStepLogic.validateSteps(any()) } just runs
|
||||
mockkObject(PositionUtils)
|
||||
every { PositionUtils.validate(any()) } just runs
|
||||
|
||||
val group = Group(1L, "Unit test group")
|
||||
val steps = mutableSetOf(RecipeStep(1L, 1, "A message"))
|
||||
|
@ -36,14 +37,15 @@ class DefaultRecipeStepLogicTest {
|
|||
|
||||
// Assert
|
||||
verify {
|
||||
recipeStepLogic.validateSteps(any()) // TODO replace with actual steps dtos when RecipeGroupInformation updated
|
||||
PositionUtils.validate(steps.map { it.position })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateGroupInformationSteps_stepSetIsNull_doesNothing() {
|
||||
// Arrange
|
||||
every { recipeStepLogic.validateSteps(any()) } just runs
|
||||
mockkObject(PositionUtils)
|
||||
every { PositionUtils.validate(any()) } just runs
|
||||
|
||||
val group = Group(1L, "Unit test group")
|
||||
val groupInfo = RecipeGroupInformation(1L, group, "A note", null)
|
||||
|
@ -53,15 +55,17 @@ class DefaultRecipeStepLogicTest {
|
|||
|
||||
// Assert
|
||||
verify(exactly = 0) {
|
||||
recipeStepLogic.validateSteps(any()) // TODO replace with actual steps dtos when RecipeGroupInformation updated
|
||||
PositionUtils.validate(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateGroupInformationSteps_invalidSteps_throwsInvalidGroupStepsPositionsException() {
|
||||
// Arrange
|
||||
val errors = setOf(InvalidStepsPositionsError("error", "An unit test error"))
|
||||
every { recipeStepLogic.validateSteps(any()) } throws InvalidStepsPositionsException(errors)
|
||||
val errors = setOf(InvalidPositionError("error", "An unit test error"))
|
||||
|
||||
mockkObject(PositionUtils)
|
||||
every { PositionUtils.validate(any()) } throws InvalidPositionsException(errors)
|
||||
|
||||
val group = Group(1L, "Unit test group")
|
||||
val steps = mutableSetOf(RecipeStep(1L, 1, "A message"))
|
||||
|
@ -71,187 +75,4 @@ class DefaultRecipeStepLogicTest {
|
|||
// Assert
|
||||
assertThrows<InvalidGroupStepsPositionsException> { 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<RecipeStepDto>()
|
||||
|
||||
// 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<InvalidStepsPositionsException> { 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<InvalidStepsPositionsException> { 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<InvalidStepsPositionsException> { 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<InvalidStepsPositionsException> { 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<RecipeStep, RecipeStepLogic, RecipeStepRepository>() {
|
||||
// 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<InvalidGroupStepsPositionsException> {
|
||||
// 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<RecipeStep>? = 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<RecipeStep>, errorType: String) {
|
||||
// val exception = assertThrows<InvalidStepsPositionsException> {
|
||||
// logic.validateSteps(steps)
|
||||
// }
|
||||
//
|
||||
// assertTrue { exception.errors.size == 1 }
|
||||
// assertTrue { exception.errors.first().type == errorType }
|
||||
// }
|
||||
//}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
//package dev.fyloz.colorrecipesexplorer.logic
|
||||
//
|
||||
//import com.nhaarman.mockitokotlin2.*
|
||||
//import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
//import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
//import dev.fyloz.colorrecipesexplorer.exception.CannotUpdateException
|
||||
//import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
//import dev.fyloz.colorrecipesexplorer.model.*
|
||||
//import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
|
||||
//import org.junit.jupiter.api.AfterEach
|
||||
//import org.junit.jupiter.api.Test
|
||||
//import org.junit.jupiter.api.TestInstance
|
||||
//import org.junit.jupiter.api.assertThrows
|
||||
//import kotlin.test.assertEquals
|
||||
//import kotlin.test.assertFalse
|
||||
//import kotlin.test.assertTrue
|
||||
//
|
||||
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
//class MaterialTypeLogicTest :
|
||||
// AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeLogic, MaterialTypeRepository>() {
|
||||
// override val repository: MaterialTypeRepository = mock()
|
||||
// private val materialService: MaterialLogic = mock()
|
||||
// override val logic: MaterialTypeLogic = spy(DefaultMaterialTypeLogic(repository))
|
||||
// override val entity: MaterialType = materialType(id = 0L, name = "material type", prefix = "MAT")
|
||||
// override val anotherEntity: MaterialType = materialType(id = 1L, name = "another material type", prefix = "AMT")
|
||||
// override val entityWithEntityName: MaterialType = materialType(2L, name = entity.name, prefix = "EEN")
|
||||
// private val systemType = materialType(id = 3L, name = "systype", prefix = "SYS", systemType = true)
|
||||
// private val anotherSystemType = materialType(id = 4L, name = "another systype", prefix = "ASY", systemType = true)
|
||||
// override val entitySaveDto: MaterialTypeSaveDto = spy(materialTypeSaveDto(name = "material type", prefix = "MAT"))
|
||||
// override val entityUpdateDto: MaterialTypeUpdateDto =
|
||||
// spy(materialTypeUpdateDto(id = 0L, name = "material type", prefix = "MAT"))
|
||||
//
|
||||
// @AfterEach
|
||||
// override fun afterEach() {
|
||||
// reset(materialService)
|
||||
// super.afterEach()
|
||||
// }
|
||||
//
|
||||
// // existsByPrefix()
|
||||
//
|
||||
// @Test
|
||||
// fun `existsByPrefix() returns true when a material type with the given prefix exists`() {
|
||||
// whenever(repository.existsByPrefix(entity.prefix)).doReturn(true)
|
||||
//
|
||||
// val found = logic.existsByPrefix(entity.prefix)
|
||||
//
|
||||
// assertTrue(found)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun `existsByPrefix() returns false when no material type with the given prefix exists`() {
|
||||
// whenever(repository.existsByPrefix(entity.prefix)).doReturn(false)
|
||||
//
|
||||
// val found = logic.existsByPrefix(entity.prefix)
|
||||
//
|
||||
// assertFalse(found)
|
||||
// }
|
||||
//
|
||||
// // getAllSystemTypes()
|
||||
//
|
||||
// @Test
|
||||
// fun `getAllSystemTypes() returns all system types`() {
|
||||
// whenever(repository.findAllBySystemTypeIs(true)).doReturn(listOf(systemType, anotherSystemType))
|
||||
//
|
||||
// val found = logic.getAllSystemTypes()
|
||||
//
|
||||
// assertTrue(found.contains(systemType))
|
||||
// assertTrue(found.contains(anotherSystemType))
|
||||
// }
|
||||
//
|
||||
// // getAllNonSystemTypes()
|
||||
//
|
||||
// @Test
|
||||
// fun `getAllNonSystemTypes() returns all non system types`() {
|
||||
// whenever(repository.findAllBySystemTypeIs(false)).doReturn(listOf(entity, anotherEntity))
|
||||
//
|
||||
// val found = logic.getAllNonSystemType()
|
||||
//
|
||||
// assertTrue(found.contains(entity))
|
||||
// assertTrue(found.contains(anotherEntity))
|
||||
// }
|
||||
//
|
||||
// // save()
|
||||
//
|
||||
// @Test
|
||||
// override fun `save(dto) calls and returns save() with the created entity`() {
|
||||
// withBaseSaveDtoTest(entity, entitySaveDto, logic)
|
||||
// }
|
||||
//
|
||||
// // saveMaterialType()
|
||||
//
|
||||
// @Test
|
||||
// fun `saveMaterialType() throws AlreadyExistsException when a material type with the given prefix already exists`() {
|
||||
// doReturn(true).whenever(logic).existsByPrefix(entity.prefix)
|
||||
//
|
||||
// assertThrows<AlreadyExistsException> { logic.save(entity) }
|
||||
// .assertErrorCode("prefix")
|
||||
// }
|
||||
//
|
||||
// // update()
|
||||
//
|
||||
// @Test
|
||||
// override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
// withBaseUpdateDtoTest(entity, entityUpdateDto, logic, { any() })
|
||||
//
|
||||
// override fun `update() saves in the repository and returns the updated value`() {
|
||||
// whenever(repository.save(entity)).doReturn(entity)
|
||||
// whenever(repository.findByName(entity.name)).doReturn(null)
|
||||
// whenever(repository.findByPrefix(entity.prefix)).doReturn(null)
|
||||
// doReturn(true).whenever(logic).existsById(entity.id!!)
|
||||
// doReturn(entity).whenever(logic).getById(entity.id!!)
|
||||
//
|
||||
// val found = logic.update(entity)
|
||||
//
|
||||
// verify(repository).save(entity)
|
||||
// assertEquals(entity, found)
|
||||
// }
|
||||
//
|
||||
// override fun `update() throws NotFoundException when no entity with the given id exists in the repository`() {
|
||||
// whenever(repository.findByName(entity.name)).doReturn(null)
|
||||
// whenever(repository.findByPrefix(entity.prefix)).doReturn(null)
|
||||
// doReturn(false).whenever(logic).existsById(entity.id!!)
|
||||
// doReturn(null).whenever(logic).getById(entity.id!!)
|
||||
//
|
||||
// assertThrows<NotFoundException> { logic.update(entity) }
|
||||
// .assertErrorCode()
|
||||
// }
|
||||
//
|
||||
// override fun `update() throws AlreadyExistsException when an entity with the updated name exists`() {
|
||||
// whenever(repository.findByName(entity.name)).doReturn(entityWithEntityName)
|
||||
// whenever(repository.findByPrefix(entity.prefix)).doReturn(null)
|
||||
// doReturn(true).whenever(logic).existsById(entity.id!!)
|
||||
// doReturn(entity).whenever(logic).getById(entity.id!!)
|
||||
//
|
||||
// assertThrows<AlreadyExistsException> { logic.update(entity) }
|
||||
// .assertErrorCode("name")
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun `update() throws AlreadyExistsException when an entity with the updated prefix exists`() {
|
||||
// val anotherMaterialType = materialType(prefix = entity.prefix)
|
||||
// whenever(repository.findByPrefix(entity.prefix)).doReturn(anotherMaterialType)
|
||||
// doReturn(entity).whenever(logic).getById(entity.id!!)
|
||||
//
|
||||
// assertThrows<AlreadyExistsException> { logic.update(entity) }
|
||||
// .assertErrorCode("prefix")
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun `update() throws CannotUpdateException when updating a system material type`() {
|
||||
// whenever(repository.existsByIdAndSystemTypeIsTrue(systemType.id!!)).doReturn(true)
|
||||
//
|
||||
// assertThrows<CannotUpdateException> { logic.update(systemType) }
|
||||
// }
|
||||
//
|
||||
// // delete()
|
||||
//
|
||||
// @Test
|
||||
// fun `delete() throws CannotDeleteException when deleting a system material type`() {
|
||||
// whenever(repository.existsByIdAndSystemTypeIsTrue(systemType.id!!)).doReturn(true)
|
||||
//
|
||||
// assertThrows<CannotDeleteException> { logic.delete(systemType) }
|
||||
// }
|
||||
//
|
||||
// override fun `delete() deletes in the repository`() {
|
||||
// whenCanBeDeleted {
|
||||
// super.`delete() deletes in the repository`()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
// whenCanBeDeleted {
|
||||
// super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
// whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
//
|
||||
// test()
|
||||
// }
|
||||
//}
|
|
@ -1,255 +1,245 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class MixLogicTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixLogic, MixRepository>() {
|
||||
override val repository: MixRepository = mock()
|
||||
private val recipeService: RecipeLogic = mock()
|
||||
private val materialTypeService: MaterialTypeLogic = mock()
|
||||
private val mixMaterialService: MixMaterialLogic = mock()
|
||||
private val mixTypeService: MixTypeLogic = mock()
|
||||
override val logic: MixLogic =
|
||||
spy(DefaultMixLogic(repository, recipeService, materialTypeService, mixMaterialService, mixTypeService))
|
||||
|
||||
override val entity: Mix = mix(id = 0L, location = "location")
|
||||
override val anotherEntity: Mix = mix(id = 1L)
|
||||
override val entitySaveDto: MixSaveDto =
|
||||
spy(mixSaveDto(mixMaterials = setOf(mixMaterialDto(materialId = 1L, quantity = 1000f, position = 0))))
|
||||
override val entityUpdateDto: MixUpdateDto = spy(mixUpdateDto(id = entity.id!!))
|
||||
|
||||
@AfterEach
|
||||
override fun afterEach() {
|
||||
super.afterEach()
|
||||
reset(recipeService, materialTypeService, mixMaterialService, mixTypeService)
|
||||
}
|
||||
|
||||
// getAllByMixType()
|
||||
|
||||
@Test
|
||||
fun `getAllByMixType() returns all mixes with the given mix type`() {
|
||||
val mixType = mixType(id = 0L)
|
||||
|
||||
whenever(repository.findAllByMixType(mixType)).doReturn(entityList)
|
||||
|
||||
val found = logic.getAllByMixType(mixType)
|
||||
|
||||
assertEquals(entityList, found)
|
||||
}
|
||||
|
||||
// save()
|
||||
|
||||
@Test
|
||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
||||
val recipe = recipe(id = entitySaveDto.recipeId)
|
||||
val materialType = materialType(id = entitySaveDto.materialTypeId)
|
||||
val material = material(
|
||||
name = entitySaveDto.name,
|
||||
inventoryQuantity = Float.MIN_VALUE,
|
||||
isMixType = true,
|
||||
materialType = materialType
|
||||
)
|
||||
val mixType = mixType(name = entitySaveDto.name, material = material)
|
||||
val mix = mix(recipe = recipe, mixType = mixType)
|
||||
val mixWithId = mix(id = 0L, recipe = recipe, mixType = mixType)
|
||||
val mixMaterials = setOf(mixMaterial(material = material(id = 1L), quantity = 1000f))
|
||||
|
||||
whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
|
||||
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialTypeDto(materialType))
|
||||
whenever(mixMaterialService.create(entitySaveDto.mixMaterials!!)).doReturn(mixMaterials)
|
||||
whenever(
|
||||
mixTypeService.getOrCreateForNameAndMaterialType(
|
||||
mixType.name,
|
||||
mixType.material.materialType!!
|
||||
)
|
||||
).doReturn(mixType)
|
||||
doReturn(true).whenever(logic).existsById(mixWithId.id!!)
|
||||
doReturn(mixWithId).whenever(logic).save(any<Mix>())
|
||||
|
||||
val found = logic.save(entitySaveDto)
|
||||
|
||||
verify(logic).save(argThat<Mix> { this.recipe == mix.recipe })
|
||||
verify(recipeService).addMix(recipe, mixWithId)
|
||||
|
||||
// Verify if this method is called instead of the MixType's constructor, which does not check if the name is already taken by a material.
|
||||
verify(mixTypeService).getOrCreateForNameAndMaterialType(mixType.name, mixType.material.materialType!!)
|
||||
|
||||
assertEquals(mixWithId, found)
|
||||
}
|
||||
|
||||
// update()
|
||||
|
||||
private fun mixUpdateDtoTest(
|
||||
scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
|
||||
sharedMixType: Boolean = false,
|
||||
op: MixUpdateDtoTestScope.() -> Unit
|
||||
) {
|
||||
with(scope) {
|
||||
doReturn(true).whenever(logic).existsById(mix.id!!)
|
||||
doReturn(mix).whenever(logic).getById(mix.id!!)
|
||||
doReturn(sharedMixType).whenever(logic).mixTypeIsShared(mix.mixType)
|
||||
doAnswer { it.arguments[0] }.whenever(logic).update(any<Mix>())
|
||||
|
||||
if (mixUpdateDto.materialTypeId != null) {
|
||||
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialTypeDto(materialType))
|
||||
}
|
||||
|
||||
op()
|
||||
}
|
||||
}
|
||||
|
||||
private fun mixUpdateDtoMixTypeTest(sharedMixType: Boolean = false, op: MixUpdateDtoTestScope.() -> Unit) {
|
||||
with(MixUpdateDtoTestScope(mixUpdateDto = mixUpdateDto(id = 0L, name = "name", materialTypeId = 0L))) {
|
||||
mixUpdateDtoTest(this, sharedMixType, op)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() {
|
||||
val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = null, materialTypeId = null))
|
||||
|
||||
doReturn(entity).whenever(logic).getById(any())
|
||||
doReturn(entity).whenever(logic).update(entity)
|
||||
|
||||
val found = logic.update(mixUpdateDto)
|
||||
|
||||
verify(logic).update(entity)
|
||||
|
||||
assertEquals(entity, found)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update(dto) calls MixTypeService saveForNameAndMaterialType() when mix type is shared`() {
|
||||
mixUpdateDtoMixTypeTest(sharedMixType = true) {
|
||||
whenever(mixTypeService.saveForNameAndMaterialType(mixUpdateDto.name!!, materialType))
|
||||
.doReturn(newMixType)
|
||||
|
||||
val found = logic.update(mixUpdateDto)
|
||||
|
||||
verify(mixTypeService).saveForNameAndMaterialType(mixUpdateDto.name!!, materialType)
|
||||
|
||||
assertEquals(newMixType, found.mixType)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update(dto) calls MixTypeService updateForNameAndMaterialType() when mix type is not shared`() {
|
||||
mixUpdateDtoMixTypeTest {
|
||||
whenever(mixTypeService.updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType))
|
||||
.doReturn(newMixType)
|
||||
|
||||
val found = logic.update(mixUpdateDto)
|
||||
|
||||
verify(mixTypeService).updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType)
|
||||
|
||||
assertEquals(newMixType, found.mixType)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update(dto) update, create and delete mix materials according to the given mix materials map`() {
|
||||
mixUpdateDtoTest {
|
||||
val mixMaterials = setOf(
|
||||
mixMaterialDto(materialId = 0L, quantity = 100f, position = 0),
|
||||
mixMaterialDto(materialId = 1L, quantity = 200f, position = 1),
|
||||
mixMaterialDto(materialId = 2L, quantity = 300f, position = 2),
|
||||
mixMaterialDto(materialId = 3L, quantity = 400f, position = 3),
|
||||
)
|
||||
mixUpdateDto.mixMaterials = mixMaterials
|
||||
|
||||
whenever(mixMaterialService.create(any<Set<MixMaterialDto>>())).doAnswer {
|
||||
(it.arguments[0] as Set<MixMaterialDto>).map { dto ->
|
||||
mixMaterial(
|
||||
material = material(id = dto.materialId),
|
||||
quantity = dto.quantity,
|
||||
position = dto.position
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
val found = logic.update(mixUpdateDto)
|
||||
|
||||
mixMaterials.forEach {
|
||||
assertTrue {
|
||||
found.mixMaterials.any { mixMaterial ->
|
||||
mixMaterial.material.id == it.materialId && mixMaterial.quantity == it.quantity && mixMaterial.position == it.position
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateLocations()
|
||||
|
||||
@Test
|
||||
fun `updateLocations() calls updateLocation() for each given MixLocationDto`() {
|
||||
val locations = setOf(
|
||||
mixLocationDto(mixId = 0, location = "Loc 0"),
|
||||
mixLocationDto(mixId = 1, location = "Loc 1"),
|
||||
mixLocationDto(mixId = 2, location = "Loc 2"),
|
||||
mixLocationDto(mixId = 3, location = "Loc 3")
|
||||
)
|
||||
|
||||
logic.updateLocations(locations)
|
||||
|
||||
locations.forEach {
|
||||
verify(logic).updateLocation(it)
|
||||
}
|
||||
}
|
||||
|
||||
// updateLocation()
|
||||
|
||||
@Test
|
||||
fun `updateLocation() updates the location of a mix in the repository according to the given MixLocationDto`() {
|
||||
val locationDto = mixLocationDto(mixId = 0L, location = "Location")
|
||||
|
||||
logic.updateLocation(locationDto)
|
||||
|
||||
verify(repository).updateLocationById(locationDto.mixId, locationDto.location)
|
||||
}
|
||||
|
||||
// delete()
|
||||
|
||||
override fun `delete() deletes in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`delete() deletes in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
// deleteById()
|
||||
|
||||
@Test
|
||||
override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
data class MixUpdateDtoTestScope(
|
||||
val mixType: MixType = mixType(name = "mix type"),
|
||||
val newMixType: MixType = mixType(name = "another mix type"),
|
||||
val materialType: MaterialType = materialType(id = 0L),
|
||||
val mix: Mix = mix(id = 0L, mixType = mixType),
|
||||
val mixUpdateDto: MixUpdateDto = spy(
|
||||
mixUpdateDto(
|
||||
id = 0L,
|
||||
name = null,
|
||||
materialTypeId = null,
|
||||
mixMaterials = setOf()
|
||||
)
|
||||
)
|
||||
)
|
||||
//@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
//class MixLogicTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixLogic, MixRepository>() {
|
||||
// override val repository: MixRepository = mock()
|
||||
// private val recipeService: RecipeLogic = mock()
|
||||
// private val materialTypeService: MaterialTypeLogic = mock()
|
||||
// private val mixMaterialService: MixMaterialLogic = mock()
|
||||
// private val mixTypeService: MixTypeLogic = mock()
|
||||
// override val logic: MixLogic =
|
||||
// spy(DefaultMixLogic(repository, recipeService, materialTypeService, mixMaterialService, mixTypeService))
|
||||
//
|
||||
// override val entity: Mix = mix(id = 0L, location = "location")
|
||||
// override val anotherEntity: Mix = mix(id = 1L)
|
||||
// override val entitySaveDto: MixSaveDto = spy(mixSaveDto(mixMaterials = setOf<MixMaterialDto>()))
|
||||
// override val entityUpdateDto: MixUpdateDto = spy(mixUpdateDto(id = entity.id!!))
|
||||
//
|
||||
// @AfterEach
|
||||
// override fun afterEach() {
|
||||
// super.afterEach()
|
||||
// reset(recipeService, materialTypeService, mixMaterialService, mixTypeService)
|
||||
// }
|
||||
//
|
||||
// // getAllByMixType()
|
||||
//
|
||||
//// @Test
|
||||
//// fun `getAllByMixType() returns all mixes with the given mix type`() {
|
||||
//// val mixType = mixType(id = 0L)
|
||||
////
|
||||
//// whenever(repository.findAllByMixType(mixType)).doReturn(entityList)
|
||||
////
|
||||
//// val found = logic.getAllByMixType(mixType)
|
||||
////
|
||||
//// assertEquals(entityList, found)
|
||||
//// }
|
||||
////
|
||||
//// // save()
|
||||
////
|
||||
//// @Test
|
||||
//// override fun `save(dto) calls and returns save() with the created entity`() {
|
||||
//// val recipe = recipe(id = entitySaveDto.recipeId)
|
||||
//// val materialType = materialType(id = entitySaveDto.materialTypeId)
|
||||
//// val material = material(
|
||||
//// name = entitySaveDto.name,
|
||||
//// inventoryQuantity = Float.MIN_VALUE,
|
||||
//// isMixType = true,
|
||||
//// materialType = materialType
|
||||
//// )
|
||||
//// val mixType = mixType(name = entitySaveDto.name, material = material)
|
||||
//// val mix = mix(recipe = recipe, mixType = mixType)
|
||||
//// val mixWithId = mix(id = 0L, recipe = recipe, mixType = mixType)
|
||||
//// val mixMaterials = setOf(mixMaterial(material = material(id = 1L), quantity = 1000f))
|
||||
////
|
||||
//// whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
|
||||
//// whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialTypeDto(materialType))
|
||||
//// whenever(mixMaterialService.create(entitySaveDto.mixMaterials!!)).doReturn(mixMaterials)
|
||||
//// whenever(
|
||||
//// mixTypeService.getOrCreateForNameAndMaterialType(
|
||||
//// mixType.name,
|
||||
//// mixType.material.materialType!!
|
||||
//// )
|
||||
//// ).doReturn(mixType)
|
||||
//// doReturn(true).whenever(logic).existsById(mixWithId.id!!)
|
||||
//// doReturn(mixWithId).whenever(logic).save(any<Mix>())
|
||||
////
|
||||
//// val found = logic.save(entitySaveDto)
|
||||
////
|
||||
//// verify(logic).save(argThat<Mix> { this.recipe == mix.recipe })
|
||||
//// verify(recipeService).addMix(recipe, mixWithId)
|
||||
////
|
||||
//// // Verify if this method is called instead of the MixType's constructor, which does not check if the name is already taken by a material.
|
||||
//// verify(mixTypeService).getOrCreateForNameAndMaterialType(mixType.name, mixType.material.materialType!!)
|
||||
////
|
||||
//// assertEquals(mixWithId, found)
|
||||
//// }
|
||||
////
|
||||
//// // update()
|
||||
////
|
||||
//// private fun mixUpdateDtoTest(
|
||||
//// scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
|
||||
//// sharedMixType: Boolean = false,
|
||||
//// op: MixUpdateDtoTestScope.() -> Unit
|
||||
//// ) {
|
||||
//// with(scope) {
|
||||
//// doReturn(true).whenever(logic).existsById(mix.id!!)
|
||||
//// doReturn(mix).whenever(logic).getById(mix.id!!)
|
||||
//// doReturn(sharedMixType).whenever(logic).mixTypeIsShared(mix.mixType)
|
||||
//// doAnswer { it.arguments[0] }.whenever(logic).update(any<Mix>())
|
||||
////
|
||||
//// if (mixUpdateDto.materialTypeId != null) {
|
||||
//// whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialTypeDto(materialType))
|
||||
//// }
|
||||
////
|
||||
//// op()
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// private fun mixUpdateDtoMixTypeTest(sharedMixType: Boolean = false, op: MixUpdateDtoTestScope.() -> Unit) {
|
||||
//// with(MixUpdateDtoTestScope(mixUpdateDto = mixUpdateDto(id = 0L, name = "name", materialTypeId = 0L))) {
|
||||
//// mixUpdateDtoTest(this, sharedMixType, op)
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// @Test
|
||||
//// override fun `update(dto) calls and returns update() with the created entity`() {
|
||||
//// val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = null, materialTypeId = null))
|
||||
////
|
||||
//// doReturn(entity).whenever(logic).getById(any())
|
||||
//// doReturn(entity).whenever(logic).update(entity)
|
||||
////
|
||||
//// val found = logic.update(mixUpdateDto)
|
||||
////
|
||||
//// verify(logic).update(entity)
|
||||
////
|
||||
//// assertEquals(entity, found)
|
||||
//// }
|
||||
////
|
||||
//// @Test
|
||||
//// fun `update(dto) calls MixTypeService saveForNameAndMaterialType() when mix type is shared`() {
|
||||
//// mixUpdateDtoMixTypeTest(sharedMixType = true) {
|
||||
//// whenever(mixTypeService.saveForNameAndMaterialType(mixUpdateDto.name!!, materialType))
|
||||
//// .doReturn(newMixType)
|
||||
////
|
||||
//// val found = logic.update(mixUpdateDto)
|
||||
////
|
||||
//// verify(mixTypeService).saveForNameAndMaterialType(mixUpdateDto.name!!, materialType)
|
||||
////
|
||||
//// assertEquals(newMixType, found.mixType)
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// @Test
|
||||
//// fun `update(dto) calls MixTypeService updateForNameAndMaterialType() when mix type is not shared`() {
|
||||
//// mixUpdateDtoMixTypeTest {
|
||||
//// whenever(mixTypeService.updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType))
|
||||
//// .doReturn(newMixType)
|
||||
////
|
||||
//// val found = logic.update(mixUpdateDto)
|
||||
////
|
||||
//// verify(mixTypeService).updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType)
|
||||
////
|
||||
//// assertEquals(newMixType, found.mixType)
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// @Test
|
||||
//// fun `update(dto) update, create and delete mix materials according to the given mix materials map`() {
|
||||
//// mixUpdateDtoTest {
|
||||
//// val mixMaterials = setOf(
|
||||
//// mixMaterialDto(materialId = 0L, quantity = 100f, position = 0),
|
||||
//// mixMaterialDto(materialId = 1L, quantity = 200f, position = 1),
|
||||
//// mixMaterialDto(materialId = 2L, quantity = 300f, position = 2),
|
||||
//// mixMaterialDto(materialId = 3L, quantity = 400f, position = 3),
|
||||
//// )
|
||||
//// mixUpdateDto.mixMaterials = mixMaterials
|
||||
////
|
||||
//// whenever(mixMaterialService.create(any<Set<MixMaterialDto>>())).doAnswer {
|
||||
//// (it.arguments[0] as Set<MixMaterialDto>).map { dto ->
|
||||
//// mixMaterial(
|
||||
//// material = material(id = dto.materialId),
|
||||
//// quantity = dto.quantity,
|
||||
//// position = dto.position
|
||||
//// )
|
||||
//// }.toSet()
|
||||
//// }
|
||||
////
|
||||
//// val found = logic.update(mixUpdateDto)
|
||||
////
|
||||
//// mixMaterials.forEach {
|
||||
//// assertTrue {
|
||||
//// found.mixMaterials.any { mixMaterial ->
|
||||
//// mixMaterial.material.id == it.materialId && mixMaterial.quantity == it.quantity && mixMaterial.position == it.position
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// // updateLocations()
|
||||
////
|
||||
//// @Test
|
||||
//// fun `updateLocations() calls updateLocation() for each given MixLocationDto`() {
|
||||
//// val locations = setOf(
|
||||
//// mixLocationDto(mixId = 0, location = "Loc 0"),
|
||||
//// mixLocationDto(mixId = 1, location = "Loc 1"),
|
||||
//// mixLocationDto(mixId = 2, location = "Loc 2"),
|
||||
//// mixLocationDto(mixId = 3, location = "Loc 3")
|
||||
//// )
|
||||
////
|
||||
//// logic.updateLocations(locations)
|
||||
////
|
||||
//// locations.forEach {
|
||||
//// verify(logic).updateLocation(it)
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// // updateLocation()
|
||||
////
|
||||
//// @Test
|
||||
//// fun `updateLocation() updates the location of a mix in the repository according to the given MixLocationDto`() {
|
||||
//// val locationDto = mixLocationDto(mixId = 0L, location = "Location")
|
||||
////
|
||||
//// logic.updateLocation(locationDto)
|
||||
////
|
||||
//// verify(repository).updateLocationById(locationDto.mixId, locationDto.location)
|
||||
//// }
|
||||
////
|
||||
//// // delete()
|
||||
////
|
||||
//// override fun `delete() deletes in the repository`() {
|
||||
//// whenCanBeDeleted {
|
||||
//// super.`delete() deletes in the repository`()
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// // deleteById()
|
||||
////
|
||||
//// @Test
|
||||
//// override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
//// whenCanBeDeleted {
|
||||
//// super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
//// whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
////
|
||||
//// test()
|
||||
//// }
|
||||
//}
|
||||
//
|
||||
//data class MixUpdateDtoTestScope(
|
||||
// val mixType: MixType = mixType(name = "mix type"),
|
||||
// val newMixType: MixType = mixType(name = "another mix type"),
|
||||
// val materialType: MaterialType = materialType(id = 0L),
|
||||
// val mix: Mix = mix(id = 0L, mixType = mixType),
|
||||
// val mixUpdateDto: MixUpdateDto = spy(
|
||||
// mixUpdateDto(
|
||||
// id = 0L,
|
||||
// name = null,
|
||||
// materialTypeId = null,
|
||||
// mixMaterials = setOf()
|
||||
// )
|
||||
// )
|
||||
//)
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
package dev.fyloz.colorrecipesexplorer.logic
|
||||
|
||||
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.TestInstance
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class MixMaterialLogicTest : AbstractModelServiceTest<MixMaterial, MixMaterialLogic, MixMaterialRepository>() {
|
||||
override val repository: MixMaterialRepository = mock()
|
||||
private val materialService: MaterialLogic = mock()
|
||||
override val logic: MixMaterialLogic = spy(DefaultMixMaterialLogic(repository, materialService))
|
||||
|
||||
private val material: Material = material(id = 0L)
|
||||
override val entity: MixMaterial = mixMaterial(id = 0L, material = material, quantity = 1000f)
|
||||
override val anotherEntity: MixMaterial = mixMaterial(id = 1L, material = material)
|
||||
|
||||
// existsByMaterial()
|
||||
|
||||
@Test
|
||||
fun `existsByMaterial() returns true when a mix material with the given material exists`() {
|
||||
whenever(repository.existsByMaterial(material)).doReturn(true)
|
||||
|
||||
val found = logic.existsByMaterial(material)
|
||||
|
||||
assertTrue(found)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `existsByMaterial() returns false when no mix material with the given material exists`() {
|
||||
whenever(repository.existsByMaterial(material)).doReturn(false)
|
||||
|
||||
val found = logic.existsByMaterial(material)
|
||||
|
||||
assertFalse(found)
|
||||
}
|
||||
|
||||
// create()
|
||||
|
||||
@Test
|
||||
fun `create(set) calls create() for each MixMaterialDto`() {
|
||||
val mixMaterialDtos = setOf(
|
||||
mixMaterialDto(materialId = 0L, quantity = 1000f, position = 1),
|
||||
mixMaterialDto(materialId = 1L, quantity = 2000f, position = 2),
|
||||
mixMaterialDto(materialId = 2L, quantity = 3000f, position = 3),
|
||||
mixMaterialDto(materialId = 3L, quantity = 4000f, position = 4)
|
||||
)
|
||||
|
||||
doAnswer {
|
||||
with(it.arguments[0] as MixMaterialDto) {
|
||||
mixMaterial(
|
||||
material = material(id = this.materialId),
|
||||
quantity = this.quantity,
|
||||
position = this.position
|
||||
)
|
||||
}
|
||||
}.whenever(logic).create(any<MixMaterialDto>())
|
||||
|
||||
val found = logic.create(mixMaterialDtos)
|
||||
|
||||
mixMaterialDtos.forEach { dto ->
|
||||
verify(logic).create(dto)
|
||||
assertTrue {
|
||||
found.any {
|
||||
it.material.id == dto.materialId && it.quantity == dto.quantity && it.position == dto.position
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create() creates a mix material according to the given MixUpdateDto`() {
|
||||
val mixMaterialDto = mixMaterialDto(materialId = 0L, quantity = 1000f, position = 1)
|
||||
|
||||
whenever(materialService.getById(mixMaterialDto.materialId)).doAnswer { materialDto(material(id = it.arguments[0] as Long)) }
|
||||
|
||||
val found = logic.create(mixMaterialDto)
|
||||
|
||||
assertTrue {
|
||||
found.material.id == mixMaterialDto.materialId &&
|
||||
found.quantity == mixMaterialDto.quantity &&
|
||||
found.position == mixMaterialDto.position
|
||||
}
|
||||
}
|
||||
|
||||
// updateQuantity()
|
||||
|
||||
@Test
|
||||
fun `updateQuantity() updates the given mix material with the given quantity`() {
|
||||
val quantity = 5000f
|
||||
assertNotEquals(quantity, entity.quantity, message = "Quantities must not be equals for this test to works")
|
||||
|
||||
doAnswer { it.arguments[0] }.whenever(logic).update(any())
|
||||
|
||||
val found = logic.updateQuantity(entity, quantity)
|
||||
|
||||
assertEquals(found.quantity, quantity)
|
||||
}
|
||||
|
||||
// validateMixMaterials()
|
||||
|
||||
@Test
|
||||
fun `validateMixMaterials() throws InvalidMixMaterialsPositionsException when the position of the first mix material is not 1`() {
|
||||
assertInvalidMixMaterialsPositionsException(
|
||||
setOf(
|
||||
mixMaterial(id = 0L, position = 0),
|
||||
mixMaterial(id = 1L, position = 1),
|
||||
mixMaterial(id = 2L, position = 2),
|
||||
mixMaterial(id = 3L, position = 3)
|
||||
),
|
||||
INVALID_FIRST_MIX_MATERIAL_POSITION_ERROR_CODE
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validateMixMaterials() throws InvalidMixMaterialsPositionsException when positions are duplicated`() {
|
||||
assertInvalidMixMaterialsPositionsException(
|
||||
setOf(
|
||||
mixMaterial(id = 0L, position = 1),
|
||||
mixMaterial(id = 1L, position = 2),
|
||||
mixMaterial(id = 2L, position = 2),
|
||||
mixMaterial(id = 3L, position = 3)
|
||||
),
|
||||
DUPLICATED_MIX_MATERIALS_POSITIONS_ERROR_CODE
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validateMixMaterials() throws InvalidMixMaterialsPositionsException when there is a gap between positions`() {
|
||||
assertInvalidMixMaterialsPositionsException(
|
||||
setOf(
|
||||
mixMaterial(id = 0L, position = 1),
|
||||
mixMaterial(id = 1L, position = 2),
|
||||
mixMaterial(id = 2L, position = 4),
|
||||
mixMaterial(id = 3L, position = 5)
|
||||
),
|
||||
GAP_BETWEEN_MIX_MATERIALS_POSITIONS_ERROR_CODE
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validateMixMaterials() throws InvalidFirstMixMaterial when the first mix material's quantity is expressed in percents`() {
|
||||
val normalMaterial = material(materialType = materialType(usePercentages = false))
|
||||
val percentsMaterial = material(materialType = materialType(usePercentages = true))
|
||||
val mixMaterials = setOf(
|
||||
mixMaterial(id = 0L, position = 1, material = percentsMaterial),
|
||||
mixMaterial(id = 1L, position = 2, material = normalMaterial),
|
||||
mixMaterial(id = 2L, position = 3, material = normalMaterial),
|
||||
mixMaterial(id = 3L, position = 4, material = normalMaterial)
|
||||
)
|
||||
|
||||
assertThrows<InvalidFirstMixMaterial> {
|
||||
logic.validateMixMaterials(mixMaterials)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertInvalidMixMaterialsPositionsException(mixMaterials: Set<MixMaterial>, errorType: String) {
|
||||
val exception = assertThrows<InvalidMixMaterialsPositionsException> {
|
||||
logic.validateMixMaterials(mixMaterials)
|
||||
}
|
||||
|
||||
assertTrue { exception.errors.size == 1 }
|
||||
assertTrue { exception.errors.first().type == errorType }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package dev.fyloz.colorrecipesexplorer.utils
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.InvalidPositionsException
|
||||
import io.mockk.clearAllMocks
|
||||
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 PositionUtilsTest {
|
||||
@AfterEach
|
||||
internal fun afterEach() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateSteps_normalBehavior_doesNothing() {
|
||||
// Arrange
|
||||
val positions = listOf(1, 2)
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
assertDoesNotThrow { PositionUtils.validate(positions) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateSteps_emptyStepSet_doesNothing() {
|
||||
// Arrange
|
||||
val positions = listOf<Int>()
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
assertDoesNotThrow { PositionUtils.validate(positions) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateSteps_hasInvalidPositions_throwsInvalidStepsPositionsException() {
|
||||
// Arrange
|
||||
val positions = listOf(2, 3)
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
assertThrows<InvalidPositionsException> { PositionUtils.validate(positions) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateSteps_firstStepPositionInvalid_returnsInvalidStepValidationError() {
|
||||
// Arrange
|
||||
val positions = listOf(2, 3)
|
||||
|
||||
// Act
|
||||
val exception = assertThrows<InvalidPositionsException> { PositionUtils.validate(positions) }
|
||||
|
||||
// Assert
|
||||
assertTrue {
|
||||
exception.errors.any { it.type == PositionUtils.INVALID_FIRST_POSITION_ERROR_CODE }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateSteps_duplicatedPositions_returnsInvalidStepValidationError() {
|
||||
// Arrange
|
||||
val positions = listOf(1, 1)
|
||||
|
||||
// Act
|
||||
val exception = assertThrows<InvalidPositionsException> { PositionUtils.validate(positions) }
|
||||
|
||||
// Assert
|
||||
assertTrue {
|
||||
exception.errors.any { it.type == PositionUtils.DUPLICATED_POSITION_ERROR_CODE }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateSteps_gapsInPositions_returnsInvalidStepValidationError() {
|
||||
// Arrange
|
||||
val positions = listOf(1, 3)
|
||||
|
||||
// Act
|
||||
val exception = assertThrows<InvalidPositionsException> { PositionUtils.validate(positions) }
|
||||
|
||||
// Assert
|
||||
assertTrue {
|
||||
exception.errors.any { it.type == PositionUtils.GAP_BETWEEN_POSITIONS_ERROR_CODE }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue