From 3033e104b2ccc468e0c96aeb616c1a03293c1f6a Mon Sep 17 00:00:00 2001 From: FyloZ Date: Fri, 19 Mar 2021 14:45:53 -0400 Subject: [PATCH] Ajout d'un endpoint pour l'inventaire --- .../model/dto/InventoryDto.kt | 3 - .../exception/RestException.kt | 29 +++- .../model/AccountModel.kt | 10 +- .../colorrecipesexplorer/model/Company.kt | 5 +- .../colorrecipesexplorer/model/Material.kt | 53 +++---- .../model/MaterialType.kt | 5 +- .../trial/colorrecipesexplorer/model/Mix.kt | 7 +- .../colorrecipesexplorer/model/MixMaterial.kt | 9 +- .../colorrecipesexplorer/model/MixType.kt | 17 +-- .../colorrecipesexplorer/model/Recipe.kt | 51 ------- .../colorrecipesexplorer/model/RecipeStep.kt | 9 +- .../repository/MaterialRepository.kt | 7 +- .../rest/InventoryController.kt | 11 -- .../rest/MaterialController.kt | 22 +++ .../service/AccountService.kt | 2 +- .../service/InventoryService.kt | 64 ++++++++- .../service/MaterialService.kt | 7 + .../service/MixService.kt | 3 +- .../service/RecipeService.kt | 6 +- .../service/RecipeStepService.kt | 3 +- .../service/utils/Collections.kt | 11 ++ .../resources/application-mysql.properties | 1 + .../resources/application-test.properties | 0 src/main/resources/application.properties | 1 - .../repository/AccountRepositoryTest.kt | 104 -------------- .../repository/CompanyRepositoryTest.kt | 15 -- .../repository/MaterialRepositoryTest.kt | 66 +++------ .../repository/MaterialTypeRepositoryTest.kt | 91 ------------ .../repository/MixMaterialRepositoryTest.kt | 37 ----- .../repository/RecipeStepRepositoryTest.kt | 16 --- .../repository/RepositoryTest.kt | 71 ---------- .../service/AbstractServiceTest.kt | 13 +- .../service/AccountsServiceTest.kt | 25 +++- .../service/CompanyServiceTest.kt | 7 + .../service/InventoryServiceTest.kt | 133 ++++++++++++++++++ .../service/MaterialServiceTest.kt | 21 +++ .../service/MaterialTypeServiceTest.kt | 7 + .../service/MixServiceTest.kt | 22 +-- .../service/RecipeServiceTest.kt | 16 ++- 39 files changed, 399 insertions(+), 581 deletions(-) delete mode 100644 src/main/java/dev/fyloz/trial/colorrecipesexplorer/model/dto/InventoryDto.kt delete mode 100644 src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/InventoryController.kt create mode 100644 src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/utils/Collections.kt create mode 100644 src/main/resources/application-test.properties delete mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/AccountRepositoryTest.kt delete mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/CompanyRepositoryTest.kt delete mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialTypeRepositoryTest.kt delete mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MixMaterialRepositoryTest.kt delete mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RecipeStepRepositoryTest.kt delete mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RepositoryTest.kt create mode 100644 src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryServiceTest.kt diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/model/dto/InventoryDto.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/model/dto/InventoryDto.kt deleted file mode 100644 index 9b12869..0000000 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/model/dto/InventoryDto.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.fyloz.trial.colorrecipesexplorer.model.dto - -data class InventoryDto(val mixId: Long, val materialIds: List, val quantities: List) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/exception/RestException.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/exception/RestException.kt index 5c32f4b..2e14e75 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/exception/RestException.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/exception/RestException.kt @@ -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) : + 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") diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/AccountModel.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/AccountModel.kt index 9d47a49..47c2247 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/AccountModel.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/AccountModel.kt @@ -64,11 +64,6 @@ data class Employee( ) : Model { @JsonProperty("permissions") fun getFlattenedPermissions(): Iterable = 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 = 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( diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Company.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Company.kt index 87c7a2c..abcdbfe 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Company.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Company.kt @@ -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( diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Material.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Material.kt index bfcd908..59972e6 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Material.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Material.kt @@ -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) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MaterialType.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MaterialType.kt index 2c1479b..96215a8 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MaterialType.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MaterialType.kt @@ -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) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt index c1d96dc..a12201b 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Mix.kt @@ -34,12 +34,7 @@ data class Mix( @OneToMany(cascade = [CascadeType.ALL], mappedBy = "mix") var mixMaterials: MutableCollection, -) : 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) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt index 95a7660..73125ed 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixMaterial.kt @@ -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( diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixType.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixType.kt index d25fb8a..34bac0e 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixType.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/MixType.kt @@ -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) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Recipe.kt index 3ead0ef..28c9117 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/Recipe.kt @@ -57,61 +57,10 @@ data class Recipe( @OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe") var steps: List ) : 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 - @JsonIgnore - get() = mixes.sortedBy { it.id } - /** The mix types contained in this recipe. */ val mixTypes: Collection @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( diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/RecipeStep.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/RecipeStep.kt index 80ccd59..1635470 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/RecipeStep.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/model/RecipeStep.kt @@ -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 ==== diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepository.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepository.kt index a678110..cc6b752 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepository.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepository.kt @@ -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 { /** 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 + /** 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( """ diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/InventoryController.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/InventoryController.kt deleted file mode 100644 index eae95da..0000000 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/InventoryController.kt +++ /dev/null @@ -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> = ResponseEntity.ok(inventoryService.getAllMaterials()) -//} diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/MaterialController.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/MaterialController.kt index a784ca6..7ce4036 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/MaterialController.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/rest/MaterialController.kt @@ -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) @@ -83,3 +85,23 @@ class MaterialController(materialService: MaterialService) : override fun update(entity: MaterialUpdateDto): ResponseEntity = ResponseEntity.notFound().build() } + +@RestController +@RequestMapping(INVENTORY_CONTROLLER_PATH) +@Profile("rest") +class InventoryController( + private val inventoryService: InventoryService +) { + @PutMapping("add") + fun add(@RequestBody quantities: Collection): ResponseEntity { + inventoryService.add(quantities) + return ResponseEntity.ok().build() + } + + @PutMapping("deduct") + fun deduct(@RequestBody quantities: Collection): ResponseEntity { + inventoryService.deduct(quantities) + return ResponseEntity.ok().build() + } +} + diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountService.kt index bfe8f91..7b6c130 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountService.kt @@ -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) diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryService.kt index c77b94f..47a5f0b 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryService.kt @@ -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) + + /** 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) + + /** 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) { + materialQuantities.forEach(::add) + } + + override fun add(materialQuantity: MaterialQuantityDto) { + materialService.updateQuantity( + materialService.getById(materialQuantity.material), + materialQuantity.quantity + ) + } + + @Transactional + override fun deduct(materialQuantities: Collection) { + with(materialQuantities.filterThrows { + 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) + } + } +} diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialService.kt index 3eb6583..c507592 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialService.kt @@ -26,6 +26,9 @@ 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 + + /** Updates the quantity of the given [material] with the given [factor]. */ + fun updateQuantity(material: Material, factor: Float) } @Service @@ -70,6 +73,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 { val recipesMixTypes = recipeService.getById(recipeId).mixTypes return getAll() diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt index 6869a37..5bd1b8f 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixService.kt @@ -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( diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeService.kt index 21aaa2a..232b985 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeService.kt @@ -81,12 +81,12 @@ class RecipeServiceImpl( private fun updateSteps(recipe: Recipe, steps: List?): List = 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 diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeStepService.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeStepService.kt index 3a527ee..79687de 100644 --- a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeStepService.kt +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeStepService.kt @@ -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(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): Collection = diff --git a/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/utils/Collections.kt b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/utils/Collections.kt new file mode 100644 index 0000000..52d430a --- /dev/null +++ b/src/main/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/utils/Collections.kt @@ -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 Iterable.filterThrows(consumer: (T) -> Unit): List = this.filter { + try { + consumer(it) + false + } catch (th: Throwable) { + th is E + } +} diff --git a/src/main/resources/application-mysql.properties b/src/main/resources/application-mysql.properties index 4d6c5ff..06d0241 100644 --- a/src/main/resources/application-mysql.properties +++ b/src/main/resources/application-mysql.properties @@ -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 diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f58528d..44a584f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/AccountRepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/AccountRepositoryTest.kt deleted file mode 100644 index 48c0233..0000000 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/AccountRepositoryTest.kt +++ /dev/null @@ -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(employeeGroupRepository, entityManager) { - override fun entity(name: String): EmployeeGroup = employeeGroup(name = name) -} - diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/CompanyRepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/CompanyRepositoryTest.kt deleted file mode 100644 index 5a6f1f4..0000000 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/CompanyRepositoryTest.kt +++ /dev/null @@ -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(companyRepository, entityManager) { - override fun entity(name: String): Company = company(name = name) -} diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepositoryTest.kt index d3d7d9e..09b44b7 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepositoryTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialRepositoryTest.kt @@ -1,62 +1,32 @@ 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.junit.runner.RunWith 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 org.springframework.test.context.junit4.SpringRunner +import kotlin.test.assertEquals -@Disabled -class MaterialRepositoryTest @Autowired constructor(materialRepository: MaterialRepository, entityManager: TestEntityManager) : - AbstractNamedJpaRepositoryTest(materialRepository, entityManager) { - override fun entity(name: String): Material = material(name = name, materialType = null) +@RunWith(SpringRunner::class) +@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) } } diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialTypeRepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialTypeRepositoryTest.kt deleted file mode 100644 index d0a4992..0000000 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MaterialTypeRepositoryTest.kt +++ /dev/null @@ -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(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) - } -} diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MixMaterialRepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MixMaterialRepositoryTest.kt deleted file mode 100644 index ccd571f..0000000 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/MixMaterialRepositoryTest.kt +++ /dev/null @@ -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) -// } -} diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RecipeStepRepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RecipeStepRepositoryTest.kt deleted file mode 100644 index 7d05f58..0000000 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RecipeStepRepositoryTest.kt +++ /dev/null @@ -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 -} diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RepositoryTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RepositoryTest.kt deleted file mode 100644 index f206028..0000000 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/repository/RepositoryTest.kt +++ /dev/null @@ -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>( - 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)) - } -} diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AbstractServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AbstractServiceTest.kt index 9fd03a8..29567c6 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AbstractServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AbstractServiceTest.kt @@ -293,9 +293,6 @@ abstract class AbstractExternalModelServiceTest, 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, U : EntityDto, S : ExternalNamedModelService, R : NamedJpaRepository> : @@ -308,23 +305,21 @@ abstract class AbstractExternalNamedModelServiceTest> withBaseSaveDtoTest( +fun > withBaseSaveDtoTest( entity: E, entitySaveDto: N, service: ExternalService, + 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() diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountsServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountsServiceTest.kt index 28ad0e3..69cd19a 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountsServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/AccountsServiceTest.kt @@ -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()) val found = service.save(entitySaveDto) - verify(service).save(entitySaveDtoEmployee) + verify(service).save(argThat { 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())).doReturn(employee) doReturn(group).whenever(service).update(group) service.removeEmployeeFromGroup(group, employee) verify(service).update(group) - verify(employeeService).update(employee) + verify(employeeService).update(argThat { 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 diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/CompanyServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/CompanyServiceTest.kt index 72d3256..8b3a143 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/CompanyServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/CompanyServiceTest.kt @@ -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 diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryServiceTest.kt new file mode 100644 index 0000000..0356433 --- /dev/null +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/InventoryServiceTest.kt @@ -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 { 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 { 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) + } +} diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialServiceTest.kt index 32565a1..530e12b 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialServiceTest.kt @@ -103,6 +103,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 +136,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 @@ -190,6 +208,9 @@ class MaterialServiceTest : verify(simdutService).update(eq(mockSimdutFile), any()) } + // updateQuantity() + + // delete() override fun `deleteById() deletes the entity with the given id in the repository`() { diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialTypeServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialTypeServiceTest.kt index 339bb47..573e813 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialTypeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MaterialTypeServiceTest.kt @@ -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 diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt index dd5dd4c..38c4428 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/MixServiceTest.kt @@ -50,28 +50,30 @@ class MixServiceTest : AbstractExternalModelServiceTest()) + doReturn(mixWithId).whenever(service).update(any()) val found = service.save(entitySaveDto) - verify(service).save(mix) - verify(service).update(mixWithMaterials) - verify(recipeService).addMix(recipe, mix) + verify(service).save(argThat { this.recipe == mix.recipe }) + verify(service).update(argThat { 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() diff --git a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeServiceTest.kt index 45cebb6..cd3f93d 100644 --- a/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/trial/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -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()) + 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 } } } }