Merge branch 'inventory' into 'master'

Ajout d'un système d'inventaire

See merge request color-recipes-explorer/backend!16
This commit is contained in:
William Nolin 2021-03-19 19:59:08 +00:00
commit 77f3b113c2
39 changed files with 446 additions and 596 deletions

View File

@ -1,3 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.model.dto
data class InventoryDto(val mixId: Long, val materialIds: List<Long>, val quantities: List<Float>)

View File

@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.exception
import com.fasterxml.jackson.annotation.JsonProperty
import dev.fyloz.trial.colorrecipesexplorer.model.Material
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialQuantityDto
import org.springframework.context.annotation.Profile
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
@ -10,7 +11,6 @@ import org.springframework.validation.FieldError
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
@ -18,13 +18,13 @@ abstract class RestException(val exceptionMessage: String, val httpStatus: HttpS
RuntimeException(exceptionMessage) {
abstract fun buildBody(): RestExceptionBody
@Suppress("unused")
open inner class RestExceptionBody(
val status: Int = httpStatus.value(),
@JsonProperty("message") val message: String = exceptionMessage
)
}
@ResponseStatus(HttpStatus.CONFLICT)
class EntityAlreadyExistsException(val value: Any) :
RestException("An entity with the given identifier already exists", HttpStatus.CONFLICT) {
@Suppress("unused")
@ -33,7 +33,6 @@ class EntityAlreadyExistsException(val value: Any) :
}
}
@ResponseStatus(HttpStatus.NOT_FOUND)
class EntityNotFoundException(val value: Any) :
RestException("An entity could not be found with the given identifier", HttpStatus.NOT_FOUND) {
@Suppress("unused")
@ -42,7 +41,6 @@ class EntityNotFoundException(val value: Any) :
}
}
@ResponseStatus(HttpStatus.CONFLICT)
class CannotDeleteEntityException(val value: Long) :
RestException(
"The entity with the given identifier could not be deleted because it is required by other entities",
@ -54,18 +52,37 @@ class CannotDeleteEntityException(val value: Long) :
}
}
@ResponseStatus
class SimdutWriteException(val material: Material) :
RestException(
"Could not write the SIMDUT file to disk",
HttpStatus.INTERNAL_SERVER_ERROR
) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = RestExceptionBody()
}
class LowQuantityException(val materialQuantity: MaterialQuantityDto) :
RestException(
"There is not enough of the given material in the inventory",
HttpStatus.CONFLICT
) {
@Suppress("unused")
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val materialId = material.id
val material = materialQuantity.material
val quantity = materialQuantity.quantity
}
}
class LowQuantitiesException(val materialQuantities: Collection<MaterialQuantityDto>) :
RestException(
"There is not enough of one or more given materials in the inventory",
HttpStatus.CONFLICT
) {
@Suppress
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
val lowQuantities = materialQuantities
}
}
@ControllerAdvice
@Profile("rest")

View File

@ -64,11 +64,6 @@ data class Employee(
) : Model {
@JsonProperty("permissions")
fun getFlattenedPermissions(): Iterable<EmployeePermission> = getPermissions()
override fun equals(other: Any?): Boolean =
other is Employee && id == other.id && firstName == other.firstName && lastName == other.lastName
override fun hashCode(): Int = Objects.hash(id, firstName, lastName)
}
/** DTO for creating employees. Allows a [password] a [groupId]. */
@ -147,10 +142,7 @@ data class EmployeeGroup(
val employees: MutableSet<Employee> = mutableSetOf()
) : NamedModel {
@JsonProperty("employeeCount")
fun getEmployeeCount() = employees.size - 1 // Remove the default employee
override fun equals(other: Any?): Boolean = other is EmployeeGroup && id == other.id && name == other.name
override fun hashCode(): Int = Objects.hash(id, name)
fun getEmployeeCount() = employees.size - 1 // -1 removes the default employee
}
open class EmployeeGroupSaveDto(

View File

@ -18,10 +18,7 @@ data class Company(
@Column(unique = true)
override val name: String
) : NamedModel {
override fun equals(other: Any?): Boolean = other is Company && name == other.name
override fun hashCode(): Int = Objects.hash(name)
}
) : NamedModel
open class CompanySaveDto(

View File

@ -2,9 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.model
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrNotBlank
import dev.fyloz.trial.colorrecipesexplorer.model.validation.NullOrSize
import org.springframework.util.Assert
import org.springframework.web.multipart.MultipartFile
import java.util.*
import javax.persistence.*
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
@ -16,6 +14,12 @@ private const val MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE = "Une quantité est
private const val MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE = "La quantité doit être supérieure ou égale à 0"
private const val MATERIAL_TYPE_NULL_MESSAGE = "Un type de produit est requis"
private const val MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE = "Un produit est requis"
private const val MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE = "Une quantité est requises"
private const val MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE = "La quantité doit être supérieure ou égale à 0"
// === Entities ===
@Entity
@Table(name = "material")
data class Material(
@ -35,20 +39,7 @@ data class Material(
@ManyToOne
@JoinColumn(name = "material_type_id")
var materialType: MaterialType?
) : NamedModel {
constructor(name: String, inventoryQuantity: Float, isMixType: Boolean, materialType: MaterialType) : this(
null,
name,
inventoryQuantity,
isMixType,
materialType
)
constructor() : this(null, "", 0f, false, null)
override fun equals(other: Any?): Boolean = other is Material && name == other.name
override fun hashCode(): Int = Objects.hash(name)
}
) : NamedModel
open class MaterialSaveDto(
@field:NotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
@ -85,27 +76,19 @@ open class MaterialUpdateDto(
id, name ?: "", inventoryQuantity
?: Float.MIN_VALUE, false, materialType
)
fun toMaterial() = toEntity()
}
data class InventoryMaterial(
@NotNull val id: Long,
@NotNull val name: String,
@NotNull val inventoryQuantity: Float,
@NotNull val materialTypeName: String,
@NotNull val lowQuantity: Boolean = false
data class MaterialQuantityDto(
@field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE)
val material: Long,
@field:NotNull(message = MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE)
@field:Min(value = 0, message = MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE)
val quantity: Float
)
@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")
Assert.notNull(materialType, "Cannot convert a material without a material type to an inventory material")
return InventoryMaterial(id!!, name, inventoryQuantity, materialType!!.name, inventoryQuantity < minimumQuantity)
}
// === DSL ===
// ==== DSL ====
fun material(
id: Long? = null,
name: String = "name",
@ -140,3 +123,9 @@ fun materialUpdateDto(
simdutFile: MultipartFile? = null,
op: MaterialUpdateDto.() -> Unit = {}
) = MaterialUpdateDto(id, name, inventoryQuantity, materialType, simdutFile).apply(op)
fun materialQuantityDto(
materialId: Long,
quantity: Float,
op: MaterialQuantityDto.() -> Unit = {}
) = MaterialQuantityDto(materialId, quantity).apply(op)

View File

@ -36,10 +36,7 @@ data class MaterialType(
@Column(name = "system_type")
@ColumnDefault("false")
val systemType: Boolean = false
) : NamedModel {
override fun equals(other: Any?): Boolean = other is MaterialType && name == other.name && prefix == other.prefix
override fun hashCode(): Int = Objects.hash(name, prefix)
}
) : NamedModel
open class MaterialTypeSaveDto(
@field:NotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)

