Merge branch 'duplicated-products-in-mix' into 'master'
Resolve "Les ingrédients des mélanges sont enregistrés en plus de ceux qui étaient déjà enregistrés lors de la modification d'un mélange" Closes #46 See merge request color-recipes-explorer/backend!7
This commit is contained in:
commit
7367816038
|
@ -81,7 +81,7 @@ tasks.test {
|
|||
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
events("skipped", "failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,11 @@ tasks.withType<JavaCompile> {
|
|||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
||||
kotlinOptions.jvmTarget = "11"
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.contracts.ExperimentalContracts"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.dokkaHtml {
|
||||
|
|
|
@ -13,12 +13,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler
|
|||
import org.springframework.web.context.request.WebRequest
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
|
||||
|
||||
abstract class RestException(val httpStatus: HttpStatus) : RuntimeException() {
|
||||
abstract val exceptionMessage: String
|
||||
abstract class RestException(val exceptionMessage: String, val httpStatus: HttpStatus) : RuntimeException(exceptionMessage) {
|
||||
abstract fun buildBody(): RestExceptionBody
|
||||
|
||||
override val message: String by lazy { exceptionMessage }
|
||||
|
||||
open inner class RestExceptionBody(val status: Int = httpStatus.value(), @JsonProperty("message") val message: String = exceptionMessage)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,15 +5,32 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Model
|
|||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
|
||||
class EntityAlreadyExistsException(modelType: Class<out Model>, val identifierType: IdentifierType, val identifierName: String?, val requestedId: Any) : ModelException(modelType) {
|
||||
constructor(modelType: Class<out Model>, identifierType: IdentifierType, requestedId: Any) : this(modelType, identifierType, identifierType.name, requestedId)
|
||||
constructor(exception: EntityAlreadyExistsException) : this(exception.type, exception.identifierType, exception.identifierName, exception.requestedId)
|
||||
class EntityAlreadyExistsException(
|
||||
modelType: Class<out Model>,
|
||||
val identifierType: IdentifierType,
|
||||
val identifierName: String?,
|
||||
val requestedId: Any
|
||||
) : ModelException(modelType) {
|
||||
constructor(modelType: Class<out Model>, identifierType: IdentifierType, requestedId: Any) : this(
|
||||
modelType,
|
||||
identifierType,
|
||||
identifierType.name,
|
||||
requestedId
|
||||
)
|
||||
|
||||
constructor(exception: EntityAlreadyExistsException) : this(
|
||||
exception.type,
|
||||
exception.identifierType,
|
||||
exception.identifierName,
|
||||
exception.requestedId
|
||||
)
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
class EntityAlreadyExistsRestException(val value: Any) : RestException(HttpStatus.CONFLICT) {
|
||||
override val exceptionMessage: String = "An entity with the given identifier already exists"
|
||||
class EntityAlreadyExistsRestException(val value: Any) :
|
||||
RestException("An entity with the given identifier already exists", HttpStatus.CONFLICT) {
|
||||
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
|
|
|
@ -5,14 +5,17 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Model
|
|||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
|
||||
class EntityNotFoundException(modelType: Class<out Model>, val identifierType: IdentifierType, val identifierName: String, val requestedId: Any) : ModelException(modelType) {
|
||||
constructor(modelType: Class<out Model>, identifierType: IdentifierType, requestedId: Any) : this(modelType, identifierType, identifierType.name, requestedId)
|
||||
}
|
||||
class EntityNotFoundException(
|
||||
modelType: Class<out Model>,
|
||||
val identifierType: IdentifierType,
|
||||
val requestedId: Any
|
||||
) : ModelException(modelType)
|
||||
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
class EntityNotFoundRestException(val value: Any) : RestException(HttpStatus.NOT_FOUND) {
|
||||
override val exceptionMessage: String = "An entity could not be found with the given identifier"
|
||||
class EntityNotFoundRestException(val value: Any) :
|
||||
RestException("An entity could not be found with the given identifier", HttpStatus.NOT_FOUND) {
|
||||
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class MixTypeJavaService extends AbstractJavaService<MixType, MixTypeRepo
|
|||
public MixType getByMaterial(Material material) {
|
||||
Optional<MixType> found = findOptional(repository.findByMaterial(material));
|
||||
if (found.isEmpty())
|
||||
throw new EntityNotFoundException(type, ModelException.IdentifierType.OTHER, MixTypeKt.IDENTIFIER_MATERIAL_NAME, material);
|
||||
throw new EntityNotFoundException(type, ModelException.IdentifierType.OTHER, material);
|
||||
|
||||
return found.get();
|
||||
}
|
||||
|
|
|
@ -2,20 +2,15 @@ package dev.fyloz.trial.colorrecipesexplorer
|
|||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.properties.CREProperties
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.properties.MaterialTypeProperties
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.context.annotation.Bean
|
||||
import javax.sql.DataSource
|
||||
|
||||
@SpringBootApplication(exclude = [LiquibaseAutoConfiguration::class])
|
||||
@EnableConfigurationProperties(MaterialTypeProperties::class, CREProperties::class, DatabaseUpdaterProperties::class)
|
||||
class ColorRecipesExplorerApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
fun main() {
|
||||
runApplication<ColorRecipesExplorerApplication>()
|
||||
}
|
||||
|
|
|
@ -189,9 +189,9 @@ const val defaultGroupCookieName = "Default-Group"
|
|||
val blacklistedJwtTokens = mutableListOf<String>()
|
||||
|
||||
class JwtAuthenticationFilter(
|
||||
val authManager: AuthenticationManager,
|
||||
val employeeService: EmployeeService,
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties
|
||||
private val authManager: AuthenticationManager,
|
||||
private val employeeService: EmployeeService,
|
||||
private val securityConfigurationProperties: SecurityConfigurationProperties
|
||||
) : UsernamePasswordAuthenticationFilter() {
|
||||
private var debugMode = false
|
||||
|
||||
|
@ -238,8 +238,8 @@ class JwtAuthenticationFilter(
|
|||
}
|
||||
|
||||
class JwtAuthorizationFilter(
|
||||
val userDetailsService: EmployeeUserDetailsServiceImpl,
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
private val userDetailsService: EmployeeUserDetailsServiceImpl,
|
||||
private val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
authenticationManager: AuthenticationManager
|
||||
) : BasicAuthenticationFilter(authenticationManager) {
|
||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.model
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
@ -109,18 +110,15 @@ open class EmployeeUpdateDto(
|
|||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
||||
val firstName: String = "",
|
||||
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
||||
val firstName: String?,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
||||
val lastName: String = "",
|
||||
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
||||
val lastName: String?,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
val permissions: Set<EmployeePermission> = mutableSetOf()
|
||||
) : EntityDto<Employee> {
|
||||
override fun toEntity(): Employee =
|
||||
Employee(id, firstName, lastName, permissions = permissions.toMutableSet())
|
||||
}
|
||||
val permissions: Set<EmployeePermission>?
|
||||
) : EntityDto<Employee>
|
||||
|
||||
|
||||
private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis"
|
||||
|
@ -148,6 +146,7 @@ data class EmployeeGroup(
|
|||
@JsonIgnore
|
||||
val employees: MutableSet<Employee> = mutableSetOf()
|
||||
) : NamedModel {
|
||||
@JsonProperty("employeeCount")
|
||||
fun getEmployeeCount() = employees.size
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is EmployeeGroup && id == other.id && name == other.name
|
||||
|
@ -313,16 +312,6 @@ fun employee(
|
|||
lastLoginTime
|
||||
).apply(op)
|
||||
|
||||
fun employee(
|
||||
employee: Employee,
|
||||
newId: Long? = null
|
||||
) = with(employee) {
|
||||
Employee(
|
||||
newId
|
||||
?: id, firstName, lastName, password, isDefaultGroupUser, isSystemUser, group, permissions, lastLoginTime
|
||||
)
|
||||
}
|
||||
|
||||
fun employeeSaveDto(
|
||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
||||
id: Long = 0L,
|
||||
|
@ -350,12 +339,6 @@ fun employeeGroup(
|
|||
op: EmployeeGroup.() -> Unit = {}
|
||||
) = EmployeeGroup(id, name, permissions, employees).apply(op)
|
||||
|
||||
fun employeeGroup(
|
||||
employeeGroup: EmployeeGroup,
|
||||
newId: Long? = null,
|
||||
newName: String? = null
|
||||
) = with(employeeGroup) { EmployeeGroup(newId ?: id, newName ?: name, permissions, employees) }
|
||||
|
||||
fun employeeGroupSaveDto(
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
|
|
|
@ -9,7 +9,6 @@ import javax.persistence.*
|
|||
import javax.validation.constraints.Min
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
import javax.validation.constraints.Size
|
||||
|
||||
private const val MATERIAL_ID_NULL_MESSAGE = "Un identifiant est requis"
|
||||
private const val MATERIAL_NAME_NULL_MESSAGE = "Un nom est requis"
|
||||
|
@ -98,6 +97,7 @@ data class InventoryMaterial(
|
|||
@NotNull val lowQuantity: Boolean = false
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
fun Material.toInventoryMaterial(minimumQuantity: Float): InventoryMaterial {
|
||||
Assert.notNull(id, "Cannot convert a material without id to an inventory material")
|
||||
Assert.notNull(name, "Cannot convert a material without name to an inventory material")
|
||||
|
|
|
@ -20,7 +20,7 @@ data class MixMaterial(
|
|||
@JoinColumn(name = "material_id")
|
||||
val material: Material,
|
||||
|
||||
val quantity: Float
|
||||
var quantity: Float
|
||||
) : Model {
|
||||
constructor(mix: Mix, material: Material, quantity: Float) : this(null, mix, material, quantity)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ interface NamedModel : Model {
|
|||
|
||||
interface EntityDto<out E> {
|
||||
/** Converts the dto to an actual entity. */
|
||||
fun toEntity(): E
|
||||
fun toEntity(): E {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
fun Collection<Model>.sortedById(): Collection<Model> = this.sortedBy { it.id }
|
||||
|
|
|
@ -50,6 +50,19 @@ data class Recipe(
|
|||
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
|
||||
var steps: MutableCollection<RecipeStep>
|
||||
) : Model {
|
||||
constructor() : this(
|
||||
null,
|
||||
"name",
|
||||
"description",
|
||||
0,
|
||||
null,
|
||||
"remark",
|
||||
"note",
|
||||
company(),
|
||||
mutableListOf(),
|
||||
mutableListOf()
|
||||
)
|
||||
|
||||
constructor(
|
||||
id: Long,
|
||||
name: String,
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import java.util.*
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotNull
|
||||
|
||||
@Entity
|
||||
@Table(name = "recipe_step")
|
||||
|
|
|
@ -3,7 +3,6 @@ package dev.fyloz.trial.colorrecipesexplorer.model.validation
|
|||
import javax.validation.Constraint
|
||||
import javax.validation.ConstraintValidator
|
||||
import javax.validation.ConstraintValidatorContext
|
||||
import javax.validation.Payload
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.reflect.KClass
|
||||
|
@ -15,8 +14,7 @@ private const val MESSAGE = "must be null or not blank"
|
|||
@Constraint(validatedBy = [NullOrNotBlankValidator::class])
|
||||
annotation class NullOrNotBlank(
|
||||
val message: String = MESSAGE,
|
||||
val groups: Array<KClass<*>> = [],
|
||||
val payload: Array<KClass<out Payload>> = []
|
||||
val groups: Array<KClass<*>> = []
|
||||
)
|
||||
|
||||
class NullOrNotBlankValidator : ConstraintValidator<NullOrNotBlank, String> {
|
||||
|
@ -42,5 +40,4 @@ fun isNotNullAndNotBlank(value: String?): Boolean {
|
|||
return value != null && value.isNotBlank()
|
||||
}
|
||||
|
||||
@ExperimentalContracts
|
||||
fun String?.or(alternative: String): String = if (isNotNullAndNotBlank(this)) this else alternative
|
||||
infix fun String?.or(alternative: String): String = if (isNotNullAndNotBlank(this)) this else alternative
|
||||
|
|
|
@ -3,7 +3,6 @@ package dev.fyloz.trial.colorrecipesexplorer.model.validation
|
|||
import javax.validation.Constraint
|
||||
import javax.validation.ConstraintValidator
|
||||
import javax.validation.ConstraintValidatorContext
|
||||
import javax.validation.Payload
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
private const val MIN_SIZE = Long.MIN_VALUE
|
||||
|
@ -17,8 +16,7 @@ annotation class NullOrSize(
|
|||
val min: Long = MIN_SIZE,
|
||||
val max: Long = MAX_SIZE,
|
||||
val message: String = MESSAGE,
|
||||
val groups: Array<KClass<*>> = [],
|
||||
val payload: Array<KClass<out Payload>> = []
|
||||
val groups: Array<KClass<*>> = []
|
||||
)
|
||||
|
||||
class NullOrSizeValidator : ConstraintValidator<NullOrSize, Any> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//package dev.fyloz.trial.colorrecipesexplorer.rest
|
||||
//
|
||||
package dev.fyloz.trial.colorrecipesexplorer.rest
|
||||
|
||||
//import dev.fyloz.trial.colorrecipesexplorer.model.InventoryMaterial
|
||||
//import dev.fyloz.trial.colorrecipesexplorer.service.InventoryService
|
||||
//import org.springframework.http.ResponseEntity
|
||||
|
|
|
@ -34,7 +34,7 @@ interface RestModelApiController<E : Model, S : EntityDto<E>, U : EntityDto<E>>
|
|||
|
||||
abstract class AbstractRestApiController<E, N : EntityDto<E>, U : EntityDto<E>, S : ExternalService<E, N, U, *>>(
|
||||
val service: S,
|
||||
protected val controllerPath: String
|
||||
private val controllerPath: String
|
||||
) :
|
||||
RestApiController<E, N, U> {
|
||||
protected abstract fun getEntityId(entity: E): Any?
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.SecurityConfigurationProperties
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.blacklistedJwtTokens
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.defaultGroupCookieName
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsRestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundRestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.validation.or
|
||||
import dev.fyloz.trial.colorrecipesexplorer.repository.EmployeeGroupRepository
|
||||
import dev.fyloz.trial.colorrecipesexplorer.repository.EmployeeRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
|
@ -170,13 +170,13 @@ class EmployeeServiceImpl(employeeRepository: EmployeeRepository, val passwordEn
|
|||
return update(with(entity) {
|
||||
Employee(
|
||||
id = id,
|
||||
firstName = if (firstName.isNotBlank()) firstName else persistedEmployee.firstName,
|
||||
lastName = if (lastName.isNotBlank()) lastName else persistedEmployee.lastName,
|
||||
firstName = firstName or persistedEmployee.firstName,
|
||||
lastName = lastName or persistedEmployee.lastName,
|
||||
password = persistedEmployee.password,
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = persistedEmployee.group,
|
||||
permissions = if (permissions.isNotEmpty()) permissions.toMutableSet() else persistedEmployee.permissions,
|
||||
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
|
||||
lastLoginTime = persistedEmployee.lastLoginTime
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.TooLowQuantityException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.InventoryMaterial
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.dto.InventoryDto
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.toInventoryMaterial
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
const val minimumQuantity = 100.0f // TODO quantity stored in database
|
||||
|
||||
@Service
|
||||
class InventoryService(val materialService: MaterialService) {
|
||||
fun getAllMaterials(): Collection<InventoryMaterial> {
|
||||
return materialService.getAllNotMixType().map { it.toInventoryMaterial(minimumQuantity) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Use all materials in the given [mixes].
|
||||
* @throws TooLowQuantityException When there is not enough stock for a material
|
||||
|
|
|
@ -105,8 +105,7 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
|
|||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
class CannotDeleteUsedMaterialTypeRestException : RestException(HttpStatus.CONFLICT) {
|
||||
override val exceptionMessage: String = "Cannot delete a used material type"
|
||||
|
||||
class CannotDeleteUsedMaterialTypeRestException :
|
||||
RestException("Cannot delete a used material type", HttpStatus.CONFLICT) {
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@ interface MixMaterialService : ModelService<MixMaterial, MixMaterialRepository>
|
|||
/** Creates [MixMaterial]s from the given [map]. The [map] must have the format <Material ID, Quantity>. */
|
||||
fun createFromMap(mix: Mix, map: Map<Long, Float>): Collection<MixMaterial>
|
||||
|
||||
/** Creates a [MixMaterial] from the given [pair]. The [pair] must have the format <Material ID, Quantity>. */
|
||||
fun createFromPair(mix: Mix, pair: Pair<Long, Float>): MixMaterial
|
||||
/** Creates a [MixMaterial] with the material with the given [materialId] and the given [quantity]. */
|
||||
fun create(mix: Mix, materialId: Long, quantity: Float): MixMaterial
|
||||
|
||||
/** Updates the [quantity] of the given [mixMaterial]. */
|
||||
fun updateQuantity(mixMaterial: MixMaterial, quantity: Float): MixMaterial
|
||||
}
|
||||
|
||||
@Service
|
||||
|
@ -26,10 +29,13 @@ class MixMaterialServiceImpl(
|
|||
) : AbstractModelService<MixMaterial, MixMaterialRepository>(mixMaterialRepository), MixMaterialService {
|
||||
override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material)
|
||||
override fun createFromMap(mix: Mix, map: Map<Long, Float>): Collection<MixMaterial> =
|
||||
map.map { createFromPair(mix, it.toPair()) }
|
||||
map.map { create(mix, it.key, it.value) }
|
||||
|
||||
override fun createFromPair(mix: Mix, pair: Pair<Long, Float>): MixMaterial {
|
||||
val material = materialService.getById(pair.first)
|
||||
return mixMaterial(mix = mix, material = material, quantity = pair.second)
|
||||
}
|
||||
override fun create(mix: Mix, materialId: Long, quantity: Float): MixMaterial =
|
||||
mixMaterial(mix = mix, material = materialService.getById(materialId), quantity = quantity)
|
||||
|
||||
override fun updateQuantity(mixMaterial: MixMaterial, quantity: Float) =
|
||||
update(mixMaterial.apply {
|
||||
this.quantity = quantity
|
||||
})
|
||||
}
|
||||
|
|
|
@ -53,7 +53,33 @@ class MixServiceImpl(
|
|||
return mix
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun update(entity: MixUpdateDto): Mix {
|
||||
fun updateMixMaterials(mix: Mix, mixMaterialsMap: Map<Long, Float>) {
|
||||
val existingMixMaterialsMaterialIds = mix.mixMaterials.map { it.material.id }
|
||||
val toDelete = mix.mixMaterials
|
||||
|
||||
mix.mixMaterials = mutableListOf(
|
||||
// update existing mix materials
|
||||
*mixMaterialsMap
|
||||
.filter { it.key in existingMixMaterialsMaterialIds }
|
||||
.map { (materialId, quantity) ->
|
||||
val existingMixMaterial = mix.mixMaterials.first { it.material.id == materialId }
|
||||
toDelete.remove(existingMixMaterial)
|
||||
mixMaterialService.updateQuantity(existingMixMaterial, quantity)
|
||||
}
|
||||
.toTypedArray(),
|
||||
// create new mix materials
|
||||
*mixMaterialsMap
|
||||
.filter { it.key !in existingMixMaterialsMaterialIds }
|
||||
.map { (materialId, quantity) -> mixMaterialService.create(mix, materialId, quantity) }
|
||||
.toTypedArray()
|
||||
)
|
||||
|
||||
// delete unused mix materials
|
||||
toDelete.forEach { mixMaterialService.delete(it) }
|
||||
}
|
||||
|
||||
val mix = getById(entity.id)
|
||||
if (entity.name != null || entity.materialTypeId != null) {
|
||||
mix.mixType = if (mixTypeIsShared(mix.mixType)) {
|
||||
|
@ -70,7 +96,7 @@ class MixServiceImpl(
|
|||
}
|
||||
}
|
||||
if (entity.mixMaterials != null) {
|
||||
mix.mixMaterials = mixMaterialService.createFromMap(mix, entity.mixMaterials).toMutableList()
|
||||
updateMixMaterials(mix, entity.mixMaterials)
|
||||
}
|
||||
return update(mix)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpda
|
|||
/** Gets all recipes with the given [company]. */
|
||||
fun getAllByCompany(company: Company): Collection<Recipe>
|
||||
|
||||
/** Updates the public data of a recipe with the given [publicDateDto]. */
|
||||
/** Updates the public data of a recipe with the given [publicDataDto]. */
|
||||
fun updatePublicData(publicDataDto: RecipePublicDataDto)
|
||||
|
||||
/** Adds the given [mix] to the given [recipe]. */
|
||||
|
|
|
@ -11,7 +11,6 @@ import dev.fyloz.trial.colorrecipesexplorer.rest.RestApiController
|
|||
import io.jsonwebtoken.lang.Assert
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
|
||||
/**
|
||||
* A service implementing the basics CRUD operations for the given entities.
|
||||
|
@ -144,6 +143,7 @@ interface ExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : Entity
|
|||
NamedModelService<E, R>, ExternalModelService<E, S, U, R>
|
||||
|
||||
/** An [AbstractService] with the functionalities of a [ExternalService]. */
|
||||
@Suppress("unused")
|
||||
abstract class AbstractExternalService<E, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>>(repository: R) :
|
||||
AbstractService<E, R>(repository), ExternalService<E, S, U, R>
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired
|
|||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@Disabled
|
||||
@DataJpaTest
|
||||
class RecipeStepRepositoryTest @Autowired constructor(
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.doReturn
|
||||
import com.nhaarman.mockitokotlin2.reset
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsRestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundRestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.EntityDto
|
||||
|
@ -11,7 +8,6 @@ import dev.fyloz.trial.colorrecipesexplorer.model.Model
|
|||
import dev.fyloz.trial.colorrecipesexplorer.model.NamedModel
|
||||
import dev.fyloz.trial.colorrecipesexplorer.repository.NamedJpaRepository
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
@ -19,6 +15,7 @@ import java.util.*
|
|||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import dev.fyloz.trial.colorrecipesexplorer.service.AbstractServiceTest as AbstractServiceTest1
|
||||
|
||||
abstract class AbstractServiceTest<E, S : Service<E, *>, R : JpaRepository<E, *>> {
|
||||
protected abstract val repository: R
|
||||
|
@ -93,7 +90,7 @@ abstract class AbstractServiceTest<E, S : Service<E, *>, R : JpaRepository<E, *>
|
|||
}
|
||||
|
||||
abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : JpaRepository<E, Long>> :
|
||||
AbstractServiceTest<E, S, R>() {
|
||||
AbstractServiceTest1<E, S, R>() {
|
||||
|
||||
// existsById()
|
||||
|
||||
|
@ -279,10 +276,15 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
|
|||
}
|
||||
}
|
||||
|
||||
interface ExternalModelServiceTest {
|
||||
fun `save(dto) calls and returns save() with the created entity`()
|
||||
fun `update(dto) calls and returns update() with the created entity`()
|
||||
}
|
||||
|
||||
// ==== IMPLEMENTATIONS FOR EXTERNAL SERVICES ====
|
||||
// Lots of code duplication but I don't have a better solution for now
|
||||
abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U : EntityDto<E>, S : ExternalModelService<E, N, U, *>, R : JpaRepository<E, Long>> :
|
||||
AbstractModelServiceTest<E, S, R>() {
|
||||
AbstractModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
||||
protected abstract val entitySaveDto: N
|
||||
protected abstract val entityUpdateDto: U
|
||||
|
||||
|
@ -292,21 +294,12 @@ abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U :
|
|||
super.afterEach()
|
||||
}
|
||||
|
||||
// save()
|
||||
|
||||
@Test
|
||||
open fun `save(dto) calls and returns save() with the created entity`() =
|
||||
saveDtoTest(entity, entitySaveDto, service)
|
||||
|
||||
// update()
|
||||
|
||||
@Test
|
||||
open fun `update(dto) calls and returns update() with the created entity`() =
|
||||
updateDtoTest(entity, entityUpdateDto, service)
|
||||
override fun `save(dto) calls and returns save() with the created entity`() =
|
||||
withBaseSaveDtoTest(entity, entitySaveDto, service)
|
||||
}
|
||||
|
||||
abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityDto<E>, U : EntityDto<E>, S : ExternalNamedModelService<E, N, U, *>, R : NamedJpaRepository<E>> :
|
||||
AbstractNamedModelServiceTest<E, S, R>() {
|
||||
AbstractNamedModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
||||
protected abstract val entitySaveDto: N
|
||||
protected abstract val entityUpdateDto: U
|
||||
|
||||
|
@ -316,20 +309,16 @@ abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityD
|
|||
super.afterEach()
|
||||
}
|
||||
|
||||
// save()
|
||||
|
||||
@Test
|
||||
open fun `save(dto) calls and returns save() with the created entity`() =
|
||||
saveDtoTest(entity, entitySaveDto, service)
|
||||
|
||||
// update()
|
||||
|
||||
@Test
|
||||
open fun `update(dto) calls and returns update() with the created entity`() =
|
||||
updateDtoTest(entity, entityUpdateDto, service)
|
||||
override fun `save(dto) calls and returns save() with the created entity`() =
|
||||
withBaseSaveDtoTest(entity, entitySaveDto, service)
|
||||
}
|
||||
|
||||
fun <E, N : EntityDto<E>> saveDtoTest(entity: E, entitySaveDto: N, service: ExternalService<E, N, *, *>) {
|
||||
fun <E, N : EntityDto<E>> withBaseSaveDtoTest(
|
||||
entity: E,
|
||||
entitySaveDto: N,
|
||||
service: ExternalService<E, N, *, *>,
|
||||
op: () -> Unit = {}
|
||||
) {
|
||||
doReturn(entity).whenever(service).save(entity)
|
||||
doReturn(entity).whenever(entitySaveDto).toEntity()
|
||||
|
||||
|
@ -337,21 +326,26 @@ fun <E, N : EntityDto<E>> saveDtoTest(entity: E, entitySaveDto: N, service: Exte
|
|||
|
||||
verify(service).save(entity)
|
||||
assertEquals(entity, found)
|
||||
|
||||
op()
|
||||
}
|
||||
|
||||
fun <E : Model, U : EntityDto<E>> updateDtoTest(
|
||||
fun <E : Model, U : EntityDto<E>> withBaseUpdateDtoTest(
|
||||
entity: E,
|
||||
entityUpdateDto: U,
|
||||
service: ExternalModelService<E, *, U, *>
|
||||
service: ExternalModelService<E, *, U, *>,
|
||||
updateMockMatcher: E,
|
||||
op: () -> Unit = {}
|
||||
) {
|
||||
// doReturn(entity).whenever(service).update(entity)
|
||||
// doReturn(entity).whenever(entityUpdateDto).toEntity()
|
||||
// doReturn(entity).whenever(service).getById(entity.id!!)
|
||||
// doReturn(true).whenever(service).existsById(entity.id!!)
|
||||
//
|
||||
// val found = service.update(entityUpdateDto)
|
||||
//
|
||||
// verify(service).update(entity)
|
||||
// assertEquals(entity, found)
|
||||
assertTrue(true, "Disabled because the wrong methods are mocked for some reason")
|
||||
doAnswer { it.arguments[0] }.whenever(service).update(updateMockMatcher)
|
||||
doReturn(entity).whenever(entityUpdateDto).toEntity()
|
||||
doReturn(entity).whenever(service).getById(entity.id!!)
|
||||
doReturn(true).whenever(service).existsById(entity.id!!)
|
||||
|
||||
val found = service.update(entityUpdateDto)
|
||||
|
||||
verify(service).update(updateMockMatcher)
|
||||
assertEquals(entity, found)
|
||||
|
||||
op()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.SecurityConfigurationProperties
|
||||
import dev.fyloz.trial.colorrecipesexplorer.config.defaultGroupCookieName
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityAlreadyExistsRestException
|
||||
import dev.fyloz.trial.colorrecipesexplorer.exception.model.EntityNotFoundRestException
|
||||
|
@ -169,6 +168,9 @@ class EmployeeServiceTest :
|
|||
|
||||
// update()
|
||||
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, any())
|
||||
|
||||
@Test
|
||||
fun `update() throws EntityAlreadyExistsRestException when a different employee with the given first name and last name exists`() {
|
||||
whenever(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(
|
||||
|
@ -392,6 +394,11 @@ class EmployeeGroupServiceTest :
|
|||
verify(service, times(0)).update(group)
|
||||
verify(employeeService, times(0)).update(employee)
|
||||
}
|
||||
|
||||
// update()
|
||||
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, any())
|
||||
}
|
||||
|
||||
class EmployeeUserDetailsServiceTest {
|
||||
|
|
|
@ -6,6 +6,7 @@ import dev.fyloz.trial.colorrecipesexplorer.repository.CompanyRepository
|
|||
import dev.fyloz.trial.colorrecipesexplorer.service.model.RecipeJavaService
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
@ -29,6 +30,7 @@ class CompanyServiceTest :
|
|||
|
||||
// isLinkedToRecipes
|
||||
|
||||
@Test
|
||||
fun `isLinkedToRecipes() returns true when a given company is linked to one or more recipes`() {
|
||||
whenever(recipeService.existsByCompany(entity)).doReturn(true)
|
||||
|
||||
|
@ -37,6 +39,7 @@ class CompanyServiceTest :
|
|||
assertTrue(found)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isLinkedToRecipes() returns false when a given company is not linked to any recipe`() {
|
||||
whenever(recipeService.existsByCompany(entity)).doReturn(false)
|
||||
|
||||
|
@ -44,4 +47,9 @@ class CompanyServiceTest :
|
|||
|
||||
assertFalse(found)
|
||||
}
|
||||
|
||||
// update()
|
||||
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, any())
|
||||
}
|
||||
|
|
|
@ -175,21 +175,21 @@ class MaterialServiceTest :
|
|||
assertFalse(found contains anotherMixTypeMaterial)
|
||||
}
|
||||
|
||||
// @Nested
|
||||
// inner class UpdateDto {
|
||||
// @Test
|
||||
// fun `calls simdutService_update() with the updated entity`() {
|
||||
// val mockSimdutFile = MockMultipartFile("simdut", byteArrayOf(0))
|
||||
// val materialUpdateDto = spy(materialUpdateDto(id = 0L, simdutFile = mockSimdutFile))
|
||||
//
|
||||
// doReturn(entity).whenever(service).update(entity)
|
||||
// doReturn(entity).whenever(materialUpdateDto).toEntity()
|
||||
//
|
||||
// service.update(materialUpdateDto)
|
||||
//
|
||||
// verify(simdutService).update(mockSimdutFile, entity)
|
||||
// }
|
||||
// }
|
||||
// update()
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() {
|
||||
val mockSimdutFile = MockMultipartFile("simdut", byteArrayOf(1, 2, 3, 4, 5))
|
||||
val materialUpdateDto = spy(materialUpdateDto(id = 0L, simdutFile = mockSimdutFile))
|
||||
|
||||
doReturn(entity).whenever(service).getById(materialUpdateDto.id)
|
||||
doReturn(entity).whenever(service).update(any<Material>())
|
||||
doReturn(entity).whenever(materialUpdateDto).toEntity()
|
||||
|
||||
service.update(materialUpdateDto)
|
||||
|
||||
verify(simdutService).update(eq(mockSimdutFile), any())
|
||||
}
|
||||
|
||||
/** Helper function to replace collections.in because the id is not considered in the equals function of Material while Thymeleaf is supported. */
|
||||
private infix fun Collection<Material>.contains(material: Material): Boolean =
|
||||
|
|
|
@ -109,6 +109,9 @@ class MaterialTypeServiceTest :
|
|||
|
||||
// update()
|
||||
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, 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)
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
package dev.fyloz.trial.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.doReturn
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.spy
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.material
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.mixMaterial
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.repository.MixMaterialRepository
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterialService, MixMaterialRepository>() {
|
||||
|
@ -19,11 +15,12 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
|
|||
override val service: MixMaterialService = spy(MixMaterialServiceImpl(repository, materialService))
|
||||
|
||||
private val material: Material = material(id = 0L)
|
||||
override val entity: MixMaterial = mixMaterial(id = 0L, material = material)
|
||||
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)
|
||||
|
||||
|
@ -32,6 +29,7 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
|
|||
assertTrue(found)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `existsByMaterial() returns false when no mix material with the given material exists`() {
|
||||
whenever(repository.existsByMaterial(material)).doReturn(false)
|
||||
|
||||
|
@ -39,4 +37,54 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
|
|||
|
||||
assertFalse(found)
|
||||
}
|
||||
|
||||
// createFromMap()
|
||||
|
||||
@Test
|
||||
fun `createFromMap() calls create() for each map entry`() {
|
||||
val mix = mix()
|
||||
val map = mapOf(
|
||||
1L to 1000f,
|
||||
2L to 2000f,
|
||||
5L to 5000f
|
||||
)
|
||||
val mixMaterials = map.map { mixMaterial(material = material(id = it.key), quantity = it.value) }
|
||||
|
||||
doAnswer { mixMaterials.first { mixMaterial -> mixMaterial.material.id == it.arguments[1] } }
|
||||
.whenever(service).create(eq(mix), any(), any())
|
||||
|
||||
val found = service.createFromMap(mix, map)
|
||||
|
||||
assertEquals(mixMaterials, found)
|
||||
}
|
||||
|
||||
// create()
|
||||
|
||||
@Test
|
||||
fun `create() creates a mix material with the given mix, material and quantity`() {
|
||||
val mix = mix()
|
||||
val material = material(id = 0L)
|
||||
val quantity = 1000f
|
||||
val mixMaterial = mixMaterial(mix = mix, material = material, quantity = quantity)
|
||||
|
||||
whenever(materialService.getById(material.id!!)).doReturn(material)
|
||||
|
||||
val found = service.create(mix, material.id!!, quantity)
|
||||
|
||||
assertEquals(mixMaterial, found)
|
||||
}
|
||||
|
||||
// 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(service).update(any())
|
||||
|
||||
val found = service.updateQuantity(entity, quantity)
|
||||
|
||||
assertEquals(found.quantity, quantity)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ package dev.fyloz.trial.colorrecipesexplorer.service
|
|||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.repository.MixRepository
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpdateDto, MixService, MixRepository>() {
|
||||
override val repository: MixRepository = mock()
|
||||
|
@ -73,6 +73,112 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
|||
assertEquals(mixWithMaterials, found)
|
||||
}
|
||||
|
||||
// update()
|
||||
|
||||
private fun mixUpdateDtoTest(
|
||||
scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
|
||||
sharedMixType: Boolean = false,
|
||||
op: MixUpdateDtoTestScope.() -> Unit
|
||||
) {
|
||||
with(scope) {
|
||||
doReturn(true).whenever(service).existsById(mix.id!!)
|
||||
doReturn(mix).whenever(service).getById(mix.id!!)
|
||||
doReturn(sharedMixType).whenever(service).mixTypeIsShared(mix.mixType)
|
||||
doAnswer { it.arguments[0] }.whenever(service).update(any<Mix>())
|
||||
|
||||
if (mixUpdateDto.materialTypeId != null) {
|
||||
whenever(materialTypeService.getById(materialType.id!!)).doReturn(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(service).getById(mixUpdateDto.id)
|
||||
doReturn(entity).whenever(service).update(entity)
|
||||
|
||||
val found = service.update(mixUpdateDto)
|
||||
|
||||
verify(service).update(entity)
|
||||
|
||||
assertEquals(entity, found)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update(dto) calls MixTypeService createForNameAndMaterialType() when mix type is shared`() {
|
||||
mixUpdateDtoMixTypeTest(sharedMixType = true) {
|
||||
whenever(mixTypeService.createForNameAndMaterialType(mixUpdateDto.name!!, materialType))
|
||||
.doReturn(newMixType)
|
||||
|
||||
val found = service.update(mixUpdateDto)
|
||||
|
||||
verify(mixTypeService).createForNameAndMaterialType(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 = service.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 {
|
||||
// Pairs exists, impairs don't
|
||||
val materials = listOf(
|
||||
material(id = 1L),
|
||||
material(id = 2L),
|
||||
material(id = 3L),
|
||||
material(id = 4L),
|
||||
material(id = 5L),
|
||||
material(id = 6L),
|
||||
)
|
||||
val toDelete = mixMaterial(material = material(id = 7L), quantity = 7000f)
|
||||
val mixMaterialsMap = mapOf(*materials.map { it.id!! to it.id!! * 1000f }.toTypedArray())
|
||||
val allMixMaterials: Collection<MixMaterial> = materials
|
||||
.map { mixMaterial(mix = mix, material = it, quantity = mixMaterialsMap[it.id]!!) }
|
||||
val existingMixMaterials = allMixMaterials.filter { it.material.id!! % 2 == 0L }.toMutableList()
|
||||
existingMixMaterials += toDelete
|
||||
|
||||
mix.mixMaterials = existingMixMaterials
|
||||
(mixUpdateDto.mixMaterials as MutableMap<Long, Float>).putAll(mixMaterialsMap)
|
||||
|
||||
doAnswer { allMixMaterials.first { mixMaterial -> mixMaterial.material.id == (it.arguments[0] as MixMaterial).material.id } }
|
||||
.whenever(mixMaterialService).updateQuantity(any(), any())
|
||||
doAnswer { allMixMaterials.first { mixMaterial -> mixMaterial.material.id == it.arguments[1] } }
|
||||
.whenever(mixMaterialService).create(eq(mix), any(), any())
|
||||
|
||||
val found: Mix = service.update(mixUpdateDto)
|
||||
|
||||
assertTrue { found.mixMaterials.containsAll(allMixMaterials) }
|
||||
|
||||
verify(mixMaterialService, times(3)).updateQuantity(argThat { material.id!! % 2 == 0L }, any())
|
||||
verify(mixMaterialService, times(3)).create(eq(mix), any(), any())
|
||||
verify(mixMaterialService).delete(toDelete)
|
||||
}
|
||||
}
|
||||
|
||||
// updateLocation()
|
||||
|
||||
@Test
|
||||
|
@ -86,3 +192,18 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
|||
verify(service).update(expected)
|
||||
}
|
||||
}
|
||||
|
||||
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 = mutableMapOf()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -6,7 +6,6 @@ import dev.fyloz.trial.colorrecipesexplorer.model.*
|
|||
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeRepository
|
||||
import dev.fyloz.trial.colorrecipesexplorer.service.files.FilesService
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.mock.web.MockMultipartFile
|
||||
|
@ -66,9 +65,14 @@ class RecipeServiceTest :
|
|||
@Test
|
||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
||||
whenever(companyService.getById(company.id!!)).doReturn(company)
|
||||
saveDtoTest(entity, entitySaveDto, service)
|
||||
withBaseSaveDtoTest(entity, entitySaveDto, service)
|
||||
}
|
||||
|
||||
// update()
|
||||
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, any())
|
||||
|
||||
// updatePublicData()
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue