#25 Migrate materials to new logic
continuous-integration/drone/push Build is passing Details

This commit is contained in:
FyloZ 2022-02-17 23:34:08 -05:00
parent cb355c9e0d
commit b598652594
Signed by: william
GPG Key ID: 835378AE9AF4AE97
24 changed files with 667 additions and 535 deletions

View File

@ -0,0 +1,14 @@
package dev.fyloz.colorrecipesexplorer
object Constants {
object ControllerPaths {
const val file = "/api/file"
const val material = "/api/material"
}
object FilePaths {
const val pdfs = "pdf"
const val simdut = "$pdfs/simdut"
}
}

View File

@ -5,6 +5,6 @@ import javax.validation.constraints.NotBlank
data class CompanyDto(
override val id: Long = 0L,
@NotBlank
@field:NotBlank
val name: String
) : EntityDto

View File

@ -0,0 +1,34 @@
package dev.fyloz.colorrecipesexplorer.dtos
import dev.fyloz.colorrecipesexplorer.model.MaterialType
import org.springframework.web.multipart.MultipartFile
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
data class MaterialDto(
override val id: Long = 0L,
val name: String,
val inventoryQuantity: Float,
val isMixType: Boolean,
val materialType: MaterialType,
val simdutUrl: String? = null
) : EntityDto
data class MaterialSaveDto(
override val id: Long = 0L,
@field:NotBlank
val name: String,
@field:Min(0)
val inventoryQuantity: Float,
val materialTypeId: Long,
val simdutFile: MultipartFile?
) : EntityDto

View File

