diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/dtos/MixTypeDto.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/dtos/MixTypeDto.kt new file mode 100644 index 0000000..3d26a6c --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/dtos/MixTypeDto.kt @@ -0,0 +1,9 @@ +package dev.fyloz.colorrecipesexplorer.dtos + +data class MixTypeDto( + override val id: Long = 0L, + + val name: String, + + val material: MaterialDto +) : EntityDto diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixLogic.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixLogic.kt index e2687f6..9de3733 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixLogic.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixLogic.kt @@ -49,13 +49,13 @@ class DefaultMixLogic( override fun save(entity: MixSaveDto): Mix { val recipe = recipeLogic.getById(entity.recipeId) val materialType = materialTypeLogic.getById(entity.materialTypeId) - val mixType = mixTypeLogic.getOrCreateForNameAndMaterialType(entity.name, materialType(materialType)) + val mixType = mixTypeLogic.getOrCreateForNameAndMaterialType(entity.name, materialType) val mixMaterials = if (entity.mixMaterials != null) mixMaterialLogic.saveAll(entity.mixMaterials).toSet() else setOf() mixMaterialLogic.validateMixMaterials(mixMaterials) - var mix = mix(recipe = recipe, mixType = mixType, mixMaterials = mixMaterials.map(::mixMaterial).toMutableSet()) + var mix = mix(recipe = recipe, mixType = mixType(mixType), mixMaterials = mixMaterials.map(::mixMaterial).toMutableSet()) mix = save(mix) recipeLogic.addMix(recipe, mix) @@ -74,9 +74,9 @@ class DefaultMixLogic( mix.mixType.material.materialType!! mix.mixType = if (mixTypeIsShared(mix.mixType)) { - mixTypeLogic.saveForNameAndMaterialType(name, materialType) + mixType(mixTypeLogic.saveForNameAndMaterialType(name, materialTypeDto(materialType))) } else { - mixTypeLogic.updateForNameAndMaterialType(mix.mixType, name, materialType) + mixType(mixTypeLogic.updateForNameAndMaterialType(mixTypeDto(mix.mixType), name, materialTypeDto(materialType))) } } if (entity.mixMaterials != null) { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogic.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogic.kt index 6b7d2ac..79eb5f9 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogic.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogic.kt @@ -1,84 +1,58 @@ package dev.fyloz.colorrecipesexplorer.logic -import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository -import org.springframework.context.annotation.Lazy -import org.springframework.stereotype.Service +import dev.fyloz.colorrecipesexplorer.config.annotations.LogicComponent +import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto +import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto +import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto +import dev.fyloz.colorrecipesexplorer.model.MixType +import dev.fyloz.colorrecipesexplorer.service.MixTypeService +import org.springframework.transaction.annotation.Transactional -interface MixTypeLogic : NamedModelService { - /** Checks if a [MixType] with the given [name] and [materialType] exists. */ - fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean - - /** Gets the mix type with the given [material]. */ - fun getByMaterial(material: Material): MixType - - /** Gets the [MixType] with the given [name] and [materialType]. */ - fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType - - /** Returns a [MixType] for the given [name] and [materialType]. If a mix type with these does not already exists, it will be created. */ - fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType +interface MixTypeLogic : Logic { + /** Returns a [MixType] for the given [name] and [materialType]. If this mix type does not already exist, it will be created. */ + fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto /** Returns a new and persisted [MixType] with the given [name] and [materialType]. */ - fun saveForNameAndMaterialType(name: String, materialType: MaterialType): MixType + fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto /** Returns the given [mixType] updated with the given [name] and [materialType]. */ - fun updateForNameAndMaterialType(mixType: MixType, name: String, materialType: MaterialType): MixType + fun updateForNameAndMaterialType(mixType: MixTypeDto, name: String, materialType: MaterialTypeDto): MixTypeDto } -@Service -@RequireDatabase -class DefaultMixTypeLogic( - mixTypeRepository: MixTypeRepository, - @Lazy val materialLogic: MaterialLogic -) : - AbstractNamedModelService(mixTypeRepository), MixTypeLogic { - override fun idNotFoundException(id: Long) = mixTypeIdNotFoundException(id) - override fun idAlreadyExistsException(id: Long) = mixTypeIdAlreadyExistsException(id) - override fun nameNotFoundException(name: String) = mixTypeNameNotFoundException(name) - override fun nameAlreadyExistsException(name: String) = mixTypeNameAlreadyExistsException(name) +@LogicComponent +class DefaultMixTypeLogic(service: MixTypeService, private val materialLogic: MaterialLogic) : + BaseLogic(service, MixType::class.simpleName!!), MixTypeLogic { + override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialTypeDto) = + service.getByNameAndMaterialType(name, materialType.id) ?: saveForNameAndMaterialType(name, materialType) - override fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean = - repository.existsByNameAndMaterialType(name, materialType) - - override fun getByMaterial(material: Material): MixType = - repository.findByMaterial(material) ?: throw nameNotFoundException(material.name) - - override fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType = - repository.findByNameAndMaterialType(name, materialType) - ?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType) - - override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType = - if (existsByNameAndMaterialType(name, materialType)) - getByNameAndMaterialType(name, materialType) - else - saveForNameAndMaterialType(name, materialType) - - override fun save(entity: MixType): MixType { - if (materialLogic.existsByName(entity.name)) - throw AlreadyExistsException("material", "material already exists", "material already exists details (TODO)", entity.name) // TODO - return super.save(entity) - } - - override fun saveForNameAndMaterialType(name: String, materialType: MaterialType): MixType = - save( - mixType( + @Transactional + override fun saveForNameAndMaterialType(name: String, materialType: MaterialTypeDto): MixTypeDto { + val material = materialLogic.save( + MaterialDto( name = name, - material = material( - name = name, - inventoryQuantity = Float.MIN_VALUE, - isMixType = true, - materialType = materialType - ) + inventoryQuantity = Float.MIN_VALUE, + isMixType = true, + materialType = materialType ) ) - override fun updateForNameAndMaterialType(mixType: MixType, name: String, materialType: MaterialType): MixType = - update(mixType.copy(material = mixType.material.copy(name = name, materialType = materialType))) - - override fun delete(entity: MixType) { - if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixTypeException(entity) - super.delete(entity) + return save(MixTypeDto(name = name, material = material)) } -} + + override fun updateForNameAndMaterialType( + mixType: MixTypeDto, + name: String, + materialType: MaterialTypeDto + ): MixTypeDto { + val material = materialLogic.update(mixType.material.copy(name = name, materialType = materialType)) + return update(mixType.copy(name = name, material = material)) + } + + override fun deleteById(id: Long) { + if (service.isUsedByMixes(id)) { + throw cannotDeleteException("Cannot delete the mix type with the id '$id' because one or more mixes depends on it") + } + + super.deleteById(id) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixType.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixType.kt index 9953f80..8b25b67 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixType.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/MixType.kt @@ -1,5 +1,6 @@ package dev.fyloz.colorrecipesexplorer.model +import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException @@ -10,91 +11,42 @@ import javax.persistence.* @Entity @Table(name = "mix_type") data class MixType( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - override val id: Long?, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + override val id: Long?, - @Column(unique = true) - override var name: String, + @Column(unique = true) + val name: String, - @OneToOne(cascade = [CascadeType.ALL]) - @JoinColumn(name = "material_id") - var material: Material -) : NamedModelEntity + @OneToOne(cascade = [CascadeType.ALL]) + @JoinColumn(name = "material_id") + var material: Material +) : ModelEntity // ==== DSL ==== fun mixType( - id: Long? = null, - name: String = "name", - material: Material = material(), - op: MixType.() -> Unit = {} + id: Long? = null, + name: String = "name", + material: Material = material(), + op: MixType.() -> Unit = {} ) = MixType(id, name, material).apply(op) fun mixType( - name: String = "name", - materialType: MaterialType = materialType(), - op: MixType.() -> Unit = {} + name: String = "name", + materialType: MaterialType = materialType(), + op: MixType.() -> Unit = {} ) = mixType( - id = null, - name, - material = material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType) + id = null, + name, + material = material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType) ).apply(op) -// ==== Exceptions ==== -private const val MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE = "Mix type not found" -private const val MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix type already exists" -private const val MIX_TYPE_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete mix type" -private const val MIX_TYPE_EXCEPTION_ERROR_CODE = "mixtype" +@Deprecated("Temporary DSL for transition") +fun mixTypeDto( + entity: MixType +) = MixTypeDto(entity.id!!, entity.name, materialDto(entity.material)) -class MixTypeNameAndMaterialTypeNotFoundException(name: String, materialType: MaterialType) : - RestException( - "notfound-mixtype-namematerialtype", - MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE, - HttpStatus.NOT_FOUND, - "A mix type with the name $name and material type ${materialType.name} could not be found", - mapOf( - "name" to name, - "materialType" to materialType.name - ) - ) - -fun mixTypeIdNotFoundException(id: Long) = - NotFoundException( - MIX_TYPE_EXCEPTION_ERROR_CODE, - MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE, - "A mix type with the id $id could not be found", - id - ) - -fun mixTypeIdAlreadyExistsException(id: Long) = - AlreadyExistsException( - MIX_TYPE_EXCEPTION_ERROR_CODE, - MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE, - "A mix type with the id $id already exists", - id - ) - -fun mixTypeNameNotFoundException(name: String) = - NotFoundException( - MIX_TYPE_EXCEPTION_ERROR_CODE, - MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE, - "A mix type with the name $name could not be found", - name, - "name" - ) - -fun mixTypeNameAlreadyExistsException(name: String) = - AlreadyExistsException( - MIX_TYPE_EXCEPTION_ERROR_CODE, - MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE, - "A mix type with the name $name already exists", - name, - "name" - ) - -fun cannotDeleteMixTypeException(mixType: MixType) = - CannotDeleteException( - MIX_TYPE_EXCEPTION_ERROR_CODE, - MIX_TYPE_CANNOT_DELETE_EXCEPTION_TITLE, - "Cannot delete the mix type ${mixType.name} because one or more mixes depends on it" - ) +@Deprecated("Temporary DSL for transition") +fun mixType( + dto: MixTypeDto +) = MixType(dto.id, dto.name, material(dto.material)) \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/MixTypeRepository.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/MixTypeRepository.kt index 7298eb6..ac9e643 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/MixTypeRepository.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/repository/MixTypeRepository.kt @@ -1,30 +1,27 @@ package dev.fyloz.colorrecipesexplorer.repository -import dev.fyloz.colorrecipesexplorer.model.Material import dev.fyloz.colorrecipesexplorer.model.MaterialType import dev.fyloz.colorrecipesexplorer.model.MixType +import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository @Repository -interface MixTypeRepository : NamedJpaRepository { - @Query("select case when(count(m) > 0) then true else false end from MixType m where m.name = :name and m.material.materialType = :materialType") - fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean +interface MixTypeRepository : JpaRepository { + /** Checks if a mix type with the given [name], [materialTypeId] and a different [id] exists. */ + @Query("select case when(count(m) > 0) then true else false end from MixType m where m.name = :name and m.material.materialType.id = :materialTypeId and m.id <> :id") + fun existsByNameAndMaterialType(name: String, materialTypeId: Long, id: Long): Boolean - /** Gets the mix type with the given [material]. */ - fun findByMaterial(material: Material): MixType? - - /** Gets the [MixType] with the given [name] and [materialType]. */ - @Query("select m from MixType m where m.name = :name and m.material.materialType = :materialType") - fun findByNameAndMaterialType(name: String, materialType: MaterialType): MixType? + /** Finds the mix type with the given [name] and [materialTypeId]. */ + @Query("select m from MixType m where m.name = :name and m.material.materialType.id = :materialTypeId") + fun findByNameAndMaterialType(name: String, materialTypeId: Long): MixType? + /** Checks if a mix depends on the mix type with the given [id]. */ @Query( - """ + """ select case when(count(m.id) > 0) then false else true end - from MixType t - left join Mix m on t.id = m.mixType.id - where t.id = :id - """ + from Mix m where m.mixType.id = :id + """ ) - fun canBeDeleted(id: Long): Boolean + fun isUsedByMixes(id: Long): Boolean } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeService.kt new file mode 100644 index 0000000..6d196f6 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MixTypeService.kt @@ -0,0 +1,36 @@ +package dev.fyloz.colorrecipesexplorer.service + +import dev.fyloz.colorrecipesexplorer.config.annotations.ServiceComponent +import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto +import dev.fyloz.colorrecipesexplorer.model.MixType +import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository + +interface MixTypeService : Service { + /** Checks if a mix type with the given [name], [materialTypeId] and a different [id] exists. */ + fun existsByNameAndMaterialType(name: String, materialTypeId: Long, id: Long? = null): Boolean + + /** Finds the mix type with the given [name] and [materialTypeId]. */ + fun getByNameAndMaterialType(name: String, materialTypeId: Long): MixTypeDto? + + /** Checks if a mix depends on the mix type with the given [id]. */ + fun isUsedByMixes(id: Long): Boolean +} + +@ServiceComponent +class DefaultMixTypeService(repository: MixTypeRepository, val materialService: MaterialService) : + BaseService(repository), MixTypeService { + override fun existsByNameAndMaterialType(name: String, materialTypeId: Long, id: Long?) = + repository.existsByNameAndMaterialType(name, materialTypeId, id ?: 0L) + + override fun getByNameAndMaterialType(name: String, materialTypeId: Long) = + repository.findByNameAndMaterialType(name, materialTypeId)?.let(::toDto) + + override fun isUsedByMixes(id: Long) = + repository.isUsedByMixes(id) + + override fun toDto(entity: MixType) = + MixTypeDto(entity.id!!, entity.name, materialService.toDto(entity.material)) + + override fun toEntity(dto: MixTypeDto) = + MixType(dto.id, dto.name, materialService.toEntity(dto.material)) +} \ No newline at end of file diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/DefaultMixTypeLogicTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/DefaultMixTypeLogicTest.kt new file mode 100644 index 0000000..4c90086 --- /dev/null +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/DefaultMixTypeLogicTest.kt @@ -0,0 +1,131 @@ +package dev.fyloz.colorrecipesexplorer.logic + +import dev.fyloz.colorrecipesexplorer.dtos.MaterialDto +import dev.fyloz.colorrecipesexplorer.dtos.MaterialTypeDto +import dev.fyloz.colorrecipesexplorer.dtos.MixTypeDto +import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException +import dev.fyloz.colorrecipesexplorer.service.MixTypeService +import io.mockk.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +class DefaultMixTypeLogicTest { + private val mixTypeServiceMock = mockk() + private val materialLogicMock = mockk() + + private val mixTypeLogic = spyk(DefaultMixTypeLogic(mixTypeServiceMock, materialLogicMock)) + + private val materialType = MaterialTypeDto(1L, "Unit test material type", "UTMT", false) + private val material = MaterialDto(1L, "Unit test material", 1000f, true, materialType) + private val mixType = MixTypeDto(id = 1L, name = "Unit test mix type", material) + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun getOrCreateForNameAndMaterialType_normalBehavior_returnsFromService() { + // Arrange + every { mixTypeServiceMock.getByNameAndMaterialType(any(), any()) } returns mixType + + // Act + val actualMixType = mixTypeLogic.getOrCreateForNameAndMaterialType(mixType.name, materialType) + + // Assert + assertEquals(mixType, actualMixType) + } + + @Test + fun getOrCreateForNameAndMaterialType_notFound_returnsFromSaveForNameAndMaterialType() { + // Arrange + every { mixTypeServiceMock.getByNameAndMaterialType(any(), any()) } returns null + every { mixTypeLogic.saveForNameAndMaterialType(any(), any()) } returns mixType + + // Act + val actualMixType = mixTypeLogic.getOrCreateForNameAndMaterialType(mixType.name, materialType) + + // Assert + assertEquals(mixType, actualMixType) + } + + @Test + fun saveForNameAndMaterialType_normalBehavior_callsSavesInMaterialLogic() { + // Arrange + every { materialLogicMock.save(any()) } returnsArgument 0 + every { mixTypeLogic.save(any()) } returnsArgument 0 + + // Act + mixTypeLogic.saveForNameAndMaterialType(mixType.name, materialType) + + // Assert + verify { + materialLogicMock.save(match { it.name == mixType.name && it.materialType == materialType }) + } + confirmVerified(materialLogicMock) + } + + @Test + fun saveForNameAndMaterialType_normalBehavior_callsSave() { + // Arrange + every { materialLogicMock.save(any()) } returnsArgument 0 + every { mixTypeLogic.save(any()) } returnsArgument 0 + + // Act + mixTypeLogic.saveForNameAndMaterialType(mixType.name, materialType) + + // Assert + verify { + mixTypeLogic.save(match { it.name == mixType.name && it.material.name == mixType.name && it.material.materialType == materialType }) + } + } + + @Test + fun updateForNameAndMaterialType_normalBehavior_callsSavesInMaterialLogic() { + // Arrange + val updatedName = mixType.name + " updated" + val updatedMaterialType = materialType.copy(id = 2L) + + every { materialLogicMock.update(any()) } returnsArgument 0 + every { mixTypeLogic.update(any()) } returnsArgument 0 + + // Act + mixTypeLogic.updateForNameAndMaterialType(mixType, updatedName, updatedMaterialType) + + // Assert + verify { + materialLogicMock.update(match { it.name == updatedName && it.materialType == updatedMaterialType }) + } + confirmVerified(materialLogicMock) + } + + @Test + fun updateForNameAndMaterialType_normalBehavior_callsSave() { + // Arrange + val updatedName = mixType.name + " updated" + val updatedMaterialType = materialType.copy(id = 2L) + + every { materialLogicMock.update(any()) } returnsArgument 0 + every { mixTypeLogic.update(any()) } returnsArgument 0 + + // Act + mixTypeLogic.updateForNameAndMaterialType(mixType, updatedName, updatedMaterialType) + + // Assert + verify { + mixTypeLogic.update(match { it.name == updatedName && it.material.name == updatedName && it.material.materialType == updatedMaterialType }) + } + } + + @Test + fun deleteById_usedByMix_throwsCannotDeleteException() { + // Arrange + every { mixTypeServiceMock.isUsedByMixes(any()) } returns true + + // Act + // Assert + assertThrows { mixTypeLogic.deleteById(mixType.id) } + } +} \ No newline at end of file diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogicTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogicTest.kt deleted file mode 100644 index 6d38923..0000000 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/logic/MixTypeLogicTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -package dev.fyloz.colorrecipesexplorer.logic - -import com.nhaarman.mockitokotlin2.* -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException -import dev.fyloz.colorrecipesexplorer.exception.NotFoundException -import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MixTypeLogicTest : AbstractNamedModelServiceTest() { - override val repository: MixTypeRepository = mock() - private val materialService: MaterialLogic = mock() - override val logic: MixTypeLogic = spy(DefaultMixTypeLogic(repository, materialService)) - - private val materialType: MaterialType = materialType() - private val material: Material = material(id = 0L, materialType = materialType) - override val entity: MixType = mixType(id = 0L, name = "mix type", material = material) - override val anotherEntity: MixType = mixType(id = 1L, name = "another mix type") - override val entityWithEntityName: MixType = mixType(id = 2L, name = entity.name) - - @AfterEach - override fun afterEach() { - reset(materialService) - super.afterEach() - } - - // existsByNameAndMaterialType - - @Test - fun `existsByNameAndMaterialType() returns repository's answer`() { - setOf(true, false).forEach { - whenever(repository.existsByNameAndMaterialType(entity.name, materialType)).doReturn(it) - - val found = logic.existsByNameAndMaterialType(entity.name, materialType) - - assertTrue { found == it } - } - } - - // getByMaterial() - - @Test - fun `getByMaterial() returns the mix type with the given material`() { - whenever(repository.findByMaterial(material)).doReturn(entity) - - val found = logic.getByMaterial(material) - - assertEquals(entity, found) - } - - @Test - fun `getByMaterial() throws NotFoundException when no mix type with the given material exists`() { - whenever(repository.findByMaterial(material)).doReturn(null) - - assertThrows { logic.getByMaterial(material) } - .assertErrorCode("name") - } - - // getByNameAndMaterialType() - - @Test - fun `getByNameAndMaterialType() returns the mix type with the given name and material type`() { - whenever(repository.findByNameAndMaterialType(entity.name, materialType)).doReturn(entity) - - val found = logic.getByNameAndMaterialType(entity.name, materialType) - - assertEquals(entity, found) - } - - // getOrCreateForNameAndMaterialType() - @Test - fun `getOrCreateForNameAndMaterialType() calls getForNameAndMaterialType() when a mix type with the given name and material type exists`() { - doReturn(true).whenever(logic).existsByNameAndMaterialType(entity.name, materialType) - doReturn(entity).whenever(logic).getByNameAndMaterialType(entity.name, materialType) - - val found = logic.getOrCreateForNameAndMaterialType(entity.name, materialType) - - verify(logic).getByNameAndMaterialType(entity.name, materialType) - - assertEquals(entity, found) - } - - @Test - fun `getOrCreateForNameAndMaterialType() calls saveForNameAndMaterialType() when no mix type with the given name and material type exists`() { - doReturn(false).whenever(logic).existsByNameAndMaterialType(entity.name, materialType) - doReturn(entity).whenever(logic).saveForNameAndMaterialType(entity.name, materialType) - - val found = logic.getOrCreateForNameAndMaterialType(entity.name, materialType) - - verify(logic).saveForNameAndMaterialType(entity.name, materialType) - - assertEquals(entity, found) - } - - // save() - - @Test - fun `save() throws AlreadyExistsException when a material with the name of the new mix type exists`() { - whenever(materialService.existsByName(entity.name)).doReturn(true) - - assertThrows { logic.save(entity) } - .assertErrorCode("name") - } - - // saveForNameAndMaterialType() - - @Test - fun `saveForNameAndMaterialType() creates a save a valid mix type with the given name and material type`() { - val name = entity.name - val materialType = materialType() - - doAnswer { it.arguments[0] }.whenever(logic).save(any()) - - val found = logic.saveForNameAndMaterialType(name, materialType) - - verify(logic).save(any()) - - assertEquals(name, found.name) - assertEquals(name, found.material.name) - assertEquals(materialType, found.material.materialType) - assertTrue(found.material.isMixType) - } - - // updateForNameAndMaterialType() - - @Test - fun `updateForNameAndMaterialType() updates the given mix type with the given name and material type`() { - val mixType = mixType(id = 1L, material = material(isMixType = true)) - val name = entity.name - val materialType = materialType() - - doAnswer { it.arguments[0] }.whenever(logic).update(any()) - - val found = logic.updateForNameAndMaterialType(mixType, name, materialType) - - verify(logic).update(any()) - - assertEquals(mixType.id, found.id) - assertEquals(name, found.name) - assertEquals(name, found.material.name) - assertEquals(materialType, found.material.materialType) - assertTrue(found.material.isMixType) - } - - // delete() - - override fun `delete() deletes in the repository`() { - whenCanBeDeleted { - super.`delete() deletes in the repository`() - } - } - - // deleteById() - - override fun `deleteById() deletes the entity with the given id in the repository`() { - whenCanBeDeleted { - super.`deleteById() deletes the entity with the given id in the repository`() - } - } - - private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) { - whenever(repository.canBeDeleted(id)).doReturn(true) - - test() - } -}