Ajout des conflits de nom entre les recettes d'une bannière (#42)

This commit is contained in:
FyloZ 2021-05-04 12:04:02 -04:00
parent 0871a728e3
commit a59bad7a7a
5 changed files with 133 additions and 55 deletions

View File

@ -12,63 +12,62 @@ import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
abstract class RestException(
val errorCode: String,
val title: String,
val status: HttpStatus,
val details: String,
val extensions: Map<String, Any> = mapOf()
val errorCode: String,
val title: String,
val status: HttpStatus,
val details: String,
val extensions: Map<String, Any> = mapOf()
) : RuntimeException(details) {
fun buildExceptionBody() = mapOf(
"type" to errorCode,
"title" to title,
"status" to status.value(),
"detail" to details,
"type" to errorCode,
"title" to title,
"status" to status.value(),
"detail" to details,
*extensions.map { it.key to it.value }.toTypedArray()
*extensions.map { it.key to it.value }.toTypedArray()
)
}
class NotFoundException(
errorCode: String,
title: String,
details: String,
identifierValue: Any,
identifierName: String = "id"
errorCode: String,
title: String,
details: String,
identifierValue: Any,
identifierName: String = "id"
) : RestException(
errorCode = "notfound-$errorCode-$identifierName",
title = title,
status = HttpStatus.NOT_FOUND,
details = details,
extensions = mapOf(
identifierName to identifierValue
)
errorCode = "notfound-$errorCode-$identifierName",
title = title,
status = HttpStatus.NOT_FOUND,
details = details,
extensions = mapOf(
identifierName to identifierValue
)
)
class AlreadyExistsException(
errorCode: String,
title: String,
details: String,
identifierValue: Any,
identifierName: String = "id"
errorCode: String,
title: String,
details: String,
identifierValue: Any,
identifierName: String = "id",
extensions: MutableMap<String, Any> = mutableMapOf()
) : RestException(
errorCode = "exists-$errorCode-$identifierName",
title = title,
status = HttpStatus.CONFLICT,
details = details,
extensions = mapOf(
identifierName to identifierValue
)
errorCode = "exists-$errorCode-$identifierName",
title = title,
status = HttpStatus.CONFLICT,
details = details,
extensions = extensions.apply { this[identifierName] = identifierValue }.toMap()
)
class CannotDeleteException(
errorCode: String,
title: String,
details: String
errorCode: String,
title: String,
details: String
) : RestException(
errorCode = "cannotdelete-$errorCode",
title = title,
status = HttpStatus.CONFLICT,
details = details
errorCode = "cannotdelete-$errorCode",
title = title,
status = HttpStatus.CONFLICT,
details = details
)
@ControllerAdvice
@ -79,19 +78,19 @@ class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() {
finalBody["instance"] = (request as ServletWebRequest).request.requestURI
return handleExceptionInternal(
exception,
finalBody,
HttpHeaders(),
exception.status,
request
exception,
finalBody,
HttpHeaders(),
exception.status,
request
)
}
override fun handleMethodArgumentNotValid(
ex: MethodArgumentNotValidException,
headers: HttpHeaders,
status: HttpStatus,
request: WebRequest
ex: MethodArgumentNotValidException,
headers: HttpHeaders,
status: HttpStatus,
request: WebRequest
): ResponseEntity<Any> {
val errors = hashMapOf<String, String>()
ex.bindingResult.allErrors.forEach {

View File

@ -288,6 +288,8 @@ private const val RECIPE_NOT_FOUND_EXCEPTION_TITLE = "Recipe not found"
private const val RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Recipe already exists"
private const val RECIPE_EXCEPTION_ERROR_CODE = "recipe"
sealed class RecipeException
fun recipeIdNotFoundException(id: Long) =
NotFoundException(
RECIPE_EXCEPTION_ERROR_CODE,
@ -303,3 +305,16 @@ fun recipeIdAlreadyExistsException(id: Long) =
"A recipe with the id $id already exists",
id
)
fun recipeNameAlreadyExistsForCompanyException(name: String, company: Company) =
AlreadyExistsException(
"${RECIPE_EXCEPTION_ERROR_CODE}-company",
RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE,
"A recipe with the name $name already exists for the company ${company.name}",
name,
"name",
mutableMapOf(
"company" to company.name,
"companyId" to company.id!!
)
)

View File

@ -8,6 +8,9 @@ interface RecipeRepository : JpaRepository<Recipe, Long> {
/** Checks if one or more recipes have the given [company]. */
fun existsByCompany(company: Company): Boolean
/** Checks if a recipe exists with the given [name] and [company]. */
fun existsByNameAndCompany(name: String, company: Company): Boolean
/** Gets all recipes with the given [company]. */
fun findAllByCompany(company: Company): Collection<Recipe>
}

View File

@ -16,6 +16,9 @@ interface RecipeService :
/** Checks if one or more recipes have the given [company]. */
fun existsByCompany(company: Company): Boolean
/** Checks if a recipe exists with the given [name] and [company]. */
fun existsByNameAndCompany(name: String, company: Company): Boolean
/** Gets all recipes with the given [company]. */
fun getAllByCompany(company: Company): Collection<Recipe>
@ -67,10 +70,18 @@ class RecipeServiceImpl(
)
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
override fun existsByNameAndCompany(name: String, company: Company) =
repository.existsByNameAndCompany(name, company)
override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)
override fun save(entity: RecipeSaveDto): Recipe {
// TODO checks if name is unique in the scope of the [company]
val company = companyService.getById(entity.companyId)
if (existsByNameAndCompany(entity.name, company)) {
throw recipeNameAlreadyExistsForCompanyException(entity.name, company)
}
return save(with(entity) {
recipe(
name = name,
@ -80,14 +91,23 @@ class RecipeServiceImpl(
sample = sample,
approbationDate = approbationDate,
remark = remark ?: "",
company = companyService.getById(companyId)
company = company
)
})
}
@Transactional
override fun update(entity: RecipeUpdateDto): Recipe {
val persistedRecipe by lazy { getById(entity.id) }
val persistedRecipe = getById(entity.id)
val name = entity.name
val company = persistedRecipe.company
if (name != null &&
name != persistedRecipe.name &&
existsByNameAndCompany(name, company)
) {
throw recipeNameAlreadyExistsForCompanyException(name, company)
}
return update(with(entity) {
recipe(
@ -99,7 +119,7 @@ class RecipeServiceImpl(
sample = sample ?: persistedRecipe.sample,
approbationDate = approbationDate ?: persistedRecipe.approbationDate,
remark = remark or persistedRecipe.remark,
company = persistedRecipe.company,
company = company,
mixes = persistedRecipe.mixes,
groupsInformation = updateGroupsInformation(persistedRecipe, entity)
)

View File

@ -1,12 +1,14 @@
package dev.fyloz.colorrecipesexplorer.service
import com.nhaarman.mockitokotlin2.*
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
import dev.fyloz.colorrecipesexplorer.model.*
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
import dev.fyloz.colorrecipesexplorer.service.files.FileService
import io.mockk.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.mock.web.MockMultipartFile
import org.springframework.web.multipart.MultipartFile
import java.io.File
@ -56,6 +58,19 @@ class RecipeServiceTest :
assertFalse(found)
}
// existsByNameAndCompany()
@Test
fun `existsByNameAndCompany() returns if a recipe exists for the given name and company in the repository`() {
setOf(true, false).forEach {
whenever(repository.existsByNameAndCompany(entity.name, company)).doReturn(it)
val exists = service.existsByNameAndCompany(entity.name, company)
assertEquals(it, exists)
}
}
// getAllByCompany()
@Test
@ -73,14 +88,40 @@ class RecipeServiceTest :
@Test
override fun `save(dto) calls and returns save() with the created entity`() {
whenever(companyService.getById(company.id!!)).doReturn(company)
doReturn(false).whenever(service).existsByNameAndCompany(entity.name, company)
withBaseSaveDtoTest(entity, entitySaveDto, service, { argThat { this.id == null && this.color == color } })
}
@Test
fun `save(dto) throw AlreadyExistsException when a recipe with the given name and company exists in the repository`() {
whenever(companyService.getById(company.id!!)).doReturn(company)
doReturn(true).whenever(service).existsByNameAndCompany(entity.name, company)
with(assertThrows<AlreadyExistsException> { service.save(entitySaveDto) }) {
this.assertErrorCode("company-name")
}
}
// update()
@Test
override fun `update(dto) calls and returns update() with the created entity`() =
override fun `update(dto) calls and returns update() with the created entity`() {
doReturn(false).whenever(service).existsByNameAndCompany(entity.name, company)
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
}
@Test
fun `update(dto) throws AlreadyExistsException when a recipe exists for the given name and company`() {
val name = "another recipe"
doReturn(entity).whenever(service).getById(entity.id!!)
doReturn(true).whenever(service).existsByNameAndCompany(name, company)
doReturn(name).whenever(entityUpdateDto).name
with(assertThrows<AlreadyExistsException> { service.update(entityUpdateDto) }) {
this.assertErrorCode("company-name")
}
}
// updatePublicData()