@ -23,7 +23,7 @@ class DefaultCompanyLogic(service: CompanyService) :
}
override fun deleteById(id: Long) {
if (service.recipesDependsOnCompanyById(id)) {
if (service.isUsedByRecipe(id)) {
throw cannotDeleteException("Cannot delete the company with the id '$id' because one or more recipes depends on it")
}

View File

@ -1,6 +1,7 @@
package dev.fyloz.colorrecipesexplorer.logic
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.utils.mapMayThrow
@ -89,7 +90,7 @@ class DefaultInventoryLogic(
}
}
class NotEnoughInventoryException(quantity: Float, material: Material) :
class NotEnoughInventoryException(quantity: Float, material: MaterialDto) :
RestException(
"notenoughinventory",
"Not enough inventory",

View File

@ -1,145 +1,120 @@
package dev.fyloz.colorrecipesexplorer.logic
import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH
import io.jsonwebtoken.lang.Assert
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import dev.fyloz.colorrecipesexplorer.model.Material
import dev.fyloz.colorrecipesexplorer.service.MaterialService
interface MaterialLogic :
ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository> {
/** Checks if a material with the given [materialType] exists. */
fun existsByMaterialType(materialType: MaterialType): Boolean
/** Checks if the given [material] has a SIMDUT file. */
fun hasSimdut(material: Material): Boolean
interface MaterialLogic : Logic<MaterialDto, MaterialService> {
/** Checks if a material with the given [name] exists. */
fun existsByName(name: String): Boolean
/** Gets all materials that are not a mix type. */
fun getAllNotMixType(): Collection<MaterialOutputDto>
fun getAllNotMixType(): Collection<MaterialDto>
/** Gets all materials available for the creation of a mix for the recipe with the given [recipeId], including normal materials and materials from [MixType]s included in the said recipe. */
fun getAllForMixCreation(recipeId: Long): Collection<MaterialOutputDto>
/**
* Gets all materials available for the creation of a mix for the recipe with the given [recipeId],
* including normal materials and materials from mix types included in the said recipe.
*/
fun getAllForMixCreation(recipeId: Long): Collection<MaterialDto>
/** Gets all materials available for updating the mix with the given [mixId], including normal materials and materials from [MixType]s included in the mix recipe, excluding the material of the [MixType] of the said mix. */
fun getAllForMixUpdate(mixId: Long): Collection<MaterialOutputDto>
/**
* Gets all materials available for updating the mix with the given [mixId],
* including normal materials and materials from mix types included in the mix recipe
* and excluding the material of the mix type of the said mix.
*/
fun getAllForMixUpdate(mixId: Long): Collection<MaterialDto>
/** Saves the given [dto]. */
fun save(dto: MaterialSaveDto): MaterialDto
/** Updates the given [dto]. */
fun update(dto: MaterialSaveDto): MaterialDto
/** Updates the quantity of the given [material] with the given [factor] and returns the updated quantity. */
fun updateQuantity(material: Material, factor: Float): Float
fun updateQuantity(material: MaterialDto, factor: Float): Float
}
@Service
@RequireDatabase
@LogicComponent
class DefaultMaterialLogic(
materialRepository: MaterialRepository,
service: MaterialService,
val recipeLogic: RecipeLogic,
val mixLogic: MixLogic,
@Lazy val materialTypeLogic: MaterialTypeLogic,
val fileService: WriteableFileLogic,
val configService: ConfigurationLogic
) :
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository>(
materialRepository
),
MaterialLogic {
override fun idNotFoundException(id: Long) = materialIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = materialIdAlreadyExistsException(id)
override fun nameNotFoundException(name: String) = materialNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = materialNameAlreadyExistsException(name)
val materialTypeLogic: MaterialTypeLogic,
val fileLogic: WriteableFileLogic
) : BaseLogic<MaterialDto, MaterialService>(service, Material::class.simpleName!!), MaterialLogic {
override fun existsByName(name: String) = service.existsByName(name, null)
override fun getAllNotMixType() = service.getAllNotMixType()
override fun Material.toOutput(): MaterialOutputDto =
MaterialOutputDto(
id = this.id!!,
name = this.name,
inventoryQuantity = this.inventoryQuantity,
isMixType = this.isMixType,
materialType = this.materialType!!,
simdutUrl = if (fileService.exists(this.simdutFilePath))
"${configService.getContent(ConfigurationType.INSTANCE_URL)}$FILE_CONTROLLER_PATH?path=${
URLEncoder.encode(
this.simdutFilePath,
StandardCharsets.UTF_8
)
}"
else null
)
override fun existsByMaterialType(materialType: MaterialType): Boolean =
repository.existsByMaterialType(materialType)
override fun hasSimdut(material: Material): Boolean = fileService.exists(material.simdutFilePath)
override fun getAllNotMixType(): Collection<MaterialOutputDto> = getAllForOutput().filter { !it.isMixType }
override fun save(entity: MaterialSaveDto): Material =
save(with(entity) {
material(
name = entity.name,
inventoryQuantity = entity.inventoryQuantity,
materialType = materialTypeLogic.getById(materialTypeId),
isMixType = false
)
}).apply {
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) fileService.write(
entity.simdutFile,
this.simdutFilePath,
false
)
}
override fun update(entity: MaterialUpdateDto): Material {
val persistedMaterial by lazy {
getById(entity.id).apply { assertPersistedMaterial(this) }
}
return update(with(entity) {
material(
id = id,
name = if (name != null && name.isNotBlank()) name else persistedMaterial.name,
inventoryQuantity = if (inventoryQuantity != null && inventoryQuantity != Float.MIN_VALUE) inventoryQuantity else persistedMaterial.inventoryQuantity,
isMixType = persistedMaterial.isMixType,
materialType = if (materialTypeId != null) materialTypeLogic.getById(materialTypeId) else persistedMaterial.materialType
)
}).apply {
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) fileService.write(
entity.simdutFile,
this.simdutFilePath,
true
)
}
}
override fun updateQuantity(material: Material, factor: Float) = with(material) {
val updatedQuantity = this.inventoryQuantity + factor
repository.updateInventoryQuantityById(this.id!!, updatedQuantity)
updatedQuantity
}
override fun getAllForMixCreation(recipeId: Long): Collection<MaterialOutputDto> {
override fun getAllForMixCreation(recipeId: Long): Collection<MaterialDto> {
val recipesMixTypes = recipeLogic.getById(recipeId).mixTypes
return getAllForOutput()
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
return getAll().filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
}
override fun getAllForMixUpdate(mixId: Long): Collection<MaterialOutputDto> {
override fun getAllForMixUpdate(mixId: Long): Collection<MaterialDto> {
val mix = mixLogic.getById(mixId)
val recipesMixTypes = mix.recipe.mixTypes
return getAllForOutput()
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
return getAll().filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
.filter { it.id != mix.mixType.material.id }
}
private fun assertPersistedMaterial(material: Material) {
Assert.notNull(material.name, "The persisted material with the id ${material.id} has a null name")
override fun save(dto: MaterialSaveDto) = save(saveDtoToDto(dto, false)).also { saveSimdutFile(dto, false) }
override fun save(dto: MaterialDto): MaterialDto {
throwIfNameAlreadyExists(dto.name)
return super.save(dto)
}
override fun delete(entity: Material) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
if (fileService.exists(entity.simdutFilePath)) fileService.delete(entity.simdutFilePath)
super.delete(entity)
override fun update(dto: MaterialSaveDto) = update(saveDtoToDto(dto, true)).also { saveSimdutFile(dto, true) }
override fun update(dto: MaterialDto): MaterialDto {
throwIfNameAlreadyExists(dto.name, dto.id)
return super.update(dto)
}
}
override fun updateQuantity(material: MaterialDto, factor: Float): Float {
val updatedQuantity = material.inventoryQuantity + factor
service.updateInventoryQuantityById(material.id, updatedQuantity)
return updatedQuantity
}
override fun deleteById(id: Long) {
if (service.isUsedByMixMaterialOrMixType(id)) {
throw cannotDeleteException("Cannot delete the material with the id '$id' because mix types and/or recipes depends on it")
}
val material = getById(id)
val simdutPath = Material.getSimdutFilePath(material.name)
if (fileLogic.exists(simdutPath)) {
fileLogic.delete(simdutPath)
}
super.deleteById(id)
}
private fun saveDtoToDto(saveDto: MaterialSaveDto, updating: Boolean): MaterialDto {
val isMixType = !updating || getById(saveDto.id).isMixType
val materialType = materialTypeLogic.getById(saveDto.materialTypeId)
return MaterialDto(saveDto.id, saveDto.name, saveDto.inventoryQuantity, isMixType, materialType, null)
}
private fun saveSimdutFile(dto: MaterialSaveDto, updating: Boolean) {
val file = dto.simdutFile
if (file != null && !file.isEmpty) {
fileLogic.write(file, Material.getSimdutFilePath(dto.name), updating)
}
}
private fun throwIfNameAlreadyExists(name: String, id: Long? = null) {
if (service.existsByName(name, id)) {
throw alreadyExistsException(value = name)
}
}
}

View File

@ -11,9 +11,6 @@ interface MaterialTypeLogic :
/** Checks if a material type with the given [prefix] exists. */
fun existsByPrefix(prefix: String): Boolean
/** Checks if the given [materialType] is used by one or more materials. */
fun isUsedByMaterial(materialType: MaterialType): Boolean
/** Gets all system material types. */
fun getAllSystemTypes(): Collection<MaterialType>
@ -26,7 +23,7 @@ interface MaterialTypeLogic :
@Service
@RequireDatabase
class DefaultMaterialTypeLogic(repository: MaterialTypeRepository, private val materialLogic: MaterialLogic) :
class DefaultMaterialTypeLogic(repository: MaterialTypeRepository) :
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
repository
), MaterialTypeLogic {
@ -38,8 +35,6 @@ class DefaultMaterialTypeLogic(repository: MaterialTypeRepository, private val m
override fun MaterialType.toOutput() = this
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
materialLogic.existsByMaterialType(materialType)
override fun getAllSystemTypes(): Collection<MaterialType> = repository.findAllBySystemTypeIs(true)
override fun getAllNonSystemType(): Collection<MaterialType> = repository.findAllBySystemTypeIs(false)

View File

@ -44,7 +44,7 @@ class DefaultMixMaterialLogic(
override fun MixMaterial.toOutput() = MixMaterialOutputDto(
this.id!!,
with(materialLogic) { this@toOutput.material.toOutput() },
this.material,
this.quantity,
this.position
)
@ -55,7 +55,7 @@ class DefaultMixMaterialLogic(
override fun create(mixMaterial: MixMaterialDto): MixMaterial =
mixMaterial(
material = materialLogic.getById(mixMaterial.materialId),
material = material(materialLogic.getById(mixMaterial.materialId)),
quantity = mixMaterial.quantity,
position = mixMaterial.position
)

View File

@ -32,27 +32,27 @@ class DefaultMixTypeLogic(
mixTypeRepository: MixTypeRepository,
@Lazy val materialLogic: MaterialLogic
) :
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeLogic {
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeLogic {
override fun idNotFoundException(id: Long) = mixTypeIdNotFoundException(id)
override fun idAlreadyExistsException(id: Long) = mixTypeIdAlreadyExistsException(id)
override fun nameNotFoundException(name: String) = mixTypeNameNotFoundException(name)
override fun nameAlreadyExistsException(name: String) = mixTypeNameAlreadyExistsException(name)
override fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean =
repository.existsByNameAndMaterialType(name, materialType)
repository.existsByNameAndMaterialType(name, materialType)
override fun getByMaterial(material: Material): MixType =
repository.findByMaterial(material) ?: throw nameNotFoundException(material.name)
repository.findByMaterial(material) ?: throw nameNotFoundException(material.name)
override fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType =
repository.findByNameAndMaterialType(name, materialType)
?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType)
repository.findByNameAndMaterialType(name, materialType)
?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType)
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
if (existsByNameAndMaterialType(name, materialType))
getByNameAndMaterialType(name, materialType)
else
saveForNameAndMaterialType(name, materialType)
if (existsByNameAndMaterialType(name, materialType))
getByNameAndMaterialType(name, materialType)
else
saveForNameAndMaterialType(name, materialType)
override fun save(entity: MixType): MixType {
if (materialLogic.existsByName(entity.name))
@ -61,24 +61,20 @@ class DefaultMixTypeLogic(
}
override fun saveForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
save(
mixType(
name = name,
material = material(
name = name,
inventoryQuantity = Float.MIN_VALUE,
isMixType = true,
materialType = materialType
)
save(
mixType(
name = name,
material = material(
name = name,
inventoryQuantity = Float.MIN_VALUE,
isMixType = true,
materialType = materialType
)
)
)
)
override fun updateForNameAndMaterialType(mixType: MixType, name: String, materialType: MaterialType): MixType =
update(mixType.apply {
this.name = name
material.name = name
material.materialType = materialType
})
update(mixType.copy(material = mixType.material.copy(name = name, materialType = materialType)))
override fun delete(entity: MixType) {
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixTypeException(entity)

View File

@ -1,13 +1,12 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.colorrecipesexplorer.Constants
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import org.springframework.web.multipart.MultipartFile
import javax.persistence.*
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
const val SIMDUT_FILES_PATH = "pdf/simdut"
@ -19,59 +18,24 @@ data class Material(
override val id: Long?,
@Column(unique = true)
override var name: String,
val name: String,
@Column(name = "inventory_quantity")
var inventoryQuantity: Float,
val inventoryQuantity: Float,
@Column(name = "mix_type")
val isMixType: Boolean,
@ManyToOne
@JoinColumn(name = "material_type_id")
var materialType: MaterialType?
) : NamedModelEntity {
val simdutFilePath
@JsonIgnore
@Transient
get() = "$SIMDUT_FILES_PATH/$name.pdf"
val materialType: MaterialType?
) : ModelEntity {
companion object {
fun getSimdutFilePath(name: String) =
"${Constants.FilePaths.simdut}/$name.pdf"
}
}
open class MaterialSaveDto(
@field:NotBlank
val name: String,
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
val inventoryQuantity: Float,
val materialTypeId: Long,
val simdutFile: MultipartFile? = null
) : EntityDto<Material>
open class MaterialUpdateDto(
val id: Long,
@field:NotBlank
val name: String?,
@field:Min(0, message = VALIDATION_SIZE_GE_ZERO)
val inventoryQuantity: Float?,
val materialTypeId: Long?,
val simdutFile: MultipartFile? = null
) : EntityDto<Material>
data class MaterialOutputDto(
override val id: Long,
val name: String,
val inventoryQuantity: Float,
val isMixType: Boolean,
val materialType: MaterialType,
val simdutUrl: String?
) : ModelEntity
data class MaterialQuantityDto(
val material: Long,
@ -99,22 +63,15 @@ fun material(
?: material.name, material.inventoryQuantity, material.isMixType, material.materialType
)
fun materialSaveDto(
name: String = "name",
inventoryQuantity: Float = 0f,
materialTypeId: Long = 0L,
simdutFile: MultipartFile? = null,
op: MaterialSaveDto.() -> Unit = {}
) = MaterialSaveDto(name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
@Deprecated("Temporary DSL for transition")
fun material(
dto: MaterialDto
) = Material(dto.id, dto.name, dto.inventoryQuantity, dto.isMixType, dto.materialType)
fun materialUpdateDto(
id: Long = 0L,
name: String? = "name",
inventoryQuantity: Float? = 0f,
materialTypeId: Long? = 0L,
simdutFile: MultipartFile? = null,
op: MaterialUpdateDto.() -> Unit = {}
) = MaterialUpdateDto(id, name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
@Deprecated("Temporary DSL for transition")
fun materialDto(
entity: Material
) = MaterialDto(entity.id!!, entity.name, entity.inventoryQuantity, entity.isMixType, entity.materialType!!)
fun materialQuantityDto(
materialId: Long,

View File

@ -32,7 +32,7 @@ data class MixMaterialDto(
data class MixMaterialOutputDto(
val id: Long,
val material: MaterialOutputDto,
val material: Material, // TODO move to MaterialDto
val quantity: Float,
val position: Int
)

View File

@ -1,16 +1,19 @@
package dev.fyloz.colorrecipesexplorer.model
import com.fasterxml.jackson.annotation.JsonIgnore
import dev.fyloz.colorrecipesexplorer.Constants
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
import dev.fyloz.colorrecipesexplorer.model.account.Group
import dev.fyloz.colorrecipesexplorer.model.account.group
import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.LocalDate
import javax.persistence.*
import javax.validation.constraints.*
import javax.validation.constraints.Max
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Pattern
private const val VALIDATION_COLOR_PATTERN = "^#([0-9a-f]{6})$"
@ -67,7 +70,7 @@ data class Recipe(
groupsInformation.firstOrNull { it.group.id == groupId }
fun imageUrl(deploymentUrl: String, name: String) =
"$deploymentUrl$FILE_CONTROLLER_PATH?path=${
"$deploymentUrl${Constants.ControllerPaths.file}?path=${
URLEncoder.encode(
"${this.imagesDirectoryPath}/$name",
StandardCharsets.UTF_8

View File

@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository
@Repository
interface CompanyRepository : JpaRepository<Company, Long> {
/** Checks if a company with the given [name] and an id different from the given [id] exists. */
/** Checks if a company with the given [name] and a different [id] exists. */
fun existsByNameAndIdNot(name: String, id: Long): Boolean
/** Checks if a recipe depends on the company with the given [id]. */
@ -17,5 +17,5 @@ interface CompanyRepository : JpaRepository<Company, Long> {
from Recipe r where r.company.id = :id
"""
)
fun recipesDependsOnCompanyById(id: Long): Boolean
fun isUsedByRecipe(id: Long): Boolean
}

View File

@ -1,29 +1,33 @@
package dev.fyloz.colorrecipesexplorer.repository
import dev.fyloz.colorrecipesexplorer.model.Material
import dev.fyloz.colorrecipesexplorer.model.MaterialType
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
@Repository
interface MaterialRepository : NamedJpaRepository<Material> {
/** Checks if one or more materials have the given [materialType]. */
fun existsByMaterialType(materialType: MaterialType): Boolean
interface MaterialRepository : JpaRepository<Material, Long> {
/** Checks if a material with the given [name] and a different [id] exists. */
fun existsByNameAndIdNot(name: String, id: Long): Boolean
/** Gets all non mix type materials. */
fun getAllByIsMixTypeIsFalse(): Collection<Material>
/** Updates the [inventoryQuantity] of the [Material] with the given [id]. */
@Modifying
@Query("UPDATE Material m SET m.inventoryQuantity = :inventoryQuantity WHERE m.id = :id")
fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float)
/** Checks if a mix material or a mix type depends on the material with the given [id]. */
@Query(
"""
"""
select case when(count(mm.id) + count(mt.id) > 0) then false else true end
from Material m
left join MixMaterial mm on m.id = mm.material.id
left join MixType mt on m.id = mt.material.id
left join MixMaterial mm on mm.material.id = m.id
left join MixType mt on mt.material.id = m.id
where m.id = :id
"""
"""
)
fun canBeDeleted(id: Long): Boolean
fun isUsedByMixMaterialOrMixType(id: Long): Boolean
}

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.Constants
import dev.fyloz.colorrecipesexplorer.logic.config.ConfigurationLogic
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
@ -10,10 +11,8 @@ import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import java.net.URI
const val FILE_CONTROLLER_PATH = "/api/file"
@RestController
@RequestMapping(FILE_CONTROLLER_PATH)
@RequestMapping(Constants.ControllerPaths.file)
class FileController(
private val fileLogic: WriteableFileLogic,
private val configurationLogic: ConfigurationLogic
@ -44,6 +43,6 @@ class FileController(
private fun created(path: String): ResponseEntity<Void> =
ResponseEntity
.created(URI.create("${configurationLogic.get(ConfigurationType.INSTANCE_URL)}$FILE_CONTROLLER_PATH?path=$path"))
.created(URI.create("${configurationLogic.get(ConfigurationType.INSTANCE_URL)}${Constants.ControllerPaths.file}?path=$path"))
.build()
}

View File

@ -1,8 +1,10 @@
package dev.fyloz.colorrecipesexplorer.rest
import dev.fyloz.colorrecipesexplorer.Constants
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewCatalog
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
import dev.fyloz.colorrecipesexplorer.logic.MaterialLogic
import dev.fyloz.colorrecipesexplorer.model.*
import org.springframework.context.annotation.Profile
import org.springframework.http.MediaType
import org.springframework.security.access.prepost.PreAuthorize
@ -10,10 +12,8 @@ import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import javax.validation.Valid
private const val MATERIAL_CONTROLLER_PATH = "api/material"
@RestController
@RequestMapping(MATERIAL_CONTROLLER_PATH)
@RequestMapping(Constants.ControllerPaths.material)
@Profile("!emergency")
@PreAuthorizeViewCatalog
class MaterialController(
@ -21,7 +21,7 @@ class MaterialController(
) {
@GetMapping
fun getAll() =
ok(materialLogic.getAllForOutput())
ok(materialLogic.getAll())
@GetMapping("notmixtype")
fun getAllNotMixType() =
@ -29,37 +29,20 @@ class MaterialController(
@GetMapping("{id}")
fun getById(@PathVariable id: Long) =
ok(materialLogic.getByIdForOutput(id))
ok(materialLogic.getById(id))
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
fun save(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
created<MaterialOutputDto>(MATERIAL_CONTROLLER_PATH) {
with(materialLogic) {
save(
materialSaveDto(
name = material.name,
inventoryQuantity = material.inventoryQuantity,
materialTypeId = material.materialTypeId,
simdutFile = simdutFile
)
).toOutput()
}
created<MaterialDto>(Constants.ControllerPaths.material) {
materialLogic.save(material.copy(simdutFile = simdutFile))
}
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
fun update(@Valid material: MaterialUpdateDto, simdutFile: MultipartFile?) =
fun update(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
noContent {
materialLogic.update(
materialUpdateDto(
id = material.id,
name = material.name,
inventoryQuantity = material.inventoryQuantity,
materialTypeId = material.materialTypeId,
simdutFile = simdutFile
)
)
materialLogic.update(material.copy(simdutFile = simdutFile))
}
@DeleteMapping("{id}")

View File

@ -10,14 +10,14 @@ interface CompanyService : Service<CompanyDto, Company, CompanyRepository> {
fun existsByName(name: String, id: Long?): Boolean
/** Checks if a recipe depends on the company with the given [id]. */
fun recipesDependsOnCompanyById(id: Long): Boolean
fun isUsedByRecipe(id: Long): Boolean
}
@ServiceComponent
class DefaultCompanyService(repository: CompanyRepository) :
BaseService<CompanyDto, Company, CompanyRepository>(repository), CompanyService {
override fun existsByName(name: String, id: Long?) = repository.existsByNameAndIdNot(name, id ?: 0)
override fun recipesDependsOnCompanyById(id: Long) = repository.recipesDependsOnCompanyById(id)
override fun isUsedByRecipe(id: Long) = repository.isUsedByRecipe(id)
override fun toDto(entity: Company) =
CompanyDto(entity.id!!, entity.name)

View File

@ -0,0 +1,58 @@
package dev.fyloz.colorrecipesexplorer.service
import dev.fyloz.colorrecipesexplorer.Constants
import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
import dev.fyloz.colorrecipesexplorer.logic.files.FileLogic
import dev.fyloz.colorrecipesexplorer.model.Material
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
import org.springframework.beans.factory.annotation.Qualifier
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
interface MaterialService : Service<MaterialDto, Material, MaterialRepository> {
/** Checks if a material with the given [name] and a different [id] exists. */
fun existsByName(name: String, id: Long?): Boolean
/** Gets all non mix type materials. */
fun getAllNotMixType(): Collection<MaterialDto>
/** Updates the [inventoryQuantity] of the [Material] with the given [id]. */
fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float)
/** Checks if a mix material or a mix type depends on the material with the given [id]. */
fun isUsedByMixMaterialOrMixType(id: Long): Boolean
}
@ServiceComponent
class DefaultMaterialService(repository: MaterialRepository, @Qualifier("defaultFileLogic") val fileLogic: FileLogic) :
BaseService<MaterialDto, Material, MaterialRepository>(repository), MaterialService {
override fun existsByName(name: String, id: Long?) = repository.existsByNameAndIdNot(name, id ?: 0)
override fun getAllNotMixType() = repository.getAllByIsMixTypeIsFalse().map(this::toDto)
override fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float) = repository.updateInventoryQuantityById(id, inventoryQuantity)
override fun isUsedByMixMaterialOrMixType(id: Long) = repository.isUsedByMixMaterialOrMixType(id)
override fun toDto(entity: Material) =
MaterialDto(
entity.id!!,
entity.name,
entity.inventoryQuantity,
entity.isMixType,
entity.materialType!!,
getSimdutUrl(entity)
)
override fun toEntity(dto: MaterialDto) =
Material(dto.id, dto.name, dto.inventoryQuantity, dto.isMixType, dto.materialType)
private fun getSimdutUrl(material: Material): String? {
val filePath = "${Constants.FilePaths.simdut}/${material.name}.pdf"
if (!fileLogic.exists(filePath)) {
return null
}
val encodedPath = URLEncoder.encode(filePath, StandardCharsets.UTF_8)
return "${Constants.ControllerPaths.file}?path=$encodedPath"
}
}

View File

@ -46,7 +46,7 @@ class DefaultCompanyLogicTest {
@Test
fun deleteById_recipesDependsOnCompany_throwsCannotDeleteException() {
// Arrange
every { companyServiceMock.recipesDependsOnCompanyById(company.id) } returns true
every { companyServiceMock.isUsedByRecipe(company.id) } returns true
// Act
// Assert

View File

@ -0,0 +1,382 @@
package dev.fyloz.colorrecipesexplorer.logic
import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto
import dev.fyloz.colorrecipesexplorer.dtos.MaterialSaveDto
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.service.MaterialService
import io.mockk.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.mock.web.MockMultipartFile
import org.springframework.web.multipart.MultipartFile
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class DefaultMaterialLogicTest {
private val materialServiceMock = mockk<MaterialService>()
private val recipeLogicMock = mockk<RecipeLogic>()
private val mixLogicMock = mockk<MixLogic>()
private val materialTypeLogicMock = mockk<MaterialTypeLogic>()
private val fileLogicMock = mockk<WriteableFileLogic>()
private val materialLogic = spyk(
DefaultMaterialLogic(
materialServiceMock, recipeLogicMock, mixLogicMock, materialTypeLogicMock, fileLogicMock
)
)
private val materialType = MaterialType(
1L, "Unit test material type", "UNT", usePercentages = false, systemType = false
) // TODO move to DTO
private val material = MaterialDto(1L, "Unit test material", 1000f, false, materialType)
private val materialMixType = material.copy(id = 2L, isMixType = true)
private val materialMixType2 = material.copy(id = 3L, isMixType = true)
private val company = Company(1L, "Unit test company")
private val recipe = Recipe(
1L,
"Unit test recipe",
"Unit test recipe",
"#FFFFFF",
0,
123,
null,
"A remark",
company,
mutableListOf(),
setOf()
)
private val mix = Mix(
1L, "location", recipe, mixType = MixType(1L, "Unit test mix type", material(materialMixType)), mutableSetOf()
)
private val mix2 = mix.copy(id = 2L, mixType = mix.mixType.copy(id = 2L, material = material(materialMixType2)))
private val simdutFileMock = MockMultipartFile(
"Unit test SIMDUT",
byteArrayOf(1, 2, 3, 4)
) // Put some content in the mock file so it is not ignored
private val materialSaveDto = MaterialSaveDto(1L, "Unit test material", 1000f, materialType.id!!, simdutFileMock)
init {
recipe.mixes.addAll(listOf(mix, mix2))
}
@AfterEach
internal fun afterEach() {
clearAllMocks()
}
@Test
fun existsByName_normalBehavior_returnsTrue() {
// Arrange
every { materialServiceMock.existsByName(any(), any()) } returns true
// Act
val exists = materialLogic.existsByName(material.name)
// Assert
assertTrue(exists)
}
@Test
fun existsByName_notFound_returnsFalse() {
// Arrange
every { materialServiceMock.existsByName(any(), any()) } returns false
// Act
val exists = materialLogic.existsByName(material.name)
// Assert
assertFalse(exists)
}
@Test
fun getAllNotMixType_normalBehavior_returnsMaterialsFromService() {
// Arrange
every { materialServiceMock.getAllNotMixType() } returns listOf(material)
// Act
val materials = materialLogic.getAllNotMixType()
// Assert
assertContains(materials, material)
}
@Test
fun getAllForMixCreation_normalBehavior_returnsNonMixTypeMaterials() {
// Arrange
every { materialLogic.getAll() } returns listOf(material, materialMixType2)
every { recipeLogicMock.getById(any()) } returns recipe
// Act
val materials = materialLogic.getAllForMixCreation(recipe.id!!)
// Assert
assertContains(materials, material)
}
@Test
fun getAllForMixCreation_normalBehavior_returnsRecipeMixTypesMaterials() {
// Arrange
every { materialLogic.getAll() } returns listOf(material, materialMixType2)
every { recipeLogicMock.getById(any()) } returns recipe
// Act
val materials = materialLogic.getAllForMixCreation(recipe.id!!)
// Assert
assertContains(materials, materialMixType2)
}
@Test
fun getAllForMixUpdate_normalBehavior_returnsNonMixTypeMaterials() {
// Arrange
every { materialLogic.getAll() } returns listOf(material, materialMixType, materialMixType2)
every { mixLogicMock.getById(any()) } returns mix
// Act
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
// Assert
assertContains(materials, material)
}
@Test
fun getAllForMixUpdate_normalBehavior_returnsRecipeMixTypesMaterials() {
// Arrange
every { materialLogic.getAll() } returns listOf(material, materialMixType, materialMixType2)
every { mixLogicMock.getById(any()) } returns mix
// Act
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
// Assert
assertContains(materials, materialMixType2)
}
@Test
fun getAllForMixUpdate_normalBehavior_excludesGivenMixTypeMaterial() {
// Arrange
every { materialLogic.getAll() } returns listOf(material, materialMixType, materialMixType2)
every { mixLogicMock.getById(any()) } returns mix
// Act
val materials = materialLogic.getAllForMixUpdate(mix.id!!)
// Assert
assertFalse { materialMixType in materials }
}
@Test
fun save_materialSaveDto_normalBehavior_callsSave() {
// Arrange
every { materialLogic.save(any<MaterialDto>()) } returns material
every { materialTypeLogicMock.getById(any()) } returns materialType
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
// Act
materialLogic.save(materialSaveDto)
// Assert
verify {
materialLogic.save(any<MaterialDto>())
}
}
@Test
fun save_materialSaveDto_normalBehavior_callsWriteInFileService() {
// Arrange
every { materialLogic.save(any<MaterialDto>()) } returns material
every { materialTypeLogicMock.getById(any()) } returns materialType
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
// Act
materialLogic.save(materialSaveDto)
// Assert
verify {
fileLogicMock.write(simdutFileMock, any(), false)
}
confirmVerified(fileLogicMock)
}
@Test
fun save_materialSaveDto_noSimdutFile_doesNotCallWriteInFileService() {
// Arrange
every { materialLogic.save(any<MaterialDto>()) } returns material
every { materialTypeLogicMock.getById(any()) } returns materialType
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
val saveDto = materialSaveDto.copy(simdutFile = null)
// Act
materialLogic.save(saveDto)
// Assert
verify(exactly = 0) {
fileLogicMock.write(simdutFileMock, any(), false)
}
confirmVerified(fileLogicMock)
}
@Test
fun save_nameExists_throwsNameAlreadyExists() {
// Arrange
every { materialServiceMock.existsByName(any(), any()) } returns true
// Act
// Assert
assertThrows<AlreadyExistsException> { materialLogic.save(material) }
}
@Test
fun update_saveDto_normalBehavior_callsUpdate() {
// Arrange
every { materialLogic.getById(any()) } returns material
every { materialLogic.update(any<MaterialDto>()) } returns material
every { materialTypeLogicMock.getById(any()) } returns materialType
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
// Act
materialLogic.update(materialSaveDto)
// Assert
verify {
materialLogic.update(any<MaterialDto>())
}
}
@Test
fun update_materialSaveDto_normalBehavior_callsWriteInFileService() {
// Arrange
every { materialLogic.getById(any()) } returns material
every { materialLogic.update(any<MaterialDto>()) } returns material
every { materialTypeLogicMock.getById(any()) } returns materialType
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
// Act
materialLogic.update(materialSaveDto)
// Assert
verify {
fileLogicMock.write(simdutFileMock, any(), true)
}
confirmVerified(fileLogicMock)
}
@Test
fun update_materialSaveDto_noSimdutFile_doesNotCallWriteInFileService() {
// Arrange
every { materialLogic.getById(any()) } returns material
every { materialLogic.update(any<MaterialDto>()) } returns material
every { materialTypeLogicMock.getById(any()) } returns materialType
every { fileLogicMock.write(any<MultipartFile>(), any(), any()) } just runs
val saveDto = materialSaveDto.copy(simdutFile = null)
// Act
materialLogic.update(saveDto)
// Assert
verify(exactly = 0) {
fileLogicMock.write(simdutFileMock, any(), true)
}
confirmVerified(fileLogicMock)
}
@Test
fun updateQuantity_normalBehavior_callsUpdateInventoryQuantityByIdInService() {
// Arrange
every { materialServiceMock.updateInventoryQuantityById(any(), any()) } just runs
val factor = 3f
// Act
materialLogic.updateQuantity(material, factor)
// Assert
verify {
materialServiceMock.updateInventoryQuantityById(material.id, material.inventoryQuantity + factor)
}
confirmVerified(materialServiceMock)
}
@Test
fun updateQuantity_normalBehavior_returnsUpdatedQuantity() {
// Arrange
every { materialServiceMock.updateInventoryQuantityById(any(), any()) } just runs
val factor = 3f
// Act
val updatedQuantity = materialLogic.updateQuantity(material, factor)
// Assert
assertEquals(material.inventoryQuantity + factor, updatedQuantity)
}
@Test
fun deleteById_normalBehavior_callsDeleteInFileLogic() {
// Arrange
every { materialLogic.getById(any()) } returns material
every { materialServiceMock.isUsedByMixMaterialOrMixType(any()) } returns false
every { materialServiceMock.deleteById(any()) } just runs
every { fileLogicMock.exists(any()) } returns true
every { fileLogicMock.delete(any()) } just runs
val simdutPath = Material.getSimdutFilePath(material.name)
// Act
materialLogic.deleteById(material.id)
// Assert
verify {
fileLogicMock.exists(simdutPath)
fileLogicMock.delete(simdutPath)
}
confirmVerified(fileLogicMock)
}
@Test
fun deleteById_simdutFileNotExists_doesNotCallDeleteInFileLogic() {
// Arrange
every { materialLogic.getById(any()) } returns material
every { materialServiceMock.isUsedByMixMaterialOrMixType(any()) } returns false
every { materialServiceMock.deleteById(any()) } just runs
every { fileLogicMock.exists(any()) } returns false
every { fileLogicMock.delete(any()) } just runs
val simdutPath = Material.getSimdutFilePath(material.name)
// Act
materialLogic.deleteById(material.id)
// Assert
verify {
fileLogicMock.exists(simdutPath)
}
verify(exactly = 0) {
fileLogicMock.delete(simdutPath)
}
confirmVerified(fileLogicMock)
}
@Test
fun deleteById_usedByMixMaterialOrMixType_throwsCannotDeleteException() {
// Arrange
every { materialLogic.getById(any()) } returns material
every { materialServiceMock.isUsedByMixMaterialOrMixType(any()) } returns true
every { materialServiceMock.deleteById(any()) } just runs
every { fileLogicMock.exists(any()) } returns false
every { fileLogicMock.delete(any()) } just runs
// Act
// Assert
assertThrows<CannotDeleteException> { materialLogic.deleteById(material.id) }
}
}

View File

@ -175,7 +175,7 @@ class InventoryLogicTest {
) {
val material = material(id = materialQuantity.material, inventoryQuantity = stored)
whenever(materialLogic.getById(material.id!!)).doReturn(material)
whenever(materialLogic.getById(material.id!!)).doReturn(materialDto(material))
materialQuantity.test(stored)
}

View File

@ -1,249 +0,0 @@
package dev.fyloz.colorrecipesexplorer.logic
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.logic.files.WriteableFileLogic
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
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 org.springframework.mock.web.MockMultipartFile
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MaterialLogicTest :
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialLogic, MaterialRepository>() {
override val repository: MaterialRepository = mock()
private val recipeService: RecipeLogic = mock()
private val mixService: MixLogic = mock()
private val materialTypeService: MaterialTypeLogic = mock()
private val fileService: WriteableFileLogic = mock()
override val logic: MaterialLogic =
spy(DefaultMaterialLogic(repository, recipeService, mixService, materialTypeService, fileService, mock()))
override val entity: Material = material(id = 0L, name = "material")
private val entityOutput = materialOutputDto(entity)
override val anotherEntity: Material = material(id = 1L, name = "another material")
override val entityWithEntityName: Material = material(id = 2L, name = "material")
override val entitySaveDto: MaterialSaveDto = spy(materialSaveDto())
override val entityUpdateDto: MaterialUpdateDto = spy(materialUpdateDto(id = 0L))
private val materialType = materialType()
@AfterEach
override fun afterEach() {
reset(recipeService, mixService, materialTypeService, fileService)
super.afterEach()
}
// existsByMaterialType
@Test
fun `existsByMaterialType() returns true when a material with the given material type exists in the repository`() {
whenever(repository.existsByMaterialType(materialType)).doReturn(true)
val found = logic.existsByMaterialType(materialType)
assertTrue(found)
}
@Test
fun `existsByMaterialType() returns false when no material with the given material type exists in the repository`() {
whenever(repository.existsByMaterialType(materialType)).doReturn(false)
val found = logic.existsByMaterialType(materialType)
assertFalse(found)
}
// hasSimdut()
@Test
fun `hasSimdut() returns false when simdutService_exists() returns false`() {
whenever(fileService.exists(any())).doReturn(false)
doReturn(entity).whenever(logic).getById(entity.id!!)
val found = logic.hasSimdut(entity)
assertFalse(found)
}
@Test
fun `hasSimdut() returns true when simdutService_exists() returns true`() {
whenever(fileService.exists(any())).doReturn(true)
doReturn(entity).whenever(logic).getById(entity.id!!)
val found = logic.hasSimdut(entity)
assertTrue(found)
}
// getAllNotMixType()
@Test
fun `getAllNotMixType() returns a list containing every material that are not a mix type`() {
val mixTypeMaterial = material(id = 1L, name = "mix type material", isMixType = true)
val mixTypeMaterialOutput = materialOutputDto(mixTypeMaterial)
val materialList = listOf(entity, mixTypeMaterial)
doReturn(materialList).whenever(logic).getAll()
val found = logic.getAllNotMixType()
assertTrue(found.contains(entityOutput))
assertFalse(found.contains(mixTypeMaterialOutput))
}
// save()
@Test
fun `save() throws AlreadyExistsException when a material with the given name exists in the repository`() {
doReturn(true).whenever(logic).existsByName(entity.name)
assertThrows<AlreadyExistsException> { logic.save(entity) }
.assertErrorCode("name")
}
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
withBaseSaveDtoTest(entity, entitySaveDto, logic, { any() })
}
@Test
fun `save(dto) calls simdutService_write() with the saved entity`() {
val mockMultipartFile = spy(MockMultipartFile("simdut", byteArrayOf()))
val materialSaveDto = spy(materialSaveDto(simdutFile = mockMultipartFile))
doReturn(false).whenever(mockMultipartFile).isEmpty
doReturn(entity).whenever(logic).save(any<Material>())
logic.save(materialSaveDto)
verify(fileService).write(mockMultipartFile, entity.simdutFilePath, false)
}
// update()
@Test
fun `update() throws AlreadyExistsException when another material with the updated name exists in the repository`() {
val material = material(id = 0L, name = "name")
val anotherMaterial = material(id = 1L, name = "name")
whenever(repository.findByName(material.name)).doReturn(anotherMaterial)
doReturn(entity).whenever(logic).getById(material.id!!)
assertThrows<AlreadyExistsException> { logic.update(material) }
.assertErrorCode("name")
}
@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(logic).getById(any())
doReturn(entity).whenever(logic).update(any<Material>())
doReturn(entity).whenever(materialUpdateDto).toEntity()
logic.update(materialUpdateDto)
verify(fileService).write(mockSimdutFile, entity.simdutFilePath, true)
}
// updateQuantity()
@Test
fun `updateQuantity() updates the quantity of the the given material in the repository`() {
val material = material(id = 0L, inventoryQuantity = 4321f)
val quantity = 1234f
val totalQuantity = material.inventoryQuantity + quantity
val found = logic.updateQuantity(material, quantity)
verify(repository).updateInventoryQuantityById(material.id!!, totalQuantity)
assertEquals(totalQuantity, found)
}
// getAllForMixCreation()
@Test
fun `getAllForMixCreation() returns all normal materials and all mix type materials for the given recipe`() {
val normalMaterial = material(id = 0L, isMixType = false)
val mixTypeMaterial = material(id = 1L, isMixType = true)
val anotherMixTypeMaterial = material(id = 2L, isMixType = true)
val materials = listOf(normalMaterial, mixTypeMaterial, anotherMixTypeMaterial)
val recipe =
recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial))))
whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
doReturn(materials).whenever(logic).getAll()
val found = logic.getAllForMixCreation(recipe.id!!)
assertTrue(materialOutputDto(normalMaterial) in found)
assertTrue(materialOutputDto(mixTypeMaterial) in found)
assertFalse(materialOutputDto(anotherMixTypeMaterial) in found)
}
// getAllForMixUpdate()
@Test
fun `getAllForMixUpdate() returns all normal materials and all mix type materials for the recipe of the given mix without the mix type of the said mix`() {
val normalMaterial = material(id = 0L, isMixType = false)
val mixTypeMaterial = material(id = 1L, isMixType = true)
val anotherMixTypeMaterial = material(id = 2L, isMixType = true)
val materials = listOf(normalMaterial, mixTypeMaterial, anotherMixTypeMaterial)
val recipe = recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(material = mixTypeMaterial))))
val mix = mix(id = 1L, recipe = recipe, mixType = mixType(material = anotherMixTypeMaterial))
recipe.mixes.add(mix)
whenever(mixService.getById(mix.id!!)).doReturn(mix)
doReturn(materials).whenever(logic).getAll()
val found = logic.getAllForMixUpdate(mix.id!!)
assertTrue(materialOutputDto(normalMaterial) in found)
assertTrue(materialOutputDto(mixTypeMaterial) in found)
assertFalse(materialOutputDto(anotherMixTypeMaterial) in found)
}
// delete()
override fun `delete() deletes in the repository`() {
whenCanBeDeleted {
super.`delete() deletes in the repository`()
}
}
// deleteById()
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`()
}
}
/** Utility property to check if the identifier of the given [Material] is even. */
private val Material.evenId: Boolean
get() = this.id!! % 2 == 0L
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
whenever(repository.canBeDeleted(id)).doReturn(true)
test()
}
private fun materialOutputDto(material: Material) = MaterialOutputDto(
id = material.id!!,
name = material.name,
inventoryQuantity = material.inventoryQuantity,
isMixType = material.isMixType,
materialType = material.materialType!!,
simdutUrl = null
)
}

View File

@ -20,7 +20,7 @@ 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, materialService))
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")
@ -56,26 +56,6 @@ class MaterialTypeLogicTest :
assertFalse(found)
}
// isUsedByMaterial()
@Test
fun `isUsedByMaterial() returns true when materialService_existsByMaterialType() returns true`() {
whenever(materialService.existsByMaterialType(entity)).doReturn(true)
val found = logic.isUsedByMaterial(entity)
assertTrue(found)
}
@Test
fun `isUsedByMaterial() returns false when materialService_existsByMaterialType() returns false`() {
whenever(materialService.existsByMaterialType(entity)).doReturn(false)
val found = logic.isUsedByMaterial(entity)
assertFalse(found)
}
// getAllSystemTypes()
@Test

View File

@ -78,7 +78,7 @@ class MixMaterialLogicTest : AbstractModelServiceTest<MixMaterial, MixMaterialLo
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 { material(id = it.arguments[0] as Long) }
whenever(materialService.getById(mixMaterialDto.materialId)).doAnswer { materialDto(material(id = it.arguments[0] as Long)) }
val found = logic.create(mixMaterialDto)