Ajustement de RecipeImageService pour utiliser FileService
This commit is contained in:
parent
0f649f983c
commit
361b1b2ba3
|
@ -25,230 +25,271 @@ private const val RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE = "Des messages sont re
|
|||
|
||||
private const val NOTE_GROUP_ID_NULL_MESSAGE = "Un identifiant de groupe est requis"
|
||||
|
||||
const val RECIPE_IMAGES_DIRECTORY = "images/recipes"
|
||||
|
||||
@Entity
|
||||
@Table(name = "recipe")
|
||||
data class Recipe(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
|
||||
val name: String,
|
||||
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
|
||||
val name: String,
|
||||
|
||||
val description: String,
|
||||
val description: String,
|
||||
|
||||
/** The color produced by the recipe. The string should be formatted as a hexadecimal color without the sharp (#). */
|
||||
val color: String,
|
||||
/** The color produced by the recipe. The string should be formatted as a hexadecimal color without the sharp (#). */
|
||||
val color: String,
|
||||
|
||||
/** The gloss of the color in percents. (0-100) */
|
||||
val gloss: Byte,
|
||||
/** The gloss of the color in percents. (0-100) */
|
||||
val gloss: Byte,
|
||||
|
||||
val sample: Int?,
|
||||
val sample: Int?,
|
||||
|
||||
@Column(name = "approbation_date")
|
||||
val approbationDate: LocalDate?,
|
||||
@Column(name = "approbation_date")
|
||||
val approbationDate: LocalDate?,
|
||||
|
||||
/** A remark given by the creator of the recipe. */
|
||||
val remark: String,
|
||||
/** A remark given by the creator of the recipe. */
|
||||
val remark: String,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "company_id")
|
||||
val company: Company,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "company_id")
|
||||
val company: Company,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
|
||||
val mixes: MutableList<Mix>,
|
||||
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
|
||||
val mixes: MutableList<Mix>,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val groupsInformation: Set<RecipeGroupInformation>
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val groupsInformation: Set<RecipeGroupInformation>
|
||||
) : Model {
|
||||
/** The mix types contained in this recipe. */
|
||||
val mixTypes: Collection<MixType>
|
||||
@JsonIgnore
|
||||
get() = mixes.map { it.mixType }
|
||||
|
||||
val imagesDirectoryPath
|
||||
@JsonIgnore
|
||||
@Transient
|
||||
get() = "$RECIPE_IMAGES_DIRECTORY/$id"
|
||||
|
||||
fun groupInformationForGroup(groupId: Long) =
|
||||
groupsInformation.firstOrNull { it.group.id == groupId }
|
||||
groupsInformation.firstOrNull { it.group.id == groupId }
|
||||
}
|
||||
|
||||
open class RecipeSaveDto(
|
||||
@field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
@field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
|
||||
@field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String,
|
||||
@field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String,
|
||||
|
||||
@field:NotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String,
|
||||
@field:NotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String,
|
||||
|
||||
@field:NotNull(message = RECIPE_GLOSS_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte,
|
||||
@field:NotNull(message = RECIPE_GLOSS_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte,
|
||||
|
||||
@field:Min(value = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
@field:Min(value = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
|
||||
val approbationDate: LocalDate?,
|
||||
val approbationDate: LocalDate?,
|
||||
|
||||
val remark: String?,
|
||||
val remark: String?,
|
||||
|
||||
@field:Min(value = 0, message = RECIPE_COMPANY_NULL_MESSAGE)
|
||||
val companyId: Long = -1L,
|
||||
@field:Min(value = 0, message = RECIPE_COMPANY_NULL_MESSAGE)
|
||||
val companyId: Long = -1L,
|
||||
) : EntityDto<Recipe> {
|
||||
override fun toEntity(): Recipe = recipe(
|
||||
name = name,
|
||||
description = description,
|
||||
sample = sample,
|
||||
approbationDate = approbationDate,
|
||||
remark = remark ?: "",
|
||||
company = company(id = companyId)
|
||||
name = name,
|
||||
description = description,
|
||||
sample = sample,
|
||||
approbationDate = approbationDate,
|
||||
remark = remark ?: "",
|
||||
company = company(id = companyId)
|
||||
)
|
||||
}
|
||||
|
||||
open class RecipeUpdateDto(
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
@field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
|
||||
@field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String?,
|
||||
@field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String?,
|
||||
|
||||
@field:NullOrNotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String?,
|
||||
@field:NullOrNotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String?,
|
||||
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte?,
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte?,
|
||||
|
||||
@field:NullOrSize(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
@field:NullOrSize(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
|
||||
val approbationDate: LocalDate?,
|
||||
val approbationDate: LocalDate?,
|
||||
|
||||
val remark: String?,
|
||||
val remark: String?,
|
||||
|
||||
val steps: Set<RecipeStepsDto>?
|
||||
val steps: Set<RecipeStepsDto>?
|
||||
) : EntityDto<Recipe>
|
||||
|
||||
data class RecipeOutputDto(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val color: String,
|
||||
val gloss: Byte,
|
||||
val sample: Int?,
|
||||
val approbationDate: LocalDate?,
|
||||
val remark: String?,
|
||||
val company: Company,
|
||||
val mixes: Set<Mix>,
|
||||
val groupsInformation: Set<RecipeGroupInformation>,
|
||||
val imagesUrls: Set<String>
|
||||
)
|
||||
|
||||
@Entity
|
||||
@Table(name = "recipe_group_information")
|
||||
data class RecipeGroupInformation(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id: Long?,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
val group: EmployeeGroup,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
val group: EmployeeGroup,
|
||||
|
||||
var note: String?,
|
||||
var note: String?,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_group_information_id")
|
||||
var steps: MutableSet<RecipeStep>?
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_group_information_id")
|
||||
var steps: MutableSet<RecipeStep>?
|
||||
)
|
||||
|
||||
data class RecipeStepsDto(
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE)
|
||||
val steps: Set<RecipeStep>
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE)
|
||||
val steps: Set<RecipeStep>
|
||||
)
|
||||
|
||||
data class RecipePublicDataDto(
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val recipeId: Long,
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val recipeId: Long,
|
||||
|
||||
val notes: Set<NoteDto>?,
|
||||
val notes: Set<NoteDto>?,
|
||||
|
||||
val mixesLocation: Set<MixLocationDto>?
|
||||
val mixesLocation: Set<MixLocationDto>?
|
||||
)
|
||||
|
||||
data class NoteDto(
|
||||
@field:NotNull(message = NOTE_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
@field:NotNull(message = NOTE_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
|
||||
val content: String?
|
||||
val content: String?
|
||||
)
|
||||
|
||||
// ==== DSL ====
|
||||
fun recipe(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
company: Company = company(),
|
||||
mixes: MutableList<Mix> = mutableListOf(),
|
||||
groupsInformation: Set<RecipeGroupInformation> = setOf(),
|
||||
op: Recipe.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
company: Company = company(),
|
||||
mixes: MutableList<Mix> = mutableListOf(),
|
||||
groupsInformation: Set<RecipeGroupInformation> = setOf(),
|
||||
op: Recipe.() -> Unit = {}
|
||||
) = Recipe(
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
color,
|
||||
gloss,
|
||||
sample,
|
||||
approbationDate,
|
||||
remark,
|
||||
company,
|
||||
mixes,
|
||||
groupsInformation
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
color,
|
||||
gloss,
|
||||
sample,
|
||||
approbationDate,
|
||||
remark,
|
||||
company,
|
||||
mixes,
|
||||
groupsInformation
|
||||
).apply(op)
|
||||
|
||||
fun recipeSaveDto(
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
companyId: Long = 0L,
|
||||
op: RecipeSaveDto.() -> Unit = {}
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
companyId: Long = 0L,
|
||||
op: RecipeSaveDto.() -> Unit = {}
|
||||
) = RecipeSaveDto(name, description, color, gloss, sample, approbationDate, remark, companyId).apply(op)
|
||||
|
||||
fun recipeUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
description: String? = "description",
|
||||
color: String? = "ffffff",
|
||||
gloss: Byte? = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String? = "remark",
|
||||
steps: Set<RecipeStepsDto>? = setOf(),
|
||||
op: RecipeUpdateDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
description: String? = "description",
|
||||
color: String? = "ffffff",
|
||||
gloss: Byte? = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String? = "remark",
|
||||
steps: Set<RecipeStepsDto>? = setOf(),
|
||||
op: RecipeUpdateDto.() -> Unit = {}
|
||||
) = RecipeUpdateDto(id, name, description, color, gloss, sample, approbationDate, remark, steps).apply(op)
|
||||
|
||||
fun recipeOutputDto(
|
||||
recipe: Recipe,
|
||||
imagesUrls: Set<String>,
|
||||
op: RecipeOutputDto.() -> Unit = {}
|
||||
) = RecipeOutputDto(
|
||||
recipe.id!!,
|
||||
recipe.name,
|
||||
recipe.description,
|
||||
recipe.color,
|
||||
recipe.gloss,
|
||||
recipe.sample,
|
||||
recipe.approbationDate,
|
||||
recipe.remark,
|
||||
recipe.company,
|
||||
recipe.mixes.toSet(),
|
||||
recipe.groupsInformation,
|
||||
imagesUrls
|
||||
).apply(op)
|
||||
|
||||
fun recipeGroupInformation(
|
||||
id: Long? = null,
|
||||
group: EmployeeGroup = employeeGroup(),
|
||||
note: String? = null,
|
||||
steps: MutableSet<RecipeStep>? = mutableSetOf(),
|
||||
op: RecipeGroupInformation.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
group: EmployeeGroup = employeeGroup(),
|
||||
note: String? = null,
|
||||
steps: MutableSet<RecipeStep>? = mutableSetOf(),
|
||||
op: RecipeGroupInformation.() -> Unit = {}
|
||||
) = RecipeGroupInformation(id, group, note, steps).apply(op)
|
||||
|
||||
fun recipePublicDataDto(
|
||||
recipeId: Long = 0L,
|
||||
notes: Set<NoteDto>? = null,
|
||||
mixesLocation: Set<MixLocationDto>? = null,
|
||||
op: RecipePublicDataDto.() -> Unit = {}
|
||||
recipeId: Long = 0L,
|
||||
notes: Set<NoteDto>? = null,
|
||||
mixesLocation: Set<MixLocationDto>? = null,
|
||||
op: RecipePublicDataDto.() -> Unit = {}
|
||||
) = RecipePublicDataDto(recipeId, notes, mixesLocation).apply(op)
|
||||
|
||||
fun noteDto(
|
||||
groupId: Long = 0L,
|
||||
content: String? = "note",
|
||||
op: NoteDto.() -> Unit = {}
|
||||
groupId: Long = 0L,
|
||||
content: String? = "note",
|
||||
op: NoteDto.() -> Unit = {}
|
||||
) = NoteDto(groupId, content).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
|
@ -256,30 +297,18 @@ 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"
|
||||
|
||||
class RecipeImageNotFoundException(id: Long, recipe: Recipe) :
|
||||
RestException(
|
||||
"notfound-recipeimage-id",
|
||||
"Recipe image not found",
|
||||
HttpStatus.NOT_FOUND,
|
||||
"A recipe image with the id $id could no be found for the recipe ${recipe.name}",
|
||||
mapOf(
|
||||
"id" to id,
|
||||
"recipe" to recipe.name
|
||||
)
|
||||
)
|
||||
|
||||
fun recipeIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
RECIPE_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A recipe with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
NotFoundException(
|
||||
RECIPE_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A recipe with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun recipeIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
RECIPE_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A recipe with the id $id already exists",
|
||||
id
|
||||
)
|
||||
AlreadyExistsException(
|
||||
RECIPE_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A recipe with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
|
|
@ -3,7 +3,9 @@ package dev.fyloz.colorrecipesexplorer.rest
|
|||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeEditRecipes
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeRemoveRecipes
|
||||
import dev.fyloz.colorrecipesexplorer.config.annotations.PreAuthorizeViewRecipes
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.rest.files.FILE_CONTROLLER_PATH
|
||||
import dev.fyloz.colorrecipesexplorer.service.MixService
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeImageService
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeService
|
||||
|
@ -12,7 +14,8 @@ import org.springframework.http.ResponseEntity
|
|||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.net.URI
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import javax.validation.Valid
|
||||
|
||||
|
||||
|
@ -22,69 +25,82 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix"
|
|||
@RestController
|
||||
@RequestMapping(RECIPE_CONTROLLER_PATH)
|
||||
@PreAuthorizeViewRecipes
|
||||
class RecipeController(private val recipeService: RecipeService) {
|
||||
class RecipeController(
|
||||
private val recipeService: RecipeService,
|
||||
private val recipeImageService: RecipeImageService,
|
||||
private val creProperties: CreProperties
|
||||
) {
|
||||
@GetMapping
|
||||
fun getAll() =
|
||||
ok(recipeService.getAll())
|
||||
ok(recipeService.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(recipeService.getById(id))
|
||||
ok(recipeService.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun save(@Valid @RequestBody recipe: RecipeSaveDto) =
|
||||
created<Recipe>(RECIPE_CONTROLLER_PATH) {
|
||||
recipeService.save(recipe)
|
||||
}
|
||||
created<Recipe>(RECIPE_CONTROLLER_PATH) {
|
||||
recipeService.save(recipe)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun update(@Valid @RequestBody recipe: RecipeUpdateDto) =
|
||||
noContent {
|
||||
recipeService.update(recipe)
|
||||
}
|
||||
noContent {
|
||||
recipeService.update(recipe)
|
||||
}
|
||||
|
||||
@PutMapping("public")
|
||||
@PreAuthorize("hasAuthority('EDIT_RECIPES_PUBLIC_DATA')")
|
||||
fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto) =
|
||||
noContent {
|
||||
recipeService.updatePublicData(publicDataDto)
|
||||
}
|
||||
noContent {
|
||||
recipeService.updatePublicData(publicDataDto)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorizeRemoveRecipes
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
recipeService.deleteById(id)
|
||||
}
|
||||
}
|
||||
noContent {
|
||||
recipeService.deleteById(id)
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping(RECIPE_CONTROLLER_PATH)
|
||||
@PreAuthorizeViewRecipes
|
||||
class RecipeImageController(val recipeImageService: RecipeImageService) {
|
||||
@GetMapping("{recipeId}/image")
|
||||
fun getAllIdsForRecipe(@PathVariable recipeId: Long) =
|
||||
ok(recipeImageService.getAllIdsForRecipe(recipeId))
|
||||
|
||||
@GetMapping("{recipeId}/image/{id}", produces = [MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE])
|
||||
fun getById(@PathVariable recipeId: Long, @PathVariable id: Long) =
|
||||
ok(recipeImageService.getByIdForRecipe(id, recipeId))
|
||||
|
||||
@PostMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PutMapping("{recipeId}/image", consumes = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
|
||||
@PreAuthorizeEditRecipes
|
||||
fun save(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity<Void> {
|
||||
val id = recipeImageService.save(image, recipeId)
|
||||
return ResponseEntity.created(URI.create("/$RECIPE_CONTROLLER_PATH/$recipeId/image/$id")).build()
|
||||
fun downloadImage(@PathVariable recipeId: Long, image: MultipartFile): ResponseEntity<RecipeOutputDto> {
|
||||
recipeImageService.download(image, recipeService.getById(recipeId))
|
||||
return getById(recipeId)
|
||||
}
|
||||
|
||||
@DeleteMapping("{recipeId}/image/{id}")
|
||||
@PreAuthorizeRemoveRecipes
|
||||
fun delete(@PathVariable recipeId: Long, @PathVariable id: Long) =
|
||||
noContent {
|
||||
recipeImageService.delete(id, recipeId)
|
||||
}
|
||||
@DeleteMapping("{recipeId}/image/{name}")
|
||||
@PreAuthorizeEditRecipes
|
||||
fun deleteImage(@PathVariable recipeId: Long, @PathVariable name: String) =
|
||||
noContent {
|
||||
recipeImageService.delete(recipeService.getById(recipeId), name)
|
||||
}
|
||||
|
||||
private fun ok(recipe: Recipe) =
|
||||
ok(recipe.toOutput())
|
||||
|
||||
private fun ok(recipes: Collection<Recipe>) =
|
||||
ok(recipes.map { it.toOutput() })
|
||||
|
||||
private fun Recipe.toOutput() =
|
||||
recipeOutputDto(
|
||||
this,
|
||||
recipeImageService.getAllImages(this)
|
||||
.map { this.imageUrl(it) }
|
||||
.toSet()
|
||||
)
|
||||
|
||||
private fun Recipe.imageUrl(name: String) =
|
||||
"${creProperties.deploymentUrl}$FILE_CONTROLLER_PATH?path=${
|
||||
URLEncoder.encode(
|
||||
"${this.imagesDirectoryPath}/$name",
|
||||
StandardCharsets.UTF_8
|
||||
)
|
||||
}"
|
||||
}
|
||||
|
||||
@RestController
|
||||
|
@ -93,26 +109,26 @@ class RecipeImageController(val recipeImageService: RecipeImageService) {
|
|||
class MixController(private val mixService: MixService) {
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(mixService.getById(id))
|
||||
ok(mixService.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun save(@Valid @RequestBody mix: MixSaveDto) =
|
||||
created<Mix>(MIX_CONTROLLER_PATH) {
|
||||
mixService.save(mix)
|
||||
}
|
||||
created<Mix>(MIX_CONTROLLER_PATH) {
|
||||
mixService.save(mix)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun update(@Valid @RequestBody mix: MixUpdateDto) =
|
||||
noContent {
|
||||
mixService.update(mix)
|
||||
}
|
||||
noContent {
|
||||
mixService.update(mix)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorizeRemoveRecipes
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
mixService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
mixService.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ class MaterialServiceImpl(
|
|||
|
||||
override fun delete(entity: Material) {
|
||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
|
||||
fileService.delete(entity.simdutFilePath)
|
||||
super.delete(entity)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.springframework.context.annotation.Lazy
|
|||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.io.File
|
||||
import java.nio.file.NoSuchFileException
|
||||
import javax.transaction.Transactional
|
||||
|
||||
interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository> {
|
||||
|
@ -139,63 +138,69 @@ class RecipeServiceImpl(
|
|||
update(mix.recipe.apply { mixes.remove(mix) })
|
||||
}
|
||||
|
||||
const val RECIPE_IMAGES_DIRECTORY = "images/recipe"
|
||||
|
||||
interface RecipeImageService {
|
||||
// TOOD change return type to ByteArrayResource
|
||||
fun getByIdForRecipe(id: Long, recipeId: Long): ByteArray
|
||||
/** Gets the name of every images associated to the recipe with the given [recipe]. */
|
||||
fun getAllImages(recipe: Recipe): Set<String>
|
||||
|
||||
/** Gets the identifier of every images associated to the recipe with the given [recipeId]. */
|
||||
fun getAllIdsForRecipe(recipeId: Long): Collection<Long>
|
||||
/** Saves the given [image] and associate it to the recipe with the given [recipe]. Returns the name of the saved image. */
|
||||
fun download(image: MultipartFile, recipe: Recipe): String
|
||||
|
||||
/** Saves the given [image] and associate it to the recipe with the given [recipeId]. Returns the identifier of the saved image. */
|
||||
fun save(image: MultipartFile, recipeId: Long): Long
|
||||
/** Deletes the image with the given [name] for the given [recipe]. */
|
||||
fun delete(recipe: Recipe, name: String)
|
||||
|
||||
/** Deletes the image with the given [recipeId] and [id]. */
|
||||
fun delete(id: Long, recipeId: Long)
|
||||
/** Gets the directory containing all images of the given [Recipe]. */
|
||||
fun Recipe.getDirectory(): File
|
||||
}
|
||||
|
||||
@Service
|
||||
class RecipeImageServiceImpl(val recipeService: RecipeService, val fileService: FileService) : RecipeImageService {
|
||||
override fun getByIdForRecipe(id: Long, recipeId: Long): ByteArray =
|
||||
try {
|
||||
fileService.read(getPath(id, recipeId)).byteArray
|
||||
} catch (ex: NoSuchFileException) {
|
||||
throw RecipeImageNotFoundException(id, recipeService.getById(recipeId))
|
||||
}
|
||||
const val RECIPE_IMAGE_ID_DELIMITER = "_"
|
||||
const val RECIPE_IMAGE_EXTENSION = ".jpg"
|
||||
|
||||
override fun getAllIdsForRecipe(recipeId: Long): Collection<Long> {
|
||||
val recipe = recipeService.getById(recipeId)
|
||||
val recipeDirectory = getRecipeDirectory(recipe.id!!)
|
||||
@Service
|
||||
class RecipeImageServiceImpl(
|
||||
val recipeService: RecipeService,
|
||||
val fileService: FileService
|
||||
) : RecipeImageService {
|
||||
override fun getAllImages(recipe: Recipe): Set<String> {
|
||||
val recipeDirectory = recipe.getDirectory()
|
||||
if (!recipeDirectory.exists() || !recipeDirectory.isDirectory) {
|
||||
return listOf()
|
||||
return setOf()
|
||||
}
|
||||
return recipeDirectory.listFiles()!! // Should never be null because we check if recipeDirectory is a directory and exists before
|
||||
return recipeDirectory.listFiles()!! // Should never be null because we check if recipeDirectory exists and is a directory before
|
||||
.filterNotNull()
|
||||
.map { it.name.toLong() }
|
||||
.map { it.name }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
override fun save(image: MultipartFile, recipeId: Long): Long {
|
||||
/** Gets the next id available for a new image for the recipe with the given [recipeId]. */
|
||||
override fun download(image: MultipartFile, recipe: Recipe): String {
|
||||
/** Gets the next id available for a new image for the given [recipe]. */
|
||||
fun getNextAvailableId(): Long =
|
||||
with(getAllIdsForRecipe(recipeId)) {
|
||||
with(getAllImages(recipe)) {
|
||||
if (isEmpty())
|
||||
0
|
||||
else
|
||||
maxOrNull()!! + 1L // maxOrNull() cannot return null because existingIds cannot be empty at this point
|
||||
maxOf {
|
||||
it.split(RECIPE_IMAGE_ID_DELIMITER)
|
||||
.last()
|
||||
.replace(RECIPE_IMAGE_EXTENSION, "")
|
||||
.toLong()
|
||||
} + 1L
|
||||
}
|
||||
|
||||
val nextAvailableId = getNextAvailableId()
|
||||
fileService.write(image, getPath(nextAvailableId, recipeId), true)
|
||||
return nextAvailableId
|
||||
return getImageFileName(recipe, getNextAvailableId()).apply {
|
||||
fileService.write(image, getImagePath(recipe, this), true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(id: Long, recipeId: Long) =
|
||||
fileService.delete(getPath(id, recipeId))
|
||||
override fun delete(recipe: Recipe, name: String) =
|
||||
fileService.delete(getImagePath(recipe, name))
|
||||
|
||||
/** Gets the images directory of the recipe with the given [recipeId]. */
|
||||
fun getRecipeDirectory(recipeId: Long) = File("$RECIPE_IMAGES_DIRECTORY/$recipeId")
|
||||
override fun Recipe.getDirectory(): File = File(with(fileService) {
|
||||
this@getDirectory.imagesDirectoryPath.fullPath().path
|
||||
})
|
||||
|
||||
/** Gets the file of the image with the given [recipeId] and [id]. */
|
||||
fun getPath(id: Long, recipeId: Long): String = "$RECIPE_IMAGES_DIRECTORY/$recipeId/$id"
|
||||
fun getImageFileName(recipe: Recipe, id: Long) =
|
||||
"${recipe.name}$RECIPE_IMAGE_ID_DELIMITER$id"
|
||||
|
||||
fun getImagePath(recipe: Recipe, name: String) =
|
||||
"${recipe.imagesDirectoryPath}/$name$RECIPE_IMAGE_EXTENSION"
|
||||
}
|
||||
|
|
|
@ -141,7 +141,6 @@ class MaterialServiceTest :
|
|||
val mockSimdutFile = MockMultipartFile("simdut", byteArrayOf(1, 2, 3, 4, 5))
|
||||
val materialUpdateDto = spy(materialUpdateDto(id = 0L, simdutFile = mockSimdutFile))
|
||||
|
||||
// doReturn(entity).whenever(service).getById(materialUpdateDto.id)
|
||||
doReturn(entity).whenever(service).getById(any())
|
||||
doReturn(entity).whenever(service).update(any<Material>())
|
||||
doReturn(entity).whenever(materialUpdateDto).toEntity()
|
||||
|
|
|
@ -113,7 +113,7 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
|||
override fun `update(dto) calls and returns update() with the created entity`() {
|
||||
val mixUpdateDto = spy(mixUpdateDto(id = 0L, name = null, materialTypeId = null))
|
||||
|
||||
doReturn(entity).whenever(service).getById(mixUpdateDto.id)
|
||||
doReturn(entity).whenever(service).getById(any())
|
||||
doReturn(entity).whenever(service).update(entity)
|
||||
|
||||
val found = service.update(mixUpdateDto)
|
||||
|
|
|
@ -4,13 +4,11 @@ import com.nhaarman.mockitokotlin2.*
|
|||
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.core.io.ByteArrayResource
|
||||
import org.springframework.mock.web.MockMultipartFile
|
||||
import java.io.File
|
||||
import java.nio.file.NoSuchFileException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
@ -165,129 +163,100 @@ class RecipeServiceTest :
|
|||
}
|
||||
}
|
||||
|
||||
private class RecipeImageServiceTestContext {
|
||||
val fileService = mockk<FileService> {
|
||||
every { write(any(), any(), any()) } just Runs
|
||||
every { delete(any()) } just Runs
|
||||
}
|
||||
val recipeImageService = spyk(RecipeImageServiceImpl(mockk(), fileService))
|
||||
val recipe = spyk(recipe())
|
||||
val recipeImagesIds = setOf(1L, 10L, 21L)
|
||||
val recipeImagesNames = recipeImagesIds.map { it.imageName }.toSet()
|
||||
val recipeImagesFiles = recipeImagesNames.map { File(it) }.toTypedArray()
|
||||
val recipeDirectory = spyk(File(recipe.imagesDirectoryPath)) {
|
||||
every { exists() } returns true
|
||||
every { isDirectory } returns true
|
||||
every { listFiles() } returns recipeImagesFiles
|
||||
}
|
||||
|
||||
init {
|
||||
with(recipeImageService) {
|
||||
every { recipe.getDirectory() } returns recipeDirectory
|
||||
}
|
||||
}
|
||||
|
||||
val Long.imageName
|
||||
get() = "${recipe.name}$RECIPE_IMAGE_ID_DELIMITER$this"
|
||||
|
||||
val String.imagePath
|
||||
get() = "${recipe.imagesDirectoryPath}/$this$RECIPE_IMAGE_EXTENSION"
|
||||
}
|
||||
|
||||
class RecipeImageServiceTest {
|
||||
private val recipeService: RecipeService = mock()
|
||||
private val fileService: FileService = mock()
|
||||
private val service = spy(RecipeImageServiceImpl(recipeService, fileService))
|
||||
|
||||
private val recipeId = 1L
|
||||
private val imageId = 5L
|
||||
private val imagePath = "$RECIPE_IMAGES_DIRECTORY/$recipeId/$imageId"
|
||||
private val recipe = recipe(id = recipeId)
|
||||
private val recipeDirectory: File = mock()
|
||||
private val imagesIds = listOf(1L, 3L, 10L, 21L)
|
||||
private val imageData = byteArrayOf(64, 32, 16, 8, 4, 2, 1)
|
||||
private val image = MockMultipartFile("$imageId", imageData)
|
||||
|
||||
@AfterEach
|
||||
internal fun tearDown() {
|
||||
reset(recipeService, fileService, service, recipeDirectory)
|
||||
internal fun afterEach() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
// getByIdForRecipe()
|
||||
private fun test(test: RecipeImageServiceTestContext.() -> Unit) {
|
||||
RecipeImageServiceTestContext().test()
|
||||
}
|
||||
|
||||
// getAllImages()
|
||||
|
||||
@Test
|
||||
fun `getByIdForRecipe() returns data for the given recipe and image id red by the file service`() {
|
||||
whenever(fileService.read(imagePath)).doReturn(ByteArrayResource(imageData))
|
||||
fun `getAllImages() returns a Set containing the name of every files in the recipe's directory`() {
|
||||
test {
|
||||
val foundImagesNames = recipeImageService.getAllImages(recipe)
|
||||
|
||||
val found = service.getByIdForRecipe(imageId, recipeId)
|
||||
|
||||
assertEquals(imageData, found)
|
||||
assertEquals(recipeImagesNames, foundImagesNames)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getByIdForRecipe() throws RecipeImageNotFoundException when no image with the given recipe and image id exists`() {
|
||||
doReturn(imagePath).whenever(service).getPath(imageId, recipeId)
|
||||
whenever(recipeService.getById(recipeId)).doReturn(recipe)
|
||||
whenever(fileService.read(imagePath)).doAnswer { throw NoSuchFileException(imagePath) }
|
||||
fun `getAllImages() returns an empty Set when the recipe's directory does not exists`() {
|
||||
test {
|
||||
every { recipeDirectory.exists() } returns false
|
||||
|
||||
assertThrows<RecipeImageNotFoundException> { service.getByIdForRecipe(imageId, recipeId) }
|
||||
assertTrue {
|
||||
recipeImageService.getAllImages(recipe).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getAllIdsForRecipe()
|
||||
// download()
|
||||
|
||||
@Test
|
||||
fun `getAllIdsForRecipe() returns a list containing all image's identifier of the images of the given recipe`() {
|
||||
val expectedFiles = imagesIds.map { File(it.toString()) }.toTypedArray()
|
||||
fun `download() writes the given image to the FileService and returns its name`() {
|
||||
test {
|
||||
val mockImage = MockMultipartFile("image.jpg", byteArrayOf(*"Random data".encodeToByteArray()))
|
||||
val expectedImageId = recipeImagesIds.maxOrNull()!! + 1L
|
||||
val expectedImageName = expectedImageId.imageName
|
||||
val expectedImagePath = expectedImageName.imagePath
|
||||
|
||||
whenever(recipeService.getById(recipeId)).doReturn(recipe)
|
||||
whenever(recipeDirectory.exists()).doReturn(true)
|
||||
whenever(recipeDirectory.isDirectory).doReturn(true)
|
||||
whenever(recipeDirectory.listFiles()).doReturn(expectedFiles)
|
||||
doReturn(recipeDirectory).whenever(service).getRecipeDirectory(recipeId)
|
||||
val foundImageName = recipeImageService.download(mockImage, recipe)
|
||||
|
||||
val found = service.getAllIdsForRecipe(recipeId)
|
||||
assertEquals(expectedImageName, foundImageName)
|
||||
|
||||
assertEquals(imagesIds, found)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAllIdsForRecipe() returns an empty list when the given recipe's directory does not exists`() {
|
||||
whenever(recipeService.getById(recipeId)).doReturn(recipe)
|
||||
whenever(recipeDirectory.exists()).doReturn(false)
|
||||
whenever(recipeDirectory.isDirectory).doReturn(true)
|
||||
doReturn(recipeDirectory).whenever(service).getRecipeDirectory(recipeId)
|
||||
|
||||
val found = service.getAllIdsForRecipe(recipeId)
|
||||
|
||||
assertTrue(found.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAllIdsForRecipe() returns an empty list when the given recipe's directory is not a directory`() {
|
||||
whenever(recipeService.getById(recipeId)).doReturn(recipe)
|
||||
whenever(recipeDirectory.exists()).doReturn(true)
|
||||
whenever(recipeDirectory.isDirectory).doReturn(false)
|
||||
doReturn(recipeDirectory).whenever(service).getRecipeDirectory(recipeId)
|
||||
|
||||
val found = service.getAllIdsForRecipe(recipeId)
|
||||
|
||||
assertTrue(found.isEmpty())
|
||||
}
|
||||
|
||||
// save()
|
||||
|
||||
@Test
|
||||
fun `save() writes the given image to the file service with the expected path`() {
|
||||
val expectedNextAvailableId = imagesIds.maxOrNull()!! + 1
|
||||
val imagePath = "$RECIPE_IMAGES_DIRECTORY/$recipeId/$expectedNextAvailableId"
|
||||
|
||||
doReturn(imagesIds).whenever(service).getAllIdsForRecipe(recipeId)
|
||||
doReturn(imagePath).whenever(service).getPath(expectedNextAvailableId, recipeId)
|
||||
|
||||
service.save(image, recipeId)
|
||||
|
||||
verify(fileService).write(image, imagePath, true)
|
||||
verify {
|
||||
fileService.write(mockImage, expectedImagePath, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete()
|
||||
|
||||
@Test
|
||||
fun `delete() deletes the image with the given recipe and image id from the file service`() {
|
||||
doReturn(imagePath).whenever(service).getPath(imageId, recipeId)
|
||||
fun `delete() deletes the image with the given name in the FileService`() {
|
||||
test {
|
||||
val imageName = recipeImagesIds.first().imageName
|
||||
val imagePath = imageName.imagePath
|
||||
|
||||
service.delete(imageId, recipeId)
|
||||
recipeImageService.delete(recipe, imageName)
|
||||
|
||||
verify(fileService).delete(imagePath)
|
||||
}
|
||||
|
||||
// getRecipeDirectory()
|
||||
|
||||
@Test
|
||||
fun `getRecipeDirectory() returns a file with the expected path`() {
|
||||
val recipeDirectoryPath = "$RECIPE_IMAGES_DIRECTORY/$recipeId"
|
||||
|
||||
val found = service.getRecipeDirectory(recipeId)
|
||||
|
||||
assertEquals(recipeDirectoryPath, found.path)
|
||||
}
|
||||
|
||||
// getPath()
|
||||
|
||||
@Test
|
||||
fun `getPath() returns the expected path`() {
|
||||
val found = service.getPath(imageId, recipeId)
|
||||
|
||||
assertEquals(imagePath, found)
|
||||
verify {
|
||||
fileService.delete(imagePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ 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
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
@ -23,33 +22,23 @@ private val mockFilePathPath = Path.of(mockFilePath)
|
|||
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
||||
|
||||
private class FileServiceTestContext {
|
||||
val fileService: FileService
|
||||
val mockFile: File
|
||||
val mockFileFullPath: FilePath
|
||||
val mockMultipartFile: MultipartFile
|
||||
|
||||
init {
|
||||
fileService = spyk(FileServiceImpl(creProperties, mockk {
|
||||
every { error(any(), any<Exception>()) } just Runs
|
||||
}))
|
||||
|
||||
mockFile = mockk {
|
||||
every { path } returns mockFilePath
|
||||
every { exists() } returns true
|
||||
every { isFile } returns true
|
||||
every { toPath() } returns mockFilePathPath
|
||||
}
|
||||
|
||||
mockFileFullPath = spyk(FilePath("${creProperties.workingDirectory}/$mockFilePath")) {
|
||||
every { file } returns mockFile
|
||||
|
||||
with(fileService) {
|
||||
every { mockFilePath.fullPath() } returns this@spyk
|
||||
}
|
||||
}
|
||||
|
||||
mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData))
|
||||
val fileService = spyk(FileServiceImpl(creProperties, mockk {
|
||||
every { error(any(), any<Exception>()) } just Runs
|
||||
}))
|
||||
val mockFile = mockk<File> {
|
||||
every { path } returns mockFilePath
|
||||
every { exists() } returns true
|
||||
every { isFile } returns true
|
||||
every { toPath() } returns mockFilePathPath
|
||||
}
|
||||
val mockFileFullPath = spyk(FilePath("${creProperties.workingDirectory}/$mockFilePath")) {
|
||||
every { file } returns mockFile
|
||||
|
||||
with(fileService) {
|
||||
every { mockFilePath.fullPath() } returns this@spyk
|
||||
}
|
||||
}
|
||||
val mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData))
|
||||
}
|
||||
|
||||
class FileServiceTest {
|
||||
|
|
Loading…
Reference in New Issue