View File

@ -34,12 +34,7 @@ data class Mix(
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "mix")
var mixMaterials: MutableCollection<MixMaterial>,
) : Model {
constructor(recipe: Recipe, mixType: MixType) : this(null, null, recipe, mixType, mutableListOf())
override fun equals(other: Any?): Boolean = other is Mix && recipe == other.recipe && mixType == other.mixType
override fun hashCode(): Int = Objects.hash(recipe, mixType)
}
) : Model
open class MixSaveDto(
@field:NotBlank(message = MIX_NAME_NULL_MESSAGE)

View File

@ -21,14 +21,7 @@ data class MixMaterial(
val material: Material,
var quantity: Float
) : Model {
constructor(mix: Mix, material: Material, quantity: Float) : this(null, mix, material, quantity)
override fun equals(other: Any?): Boolean =
other is MixMaterial && other.mix == mix && other.material == material && other.quantity == quantity
override fun hashCode(): Int = Objects.hash(mix, material, quantity)
}
) : Model
// ==== DSL ====
fun mixMaterial(

View File

@ -18,17 +18,7 @@ data class MixType(
@OneToOne(cascade = [CascadeType.ALL])
@JoinColumn(name = "material_id")
var material: Material
) : NamedModel {
constructor(name: String, material: Material) : this(null, name, material)
constructor(name: String, materialType: MaterialType) : this(
null,
name,
material(name = name, inventoryQuantity = Float.MIN_VALUE, isMixType = true, materialType = materialType)
)
override fun equals(other: Any?): Boolean = other is MixType && other.name == name && other.material == material
override fun hashCode(): Int = Objects.hash(name, material)
}
) : NamedModel
// ==== DSL ====
fun mixType(
@ -42,7 +32,8 @@ fun mixType(
name: String = "name",
materialType: MaterialType = materialType(),
op: MixType.() -> Unit = {}
) = MixType(
) = mixType(
id = null,
name,
material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType)
material = material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType)
).apply(op)

View File

@ -57,61 +57,10 @@ data class Recipe(
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
var steps: List<RecipeStep>
) : Model {
constructor() : this(
null,
"name",
"description",
"#ffffff",
0,
0,
null,
"remark",
"note",
company(),
mutableListOf(),
mutableListOf()
)
constructor(
id: Long,
name: String,
company: Company,
description: String,
color: String,
gloss: Byte,
sample: Int,
approbationDate: LocalDate?,
remark: String,
note: String
) : this(
id,
name,
description,
color,
gloss,
sample,
approbationDate,
remark,
note,
company,
mutableListOf(),
mutableListOf()
)
val mixesSortedById: Collection<Mix>
@JsonIgnore
get() = mixes.sortedBy { it.id }
/** The mix types contained in this recipe. */
val mixTypes: Collection<MixType>
@JsonIgnore
get() = mixes.map { it.mixType }
/** Checks if the recipe contains the given [mixType]. */
fun containsMixType(mixType: MixType) = mixTypes.contains(mixType)
override fun equals(other: Any?): Boolean = other is Recipe && other.name == name && other.company == company
override fun hashCode(): Int = Objects.hash(name, company)
}
data class RecipePublicDataDto(

View File

@ -17,14 +17,7 @@ data class RecipeStep(
val recipe: Recipe?,
val message: String
) : Model {
constructor(recipe: Recipe?, message: String) : this(null, recipe, message)
override fun equals(other: Any?): Boolean =
other is RecipeStep && other.recipe == recipe && other.message == message
override fun hashCode(): Int = Objects.hash(recipe, message)
}
) : Model
// ==== DSL ====

View File

@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.Material
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialType
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
@ -10,8 +11,10 @@ interface MaterialRepository : NamedJpaRepository<Material> {
/** Checks if one or more materials have the given [materialType]. */
fun existsByMaterialType(materialType: MaterialType): Boolean
/** Gets all the materials with the given [materialType]. */
fun findAllByMaterialType(materialType: MaterialType): 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)
@Query(
"""

View File

@ -1,11 +0,0 @@
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
//import org.springframework.web.bind.annotation.RestController
//
//@RestController
//class InventoryController(val inventoryService: InventoryService) {
// fun getAllMaterials(): ResponseEntity<Collection<InventoryMaterial>> = ResponseEntity.ok(inventoryService.getAllMaterials())
//}

View File

@ -1,6 +1,7 @@
package dev.fyloz.trial.colorrecipesexplorer.rest
import dev.fyloz.trial.colorrecipesexplorer.model.*
import dev.fyloz.trial.colorrecipesexplorer.service.InventoryService
import dev.fyloz.trial.colorrecipesexplorer.service.MaterialService
import org.springframework.context.annotation.Profile
import org.springframework.http.HttpHeaders
@ -12,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile
import javax.validation.Valid
private const val MATERIAL_CONTROLLER_PATH = "api/material"
private const val INVENTORY_CONTROLLER_PATH = "api/material/inventory"
@RestController
@RequestMapping(MATERIAL_CONTROLLER_PATH)
@ -52,8 +54,12 @@ class MaterialController(materialService: MaterialService) :
}
}
@GetMapping("/simdut")
fun getAllIdsWithSimdut(): ResponseEntity<Collection<Long>> =
ResponseEntity.ok(service.getAllIdsWithSimdut())
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
fun saveTest(@Valid entity: MaterialSaveDto, simdutFile: MultipartFile?): ResponseEntity<Material> =
fun save(@Valid entity: MaterialSaveDto, simdutFile: MultipartFile?): ResponseEntity<Material> =
super.save(
materialSaveDto(
name = entity.name,
@ -63,7 +69,7 @@ class MaterialController(materialService: MaterialService) :
)
)
@PostMapping("oldsave")
@PostMapping("unused_save")
override fun save(entity: MaterialSaveDto): ResponseEntity<Material> =
ResponseEntity.notFound().build()
@ -79,7 +85,27 @@ class MaterialController(materialService: MaterialService) :
)
)
@PutMapping("oldupdate")
@PutMapping("unused_update")
override fun update(entity: MaterialUpdateDto): ResponseEntity<Void> =
ResponseEntity.notFound().build()
}
@RestController
@RequestMapping(INVENTORY_CONTROLLER_PATH)
@Profile("rest")
class InventoryController(
private val inventoryService: InventoryService
) {
@PutMapping("add")
fun add(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Void> {
inventoryService.add(quantities)
return ResponseEntity.ok().build()
}
@PutMapping("deduct")
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>): ResponseEntity<Void> {
inventoryService.deduct(quantities)
return ResponseEntity.ok().build()
}
}

View File

@ -309,7 +309,7 @@ class EmployeeGroupServiceImpl(
override fun removeEmployeeFromGroup(group: EmployeeGroup, employee: Employee) {
if (employee.group == null || employee.group != group) return
group.employees.remove(employee)
group.employees.removeIf { it.id == employee.id }
employee.group = null
update(group)
employeeService.update(employee)

View File

@ -1,6 +1,62 @@
package dev.fyloz.trial.colorrecipesexplorer.service
//interface InventoryService {
// fun deduct(quantities: InventoryDeductDto)
// fun deduct(material: Material, quantity: Float)
//}
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantitiesException
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantityException
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.service.utils.filterThrows
import org.springframework.stereotype.Service
import javax.transaction.Transactional
interface InventoryService {
/** Adds each given [MaterialQuantityDto] to the inventory. */
fun add(materialQuantities: Collection<MaterialQuantityDto>)
/** Adds a given quantity to the given [Material]'s inventory quantity according to the given [materialQuantity]. */
fun add(materialQuantity: MaterialQuantityDto)
/** Deducts the inventory quantity of each given [MaterialQuantityDto]. */
fun deduct(materialQuantities: Collection<MaterialQuantityDto>)
/** Deducts the inventory quantity of a given [Material] by a given quantity according to the given [materialQuantity]. */
fun deduct(materialQuantity: MaterialQuantityDto)
}
@Service
class InventoryServiceImpl(
private val materialService: MaterialService
) : InventoryService {
@Transactional
override fun add(materialQuantities: Collection<MaterialQuantityDto>) {
materialQuantities.forEach(::add)
}
override fun add(materialQuantity: MaterialQuantityDto) {
materialService.updateQuantity(
materialService.getById(materialQuantity.material),
materialQuantity.quantity
)
}
@Transactional
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>) {
with(materialQuantities.filterThrows<MaterialQuantityDto, LowQuantityException> {
deduct(it)
}) {
if (this.isNotEmpty()) {
throw LowQuantitiesException(this)
}
}
}
override fun deduct(materialQuantity: MaterialQuantityDto) {
val material = materialService.getById(materialQuantity.material)
if (material.inventoryQuantity >= materialQuantity.quantity) {
materialService.updateQuantity(
material,
-materialQuantity.quantity
)
} else {
throw LowQuantityException(materialQuantity)
}
}
}

View File

@ -26,6 +26,12 @@ interface MaterialService :
/** 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<Material>
/** Gets the identifier of materials for which a SIMDUT exists. */
fun getAllIdsWithSimdut(): Collection<Long>
/** Updates the quantity of the given [material] with the given [factor]. */
fun updateQuantity(material: Material, factor: Float)
}
@Service
@ -35,9 +41,7 @@ class MaterialServiceImpl(
val recipeService: RecipeService,
val mixService: MixService
) :
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository>(
materialRepository
),
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository>(materialRepository),
MaterialService {
override fun existsByMaterialType(materialType: MaterialType): Boolean =
repository.existsByMaterialType(materialType)
@ -46,6 +50,11 @@ class MaterialServiceImpl(
override fun getSimdut(id: Long): ByteArray = simdutService.read(getById(id))
override fun getAllNotMixType(): Collection<Material> = getAll().filter { !it.isMixType }
override fun getAllIdsWithSimdut(): Collection<Long> =
getAllNotMixType()
.filter { simdutService.exists(it) }
.map { it.id!! }
override fun save(entity: MaterialSaveDto): Material =
save(entity.toMaterial()).apply {
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.write(this, entity.simdutFile)
@ -70,6 +79,10 @@ class MaterialServiceImpl(
}
}
override fun updateQuantity(material: Material, factor: Float) = with(material) {
repository.updateInventoryQuantityById(this.id!!, this.inventoryQuantity + factor)
}
override fun getAllForMixCreation(recipeId: Long): Collection<Material> {
val recipesMixTypes = recipeService.getById(recipeId).mixTypes
return getAll()

View File

@ -36,7 +36,8 @@ class MixServiceImpl(
val materialType = materialTypeService.getById(entity.materialTypeId)
val mixType = mixTypeService.getOrCreateForNameAndMaterialType(entity.name, materialType)
var mix = save(mix(recipe = recipe, mixType = mixType))
var mix = mix(recipe = recipe, mixType = mixType)
mix = save(mix)
val mixMaterials =
if (entity.mixMaterials != null) mixMaterialService.createFromMap(mix, entity.mixMaterials) else listOf()
mix = update(

View File

@ -81,12 +81,12 @@ class RecipeServiceImpl(
private fun updateSteps(recipe: Recipe, steps: List<RecipeStep>?): List<RecipeStep> =
if (steps != null) {
val toDelete = recipe.steps.filter { it !in steps }
val toDelete = recipe.steps.filter { !steps.any { step -> step.message == it.message } }
toDelete.forEach(stepService::delete)
recipe.steps
.filter { it !in toDelete } + steps
.filter { !toDelete.any { step -> step.message == it.message } } + steps
.map { recipeStep(recipe = recipe, message = it.message) }
.filter { it !in recipe.steps }
.filter { !recipe.steps.any { step -> step.message == it.message } }
.toMutableList()
} else {
recipe.steps

View File

@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.service
import dev.fyloz.trial.colorrecipesexplorer.model.Recipe
import dev.fyloz.trial.colorrecipesexplorer.model.RecipeStep
import dev.fyloz.trial.colorrecipesexplorer.model.recipeStep
import dev.fyloz.trial.colorrecipesexplorer.repository.RecipeStepRepository
import org.springframework.stereotype.Service
import javax.transaction.Transactional
@ -19,7 +20,7 @@ class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) :
AbstractModelService<RecipeStep, RecipeStepRepository>(recipeStepRepository),
RecipeStepService {
override fun createForRecipe(recipe: Recipe, message: String): RecipeStep =
RecipeStep(recipe, message)
recipeStep(recipe = recipe, message = message)
@Transactional
override fun createAllForRecipe(recipe: Recipe, messages: Collection<String>): Collection<RecipeStep> =

View File

@ -0,0 +1,11 @@
package dev.fyloz.trial.colorrecipesexplorer.service.utils
/** Returns a list containing only the elements which causes the given [consumer] to throw the given throwable [E]. */
inline fun <T, reified E : Throwable> Iterable<T>.filterThrows(consumer: (T) -> Unit): List<T> = this.filter {
try {
consumer(it)
false
} catch (th: Throwable) {
th is E
}
}

View File

@ -3,3 +3,4 @@ spring.datasource.username=root
spring.datasource.password=pass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.ddl-auto=none

View File

@ -27,7 +27,6 @@ spring.jpa.show-sql=true
spring.messages.fallback-to-system-locale=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=true
server.http2.enabled=true
server.error.whitelabel.enabled=false

View File

@ -1,104 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.EmployeeGroup
import dev.fyloz.trial.colorrecipesexplorer.model.employee
import dev.fyloz.trial.colorrecipesexplorer.model.employeeGroup
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
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
import kotlin.test.*
@Disabled
@DataJpaTest
class EmployeeRepositoryTest @Autowired constructor(private val employeeRepository: EmployeeRepository, val entityManager: TestEntityManager) {
private var employeeGroup = employeeGroup()
private val employee = employee(id = 0L, firstName = "fname", lastName = "lname")
private val anotherEmployee = employee(id = 1L, firstName = "another fname", lastName = "another lname")
private val groupEmployee = employee(id = 2L, firstName = "group fname", lastName = "group lname", group = employeeGroup)
private val defaultGroupEmployee = employee(id = 3L, firstName = "default fname", lastName = "default lname", group = employeeGroup, isDefaultGroupUser = true)
@AfterEach
fun afterEach() {
if (employeeGroup.id != null) {
entityManager.remove(employeeGroup)
employeeGroup = employeeGroup(id = null)
groupEmployee.group = employeeGroup
defaultGroupEmployee.group = employeeGroup
}
}
@Test
fun `existsByFirstNameAndLastName() returns true when an employee with the given first name and last name exists`() {
entityManager.persist(employee)
val exists = employeeRepository.existsByFirstNameAndLastName(employee.firstName, employee.lastName)
assertTrue(exists)
}
@Test
fun `existsByFirstNameAndLastName() returns false when no employee with the given first name and last name exists`() {
entityManager.persist(anotherEmployee)
val exists = employeeRepository.existsByFirstNameAndLastName(employee.firstName, employee.lastName)
assertFalse(exists)
}
@Test
fun `findByFirstNameAndLastName() returns the employee with the given first name and last name`() {
val expected = entityManager.persist(employee)
entityManager.persist(anotherEmployee) // Persist another employee to make sure the correct one is return by the tested method
assertNotNull(expected)
assertNotNull(expected.id)
val found = employeeRepository.findByFirstNameAndLastName(employee.firstName, employee.lastName)
assertEquals(found, expected)
}
@Test
fun `findByFirstNameAndLastName() returns null when no employee with the given first name and last name exists`() {
entityManager.persist(anotherEmployee)
val found = employeeRepository.findByFirstNameAndLastName(employee.firstName, employee.lastName)
assertNull(found)
}
@Test
fun `findAllByGroup() returns all employees in the given group`() {
entityManager.persist(employeeGroup)
entityManager.persist(employee)
entityManager.persist(groupEmployee)
entityManager.persist(defaultGroupEmployee)
val found = employeeRepository.findAllByGroup(employeeGroup)
assertTrue(found.contains(groupEmployee))
assertTrue(found.contains(defaultGroupEmployee))
assertFalse(found.contains(employee))
}
@Test
fun `findByIsDefaultGroupUserIsTrueAndGroupIs() returns the default employee of the given group`() {
entityManager.persist(employeeGroup)
entityManager.persist(employee)
entityManager.persist(groupEmployee)
entityManager.persist(defaultGroupEmployee)
val found = employeeRepository.findByIsDefaultGroupUserIsTrueAndGroupIs(employeeGroup)
assertEquals(found, defaultGroupEmployee)
}
}
@Disabled
class EmployeeGroupRepositoryTest @Autowired constructor(employeeGroupRepository: EmployeeGroupRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<EmployeeGroup, EmployeeGroupRepository>(employeeGroupRepository, entityManager) {
override fun entity(name: String): EmployeeGroup = employeeGroup(name = name)
}

View File

@ -1,15 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.Company
import dev.fyloz.trial.colorrecipesexplorer.model.company
import org.junit.jupiter.api.Disabled
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
@Disabled
@DataJpaTest
class CompanyRepositoryTest @Autowired constructor(companyRepository: CompanyRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<Company, CompanyRepository>(companyRepository, entityManager) {
override fun entity(name: String): Company = company(name = name)
}

View File

@ -1,62 +1,29 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.Material
import dev.fyloz.trial.colorrecipesexplorer.model.material
import dev.fyloz.trial.colorrecipesexplorer.model.materialType
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.test.assertEquals
@Disabled
class MaterialRepositoryTest @Autowired constructor(materialRepository: MaterialRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<Material, MaterialRepository>(materialRepository, entityManager) {
override fun entity(name: String): Material = material(name = name, materialType = null)
@DataJpaTest(excludeAutoConfiguration = [LiquibaseAutoConfiguration::class])
class MaterialRepositoryTest @Autowired constructor(
private val materialRepository: MaterialRepository,
private val entityManager: TestEntityManager
) {
// updateInventoryQuantityById()
@Test
fun `existsByMaterialType() returns true when a material with the given material type exists`() {
val materialType = materialType()
val material = material(materialType = materialType)
fun `updateInventoryQuantityById() updates the quantity of the material with the given identifier`() {
var material = entityManager.persist(material(inventoryQuantity = 1000f, materialType = null))
val updatedQuantity = 1235f
entityManager.persist(materialType)
entityManager.persist(material)
materialRepository.updateInventoryQuantityById(material.id!!, updatedQuantity)
val found = repository.existsByMaterialType(materialType)
material = entityManager.refresh(material)
assertTrue(found)
}
@Test
fun `existsByMaterialType() returns false when no material with the given material type exists`() {
val materialType = materialType()
entityManager.persist(materialType)
val found = repository.existsByMaterialType(materialType)
assertFalse(found)
}
@Test
fun `findAllByMaterialType() returns all materials with the given material type`() {
val materialType = materialType(name = "material type", prefix = "MAT")
val anotherMaterialType = materialType(name = "another material type", prefix = "ANO")
val material = material(name = "material", materialType = materialType)
val anotherMaterial = material(name = "another material", materialType = materialType)
val yetAnotherMaterial = material(name = "yet another material", materialType = anotherMaterialType)
entityManager.persist(materialType)
entityManager.persist(anotherMaterialType)
entityManager.persist(material)
entityManager.persist(anotherMaterial)
entityManager.persist(yetAnotherMaterial)
val found = repository.findAllByMaterialType(materialType)
assertTrue(found.contains(material))
assertTrue(found.contains(anotherMaterial))
assertFalse(found.contains(yetAnotherMaterial))
assertEquals(updatedQuantity, material.inventoryQuantity)
}
}

View File

@ -1,91 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialType
import dev.fyloz.trial.colorrecipesexplorer.model.materialType
import org.junit.jupiter.api.Disabled
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import org.junit.jupiter.api.Test
import kotlin.test.*
@Disabled
class MaterialTypeRepositoryTest @Autowired constructor(materialTypeRepository: MaterialTypeRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<MaterialType, MaterialTypeRepository>(materialTypeRepository, entityManager) {
override fun entity(name: String): MaterialType = entity(name = name, prefix = "MAT")
private fun entity(name: String = "materialType", prefix: String = "MAT"): MaterialType = materialType(name = name, prefix = prefix)
@Test
fun `existsByPrefix() returns true when a material type with the given prefix exists`() {
val materialType = entity()
entityManager.persist(materialType)
val found = repository.existsByPrefix(materialType.prefix)
assertTrue(found)
}
@Test
fun `existsByPrefix() returns false when no material type with the given prefix exists`() {
val materialType = entity()
val anotherMaterialType = entity(prefix = "ANO")
entityManager.persist(anotherMaterialType)
val found = repository.existsByPrefix(materialType.prefix)
assertFalse(found)
}
@Test
fun `findAllBySystemTypeIs() returns all system types`() {
val materialType = entity()
val systemType = materialType(name = "system type", prefix = "SYT", systemType = true)
val anotherSystemType = materialType(name = "another system type", prefix = "ASY", systemType = true)
entityManager.persist(materialType)
entityManager.persist(systemType)
entityManager.persist(anotherSystemType)
val found = repository.findAllBySystemTypeIs(true)
assertTrue(found.contains(systemType))
assertTrue(found.contains(anotherSystemType))
assertFalse(found.contains(materialType))
}
@Test
fun `findAllBySystemTypeIs() returns all material types which are not system types`() {
val materialType = entity()
val anotherMaterialType = entity(name = "another material type", prefix = "AMT")
val systemType = materialType(name = "system type", prefix = "SYT", systemType = true)
entityManager.persist(materialType)
entityManager.persist(anotherMaterialType)
entityManager.persist(systemType)
val found = repository.findAllBySystemTypeIs(false)
assertTrue(found.contains(anotherMaterialType))
assertTrue(found.contains(materialType))
assertFalse(found.contains(systemType))
}
@Test
fun `findByPrefix() returns the material type with the given prefix`() {
val materialType = entity()
entityManager.persist(materialType)
val found = repository.findByPrefix(materialType.prefix)
assertNotNull(found)
assertEquals(materialType, found)
}
@Test
fun `findByPrefix() returns null when no material type with the given prefix exists`() {
entityManager.persist(entity(prefix = "ANO"))
val found = repository.findByPrefix(entity().prefix)
assertNull(found)
}
}

View File

@ -1,37 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import org.junit.jupiter.api.Disabled
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
@Disabled
@DataJpaTest
class MixMaterialRepositoryTest @Autowired constructor(
private val mixMaterialRepository: MixMaterialRepository,
val entityManager: TestEntityManager
) {
// TODO trouver pourquoi il y a toujours un PersistentObjectException
// private val material = material(id = 0L)
// private val mixMaterial = mixMaterial(id = 0L, material = material)
// private val anotherMixMaterial = mixMaterial(id = 1L, material = material(id = 1L))
//
// @Test
// fun `existsByMaterial() returns true when a mix material with the given material exists`() {
// entityManager.persist(mixMaterial)
//
// val exists = mixMaterialRepository.existsByMaterial(material)
//
// assertTrue(exists)
// }
//
// @Test
// fun `existsByMaterial() returns false when no mix material with the given material exists`() {
// entityManager.persist(anotherMixMaterial)
//
// val exists = mixMaterialRepository.existsByMaterial(material)
//
// assertFalse(exists)
// }
}

View File

@ -1,16 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import org.junit.jupiter.api.Disabled
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(
recipeStepRepository: RecipeStepRepository,
entityManager: TestEntityManager
) {
// Nothing for now
}

View File

@ -1,71 +0,0 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.NamedModel
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import kotlin.test.*
@DataJpaTest(excludeAutoConfiguration = [LiquibaseAutoConfiguration::class])
abstract class AbstractNamedJpaRepositoryTest<E : NamedModel, R : NamedJpaRepository<E>>(
protected val repository: R,
protected val entityManager: TestEntityManager
) {
protected abstract fun entity(name: String = "entity"): E
@Test
fun `existsByName() returns true when an entity with the given name exists`() {
val entity = entity()
entityManager.persist(entity)
val found = repository.existsByName(entity.name)
assertTrue(found)
}
@Test
fun `existsByName() returns false when no entity with the given name exist`() {
val entity = entity()
val anotherEntity = entity("another entity")
entityManager.persist(anotherEntity)
val found = repository.existsByName(entity.name)
assertFalse(found)
}
@Test
fun `findByName() returns the entity with the given name`() {
val entity = entity()
entityManager.persist(entity)
val found = repository.findByName(entity.name)
assertNotNull(found)
assertEquals(entity.name, found.name)
}
@Test
fun `findByName() returns null when no entity with the given name exists`() {
val entity = entity()
val anotherEntity = entity("another entity")
entityManager.persist(anotherEntity)
val found = repository.findByName(entity.name)
assertNull(found)
}
@Test
fun `deleteByName() removes the entity with the given name`() {
val entity = entity()
entityManager.persist(entity)
repository.deleteByName(entity.name)
assertFalse(repository.existsByName(entity.name))
}
}

View File

@ -293,9 +293,6 @@ abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U :
reset(entitySaveDto, entityUpdateDto)
super.afterEach()
}
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>> :
@ -308,23 +305,21 @@ abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityD
reset(entitySaveDto, entityUpdateDto)
super.afterEach()
}
override fun `save(dto) calls and returns save() with the created entity`() =
withBaseSaveDtoTest(entity, entitySaveDto, service)
}
fun <E, N : EntityDto<E>> withBaseSaveDtoTest(
fun <E : Model, N : EntityDto<E>> withBaseSaveDtoTest(
entity: E,
entitySaveDto: N,
service: ExternalService<E, N, *, *>,
saveMockMatcher: () -> E = { entity },
op: () -> Unit = {}
) {
doReturn(entity).whenever(service).save(entity)
doReturn(entity).whenever(service).save(saveMockMatcher())
doReturn(entity).whenever(entitySaveDto).toEntity()
val found = service.save(entitySaveDto)
verify(service).save(entity)
verify(service).save(saveMockMatcher())
assertEquals(entity, found)
op()

View File

@ -154,15 +154,21 @@ class EmployeeServiceTest :
assertEquals("${entity.firstName} ${entity.lastName}", exception.value)
}
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
withBaseSaveDtoTest(entity, entitySaveDto, service, {argThat {
this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName
}})
}
@Test
fun `save(dto) calls and returns save() with the created employee`() {
whenever(entitySaveDto.toEntity()).doReturn(entitySaveDtoEmployee)
doReturn(entitySaveDtoEmployee).whenever(service).save(entitySaveDtoEmployee)
doReturn(entitySaveDtoEmployee).whenever(service).save(any<Employee>())
val found = service.save(entitySaveDto)
verify(service).save(entitySaveDtoEmployee)
verify(service).save(argThat<Employee> { this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName })
assertEquals(entitySaveDtoEmployee, found)
}
@ -306,7 +312,7 @@ class EmployeeGroupServiceTest :
verify(service).update(group)
verify(employeeService).update(employee)
assertTrue(group.employees.contains(employee))
assertTrue(group.employees.any { it.id == employee.id })
assertEquals(group, employee.group)
}
@ -337,7 +343,7 @@ class EmployeeGroupServiceTest :
verify(service).update(group)
verify(employeeService, times(2)).update(employee)
assertTrue(group.employees.contains(employee))
assertTrue(group.employees.any { it.id == employee.id })
assertEquals(group, employee.group)
}
@ -360,13 +366,13 @@ class EmployeeGroupServiceTest :
val group = employeeGroup(employees = mutableSetOf(employee))
employee.group = group
whenever(employeeService.update(employee)).doReturn(employee)
whenever(employeeService.update(any<Employee>())).doReturn(employee)
doReturn(group).whenever(service).update(group)
service.removeEmployeeFromGroup(group, employee)
verify(service).update(group)
verify(employeeService).update(employee)
verify(employeeService).update(argThat<Employee> { this.group == null })
assertFalse(group.employees.contains(employee))
assertNull(employee.group)
@ -396,6 +402,13 @@ class EmployeeGroupServiceTest :
verify(employeeService, times(0)).update(employee)
}
// save()
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
withBaseSaveDtoTest(entity, entitySaveDto, service)
}
// update()
@Test

View File

@ -46,6 +46,13 @@ class CompanyServiceTest :
assertFalse(found)
}
// save()
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
withBaseSaveDtoTest(entity, entitySaveDto, service)
}
// update()
@Test

View File

@ -0,0 +1,133 @@
package dev.fyloz.trial.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantitiesException
import dev.fyloz.trial.colorrecipesexplorer.exception.LowQuantityException
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialQuantityDto
import dev.fyloz.trial.colorrecipesexplorer.model.material
import dev.fyloz.trial.colorrecipesexplorer.model.materialQuantityDto
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class InventoryServiceTest {
private val materialService: MaterialService = mock()
private val service = spy(InventoryServiceImpl(materialService))
@AfterEach
fun afterEach() {
reset(materialService, service)
}
// add()
@Test
fun `add(materialQuantities) calls add() for each MaterialQuantityDto`() {
val materialQuantities = listOf(
materialQuantityDto(materialId = 1, quantity = 1234f),
materialQuantityDto(materialId = 2, quantity = 2345f),
materialQuantityDto(materialId = 3, quantity = 3456f),
materialQuantityDto(materialId = 4, quantity = 4567f)
)
service.add(materialQuantities)
materialQuantities.forEach {
verify(service).add(it)
}
}
@Test
fun `add(materialQuantity) updates material's quantity`() {
withGivenQuantities(0f, 1000f) {
service.add(it)
verify(materialService).updateQuantity(
argThat { this.id == it.material },
eq(it.quantity)
)
}
}
// deduct()
@Test
fun `deduct(materialQuantities) calls deduct() for each MaterialQuantityDto`() {
val materialQuantities = listOf(
materialQuantityDto(materialId = 1, quantity = 1234f),
materialQuantityDto(materialId = 2, quantity = 2345f),
materialQuantityDto(materialId = 3, quantity = 3456f),
materialQuantityDto(materialId = 4, quantity = 4567f)
)
service.deduct(materialQuantities)
materialQuantities.forEach {
verify(service).deduct(it)
}
}
@Test
fun `deduct(materialQuantities) throws LowQuantitiesException when there is not enough inventory of a given material`() {
val materialQuantities = listOf(
materialQuantityDto(materialId = 1, quantity = 1234f),
materialQuantityDto(materialId = 2, quantity = 2345f),
materialQuantityDto(materialId = 3, quantity = 3456f),
materialQuantityDto(materialId = 4, quantity = 4567f)
)
val inventoryQuantity = 3000f
val lowQuantities = materialQuantities.filter { it.quantity > inventoryQuantity }
materialQuantities.forEach {
withGivenQuantities(inventoryQuantity, it)
}
val exception = assertThrows<LowQuantitiesException> { service.deduct(materialQuantities) }
assertTrue { exception.materialQuantities.containsAll(lowQuantities) }
}
@Test
fun `deduct(materialQuantity) updates material's quantity`() {
withGivenQuantities(5000f, 1000f) {
service.deduct(it)
verify(materialService).updateQuantity(
argThat { this.id == it.material },
eq(-it.quantity)
)
}
}
@Test
fun `deduct(materialQuantity) throws LowQuantityException when there is not enough inventory of the given material`() {
withGivenQuantities(0f, 1000f) {
val exception = assertThrows<LowQuantityException> { service.deduct(it) }
assertEquals(it, exception.materialQuantity)
}
}
private fun withGivenQuantities(
inventory: Float,
deductedQuantity: Float,
materialId: Long = 0L,
test: (MaterialQuantityDto) -> Unit = {}
) {
val materialQuantity = materialQuantityDto(materialId = materialId, quantity = deductedQuantity)
withGivenQuantities(inventory, materialQuantity, test)
}
private fun withGivenQuantities(
inventory: Float,
materialQuantity: MaterialQuantityDto,
test: (MaterialQuantityDto) -> Unit = {}
) {
val material = material(id = materialQuantity.material, inventoryQuantity = inventory)
whenever(materialService.getById(material.id!!)).doReturn(material)
test(materialQuantity)
}
}

View File

@ -93,6 +93,33 @@ class MaterialServiceTest :
assertFalse(found.contains(mixTypeMaterial))
}
// getAllIdsWithSimdut()
@Test
fun `getAllIdsWithSimdut() returns a list containing the identifier of every material with a SIMDUT file`() {
val materials = listOf(
material(id = 0L),
material(id = 1L),
material(id = 2L),
material(id = 3L)
)
val hasSimdut = mapOf(
*materials
.map { it.id!! to it.evenId }
.toTypedArray()
)
val expectedIds = hasSimdut
.filter { it.value }
.map { it.key }
whenever(simdutService.exists(any())).doAnswer { hasSimdut[(it.arguments[0] as Material).id] }
doReturn(materials).whenever(service).getAllNotMixType()
val found = service.getAllIdsWithSimdut()
assertEquals(expectedIds, found)
}
// save()
@Test
@ -103,6 +130,11 @@ class MaterialServiceTest :
assertEquals(entity.name, exception.value)
}
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
withBaseSaveDtoTest(entity, entitySaveDto, service)
}
@Test
fun `save(dto) calls simdutService_write() with the saved entity`() {
val mockMultipartFile = spy(MockMultipartFile("simdut", byteArrayOf()))
@ -131,6 +163,19 @@ class MaterialServiceTest :
assertEquals(material.name, exception.value)
}
// 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
service.updateQuantity(material, quantity)
verify(repository).updateInventoryQuantityById(material.id!!, totalQuantity)
}
// getAllForMixCreation()
@Test
@ -147,9 +192,9 @@ class MaterialServiceTest :
val found = service.getAllForMixCreation(recipe.id!!)
assertTrue(found contains normalMaterial)
assertTrue(found contains mixTypeMaterial)
assertFalse(found contains anotherMixTypeMaterial)
assertTrue(normalMaterial in found)
assertTrue(mixTypeMaterial in found)
assertFalse(anotherMixTypeMaterial in found)
}
// getAllForMixUpdate()
@ -169,9 +214,9 @@ class MaterialServiceTest :
val found = service.getAllForMixUpdate(mix.id!!)
assertTrue(found contains normalMaterial)
assertTrue(found contains mixTypeMaterial)
assertFalse(found contains anotherMixTypeMaterial)
assertTrue(normalMaterial in found)
assertTrue(mixTypeMaterial in found)
assertFalse(anotherMixTypeMaterial in found)
}
// update()
@ -190,13 +235,14 @@ class MaterialServiceTest :
verify(simdutService).update(eq(mockSimdutFile), any())
}
// delete()
override fun `deleteById() deletes the entity with the given id in the repository`() {
super.`deleteById() deletes the entity with the given id in the repository`()
}
/** 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 =
any { it.id == material.id }
/** Utility property to check if the identifier of the given [Material] is even. */
val Material.evenId: Boolean
get() = this.id!! % 2 == 0L
}

View File

@ -96,6 +96,13 @@ class MaterialTypeServiceTest :
assertTrue(found.contains(anotherEntity))
}
// save()
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
withBaseSaveDtoTest(entity, entitySaveDto, service)
}
// saveMaterialType()
@Test

View File

@ -50,28 +50,30 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
val mix = mix(recipe = recipe, mixType = mixType)
val mixWithId = mix(id = 0L, recipe = recipe, mixType = mixType)
val mixMaterials = listOf(mixMaterial(mix = mixWithId, material = material(id = 1L), quantity = 1000f))
val mixWithMaterials = mix.apply { this.mixMaterials.addAll(mixMaterials) }
whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialType)
whenever(mixMaterialService.createFromMap(mixWithId, entitySaveDto.mixMaterials!!)).doReturn(mixMaterials)
whenever(mixTypeService.getOrCreateForNameAndMaterialType(mixType.name, mixType.material.materialType!!)).doReturn(
mixType
)
whenever(
mixTypeService.getOrCreateForNameAndMaterialType(
mixType.name,
mixType.material.materialType!!
)
).doReturn(mixType)
doReturn(true).whenever(service).existsById(mixWithId.id!!)
doReturn(mixWithId).whenever(service).save(mix)
doReturn(mixWithMaterials).whenever(service).update(mixWithMaterials)
doReturn(mixWithId).whenever(service).save(any<Mix>())
doReturn(mixWithId).whenever(service).update(any<Mix>())
val found = service.save(entitySaveDto)
verify(service).save(mix)
verify(service).update(mixWithMaterials)
verify(recipeService).addMix(recipe, mix)
verify(service).save(argThat<Mix> { this.recipe == mix.recipe })
verify(service).update(argThat<Mix> { this.id == mixWithId.id && this.recipe == mixWithId.recipe && this.mixMaterials == mixMaterials })
verify(recipeService).addMix(recipe, mixWithId)
// Verify if this method is called instead of the MixType's constructor, which does not check if the name is already taken by a material.
verify(mixTypeService).getOrCreateForNameAndMaterialType(mixType.name, mixType.material.materialType!!)
assertEquals(mixWithMaterials, found)
assertEquals(mixWithId, found)
}
// update()

View File

@ -73,7 +73,7 @@ class RecipeServiceTest :
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
whenever(companyService.getById(company.id!!)).doReturn(company)
withBaseSaveDtoTest(entity, entitySaveDto, service)
withBaseSaveDtoTest(entity, entitySaveDto, service, { argThat { this.id == null && this.color == color } })
}
// update()
@ -99,12 +99,16 @@ class RecipeServiceTest :
val recipe = recipe(id = 0L, steps = originalSteps)
val dto = spy(recipeUpdateDto(id = recipe.id!!, steps = updatedSteps))
withBaseUpdateDtoTest(recipe, dto, service, { any() }) {
verify(stepService).delete(argThat { this in deletedSteps })
doAnswer { it.arguments[0] }.whenever(service).update(any<Recipe>())
doReturn(recipe).whenever(dto).toEntity()
doReturn(recipe).whenever(service).getById(recipe.id!!)
doReturn(true).whenever(service).existsById(recipe.id!!)
assertTrue {
this.steps.containsAll(updatedSteps)
}
val found = service.update(dto)
verify(stepService).delete(argThat { this in deletedSteps })
updatedSteps.forEach {
assertTrue { found.steps.any { step -> step.message == it.message } }
}
}