From 2bd59e72c60fe8e56f09d35b4c44a7570f13904b Mon Sep 17 00:00:00 2001 From: William Nolin Date: Tue, 31 Aug 2021 10:36:13 -0400 Subject: [PATCH 1/5] #13 Add icon and logo to resources --- src/main/resources/images/favicon.png | Bin 0 -> 6745 bytes src/main/resources/images/logo.png | Bin 0 -> 2338 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/images/favicon.png create mode 100644 src/main/resources/images/logo.png diff --git a/src/main/resources/images/favicon.png b/src/main/resources/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1e877b7c56f791c9d054152b2bc3f64b2e394de2 GIT binary patch literal 6745 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/images/logo.png b/src/main/resources/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..08822c80f11c5da7d23f4d739afe4904d62c71d6 GIT binary patch literal 2338 zcmV+-3ElRIP)EX>4Tx04R}tkv&MmKpe$iQ>7wR5eteaWT;LSL`5963Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT3oT+iIPS;0dyl(!fKV+m&1xG5G~G56 zv8b3zuZn?J_|cDU^kP_MmN6$uad?ied-(Wz7vWjn=l&dBYQ|!KPb8jYhG`RT5KnK~ z2Iqa^5X;Ld@j3CBNf#u3x%5W^ytAVGwJG72cdMub+K6blL3k9znAUB5&wg+6mx)2Cnp`zf=WgK1r`M zweS(pvkhEaH#KDsxZD8-o($QPUCB>V$Yz1}Gy0}9(02=TuGM>M?BnzSNK#kv8{ps& z7|l`ky2raaT6_EVOryUaL!fe$BtIkI00006VoOIv00000008+zyMF)x010qNS#tmY zE+YT{E+YYWr9XB6000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00#O=L_t(|+U=ZsOcX~P$G_lt6cr0ZsK6N;RK!c57J*<5 zZ8ScLB!vbOOMBuEQh>Cy@8Rk?^5Yv$3_~q)xt(avFB5z5iHl~OMKnGJQ&#;IHw_zWrxng)5x!g`H=UKdh&Ik@>1vNFyV3t#6 zVvimvn5*mfNMbZTW9QCoB~gc1L_77%laq0yv=r-neKE-_E!tq(GzGf5$LX5O)*0d9 zM^Ij#Oc$WmdX4IrmWG>q_rlxW-mL9rXI~iiV+jh{hk}BoWTLf+I%nfXlq4j;$=1A~ z>-G0=^l06L9MhULrx6g~Og0K25mT`CUS7yKbOojn`QlPABkeMLnQy1R8Fjx8l689LqT?zFduql*hia7-icv|w6$@R^_#n_xpr;P=bMxICut$D(dAa z2M6q1yjXCPtgMYtDtj#YFq$7c5Uig*{R7IH#GLid4-bc*RLWc5-(P~**gKYgAT~CF z)jA!00aVnY%GRwDtiO6SLlm9uIe$KXM$0(V*FPmA#Vo2mLXN( ziIl#+T(q|v$Vf4Z8a8z*Z=K1cL2>azi;jP0NCIh1MWyzeXc`0Jct|>-4U!a_ri*7@?uRkb%~6jmovPLB|-RJd5QL ziI=E4ytnr_+}t*gN!!DHda9>OR3upM;j!N$ZwFqDio&IrFL~>?ZaqowN?DbtWo0-nuuNrR zV~6bQM2m}C*WHbsyLZFHQ!kbJATMtdS!gYy0zlr`vx4<9SqzlQpGMri@JY&-+`Bs~J)vM9Whmz|h`~=$stfiH;WBZqA&qkd?I>iHY~n)YOcQ4i)

y1MQ@g{$ zP&8g2h%z%N@$ujo|9erfu_tI1K@Xd-9xcK<7=sNr1sZ)q5FXye7N_~-^ zznQGG22lY(TU(1TA0MP$xdM&JB&yhd->?A{9RFu@yuJ4!Gt-~Uv<6XM*{Vg-^5yt6 zDhh=a6;K&Q_F|k~--GJvVq|9*kK2q8edLJXHZd`|2nuo}JH?z#@s+8bo@~dlW31@< zbynBh%(|XGXZ;+<3?>sZ8ue^o;4h}ps90NDBde}fvZA6~wrkf0=ISb)ko)Xut*wIX zH8$>}Wa_PA^ZE~hpsAlANL1pzz+&%ArKYCR*%@b&l9DVoHjBMRWsEMJ$6ba&KH^4G zKS79f>Q7Hkr?W#?{?tzpH1!iCDnXE_1c^!zBq~9WsKoog-#&w-3(eC)1ONa407*qo IM6N<$g1U)NkpKVy literal 0 HcmV?d00001 From a02099387e844e237f9ec5b1e0e1f1732302a906 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Wed, 1 Sep 2021 17:53:19 -0400 Subject: [PATCH 2/5] #13 Add icon and logo endpoints to ConfigurationController --- .../model/Configuration.kt | 12 +--- .../rest/ConfigurationController.kt | 31 +++++++-- .../rest/FileController.kt | 11 ++-- .../colorrecipesexplorer/rest/RestUtils.kt | 13 ++++ .../rest/TouchUpKitController.kt | 4 +- .../service/MaterialService.kt | 3 +- .../service/RecipeService.kt | 3 +- .../service/TouchUpKitService.kt | 9 ++- .../service/config/ConfigurationService.kt | 61 ++++++++++++++---- .../service/config/ConfigurationSource.kt | 2 +- .../service/{ => files}/FileService.kt | 15 +++-- .../service/files/ResourceFileService.kt | 26 ++++++++ .../images/{favicon.png => icon.png} | Bin 13 files changed, 143 insertions(+), 47 deletions(-) rename src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/{ => files}/FileService.kt (96%) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt rename src/main/resources/images/{favicon.png => icon.png} (100%) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt index b913523..990551c 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Configuration.kt @@ -100,19 +100,13 @@ enum class ConfigurationType( val secure: Boolean = false ) { INSTANCE_NAME("instance.name", defaultContent = "Color Recipes Explorer", public = true), - INSTANCE_LOGO_PATH("instance.logo.path", defaultContent = "images/logo", public = true), - INSTANCE_ICON_PATH("instance.icon.path", defaultContent = "images/icon", public = true), + INSTANCE_LOGO_SET("instance.logo.set", defaultContent = false, public = true), + INSTANCE_ICON_SET("instance.icon.set", defaultContent = false, public = true), INSTANCE_URL("instance.url", "http://localhost:9090", public = true), DATABASE_URL("database.url", defaultContent = "mysql://localhost/cre", file = true, requireRestart = true), DATABASE_USER("database.user", defaultContent = "cre", file = true, requireRestart = true), - DATABASE_PASSWORD( - "database.password", - defaultContent = "asecurepassword", - file = true, - requireRestart = true, - secure = true - ), + DATABASE_PASSWORD("database.password", defaultContent = "asecurepassword", file = true, requireRestart = true, secure = true), DATABASE_SUPPORTED_VERSION("database.version.supported", computed = true), RECIPE_APPROBATION_EXPIRATION("recipe.approbation.expiration", defaultContent = 4.months), diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt index 1e7cff4..5e30ea6 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt @@ -7,6 +7,7 @@ import dev.fyloz.colorrecipesexplorer.model.account.Permission import dev.fyloz.colorrecipesexplorer.model.account.toAuthority import dev.fyloz.colorrecipesexplorer.restartApplication import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService +import org.springframework.http.MediaType import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.core.Authentication import org.springframework.web.bind.annotation.* @@ -33,17 +34,35 @@ class ConfigurationController(val configurationService: ConfigurationService) { configurationService.set(configurations) } - @PutMapping("image") - @PreAuthorize("hasAuthority('ADMIN')") - fun setImage(@RequestParam @NotBlank key: String, @RequestParam @NotBlank image: MultipartFile) = noContent { - configurationService.set(ConfigurationImageDto(key, image)) - } - @PostMapping("restart") @PreAuthorize("hasAuthority('ADMIN')") fun restart() = noContent { restartApplication() } + + // Icon + + @GetMapping("icon") + fun getIcon() = + ok(configurationService.getConfiguredIcon(), MediaType.IMAGE_PNG_VALUE) + + @PutMapping("icon") + @PreAuthorize("hasAuthority('ADMIN')") + fun setIcon(@RequestParam icon: MultipartFile) = noContent { + configurationService.setConfiguredIcon(icon) + } + + // Logo + + @GetMapping("logo") + fun getLogo() = + ok(configurationService.getConfiguredLogo(), MediaType.IMAGE_PNG_VALUE) + + @PutMapping("logo") + @PreAuthorize("hasAuthority('ADMIN')") + fun setLogo(@RequestParam logo: MultipartFile) = noContent { + configurationService.setConfiguredLogo(logo) + } } private fun Authentication?.hasAuthority(configuration: ConfigurationBase) = when { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt index 5f1e689..c7879d6 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt @@ -2,8 +2,9 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.model.ConfigurationType import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService -import dev.fyloz.colorrecipesexplorer.service.FileService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import org.springframework.core.io.ByteArrayResource +import org.springframework.core.io.Resource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize @@ -12,19 +13,18 @@ import org.springframework.web.multipart.MultipartFile import java.net.URI const val FILE_CONTROLLER_PATH = "/api/file" -private const val DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE @RestController @RequestMapping(FILE_CONTROLLER_PATH) class FileController( - private val fileService: FileService, + private val fileService: WriteableFileService, private val configService: ConfigurationService ) { @GetMapping(produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE]) fun upload( @RequestParam path: String, @RequestParam(required = false) mediaType: String? - ): ResponseEntity { + ): ResponseEntity { val file = fileService.read(path) return ResponseEntity.ok() .header("Content-Disposition", "filename=${getFileNameFromPath(path)}") @@ -56,7 +56,4 @@ class FileController( ResponseEntity .created(URI.create("${configService.get(ConfigurationType.INSTANCE_URL)}$FILE_CONTROLLER_PATH?path=$path")) .build() - - private fun getFileNameFromPath(path: String) = - path.split("/").last() } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt index 23d59da..36b892e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt @@ -2,12 +2,14 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.model.Model +import org.springframework.core.io.Resource import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import java.net.URI +const val DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE lateinit var CRE_PROPERTIES: CreProperties /** Creates a HTTP OK [ResponseEntity] from the given [body]. */ @@ -24,6 +26,14 @@ fun ok(action: () -> Unit): ResponseEntity { return ResponseEntity.ok().build() } +fun ok(file: Resource, mediaType: String? = null): ResponseEntity { + return ResponseEntity.ok() + .header("Content-Disposition", "filename=${file.filename}") + .contentLength(file.contentLength()) + .contentType(MediaType.parseMediaType(mediaType ?: DEFAULT_MEDIA_TYPE)) + .body(file) +} + /** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */ fun created(controllerPath: String, body: T): ResponseEntity = created(controllerPath, body, body.id!!) @@ -63,3 +73,6 @@ fun httpHeaders( op() } + +fun getFileNameFromPath(path: String) = + path.split("/").last() diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt index e9cbe47..027d71d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/TouchUpKitController.kt @@ -5,7 +5,7 @@ import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitSaveDto import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitUpdateDto import dev.fyloz.colorrecipesexplorer.service.TouchUpKitService import org.springframework.context.annotation.Profile -import org.springframework.core.io.ByteArrayResource +import org.springframework.core.io.Resource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize @@ -57,7 +57,7 @@ class TouchUpKitController( } @GetMapping("pdf") - fun getJobPdf(@RequestParam project: String): ResponseEntity { + fun getJobPdf(@RequestParam project: String): ResponseEntity { with(touchUpKitService.generateJobPdfResource(project)) { return ResponseEntity.ok() .header("Content-Disposition", "filename=TouchUpKit_$project.pdf") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt index 7d752f9..327d6e2 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialService.kt @@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import io.jsonwebtoken.lang.Assert import org.springframework.context.annotation.Lazy import org.springframework.context.annotation.Profile @@ -39,7 +40,7 @@ class MaterialServiceImpl( val recipeService: RecipeService, val mixService: MixService, @Lazy val materialTypeService: MaterialTypeService, - val fileService: FileService, + val fileService: WriteableFileService, val configService: ConfigurationService ) : AbstractExternalNamedModelService( diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt index 4dcda07..4361728 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeService.kt @@ -5,6 +5,7 @@ import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.model.validation.or import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import dev.fyloz.colorrecipesexplorer.utils.setAll import org.springframework.context.annotation.Lazy import org.springframework.context.annotation.Profile @@ -222,7 +223,7 @@ const val RECIPE_IMAGE_EXTENSION = ".jpg" @Service @Profile("!emergency") class RecipeImageServiceImpl( - val fileService: FileService + val fileService: WriteableFileService ) : RecipeImageService { override fun getAllImages(recipe: Recipe): Set { val recipeDirectory = recipe.getDirectory() diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitService.kt index b966525..a6bbc1e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitService.kt @@ -5,9 +5,12 @@ import dev.fyloz.colorrecipesexplorer.model.touchupkit.* import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository import dev.fyloz.colorrecipesexplorer.rest.TOUCH_UP_KIT_CONTROLLER_PATH import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService +import dev.fyloz.colorrecipesexplorer.service.files.FileService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import dev.fyloz.colorrecipesexplorer.utils.* import org.springframework.context.annotation.Profile import org.springframework.core.io.ByteArrayResource +import org.springframework.core.io.Resource import org.springframework.stereotype.Service import java.time.LocalDate import java.time.Period @@ -32,7 +35,7 @@ interface TouchUpKitService : * If TOUCH_UP_KIT_CACHE_PDF is enabled and a file exists for the job, its content will be returned. * If caching is enabled but no file exists for the job, the generated ByteArrayResource will be cached on the disk. */ - fun generateJobPdfResource(job: String): ByteArrayResource + fun generateJobPdfResource(job: String): Resource /** Writes the given [document] to the [FileService] if TOUCH_UP_KIT_CACHE_PDF is enabled. */ fun String.cachePdfDocument(document: PdfDocument) @@ -41,7 +44,7 @@ interface TouchUpKitService : @Service @Profile("!emergency") class TouchUpKitServiceImpl( - private val fileService: FileService, + private val fileService: WriteableFileService, private val configService: ConfigurationService, touchUpKitRepository: TouchUpKitRepository ) : AbstractExternalModelService( @@ -120,7 +123,7 @@ class TouchUpKitServiceImpl( } } - override fun generateJobPdfResource(job: String): ByteArrayResource { + override fun generateJobPdfResource(job: String): Resource { if (cacheGeneratedFiles) { with(job.pdfDocumentPath()) { if (fileService.exists(this)) { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationService.kt index 8e5d0c2..302c3b3 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationService.kt @@ -2,13 +2,16 @@ package dev.fyloz.colorrecipesexplorer.service.config import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties import dev.fyloz.colorrecipesexplorer.model.* -import dev.fyloz.colorrecipesexplorer.service.FileService +import dev.fyloz.colorrecipesexplorer.service.files.ResourceFileService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import dev.fyloz.colorrecipesexplorer.utils.decrypt import dev.fyloz.colorrecipesexplorer.utils.encrypt import org.slf4j.Logger import org.springframework.context.annotation.Lazy +import org.springframework.core.io.Resource import org.springframework.security.crypto.keygen.KeyGenerators import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile interface ConfigurationService { /** Gets all set configurations. */ @@ -35,6 +38,12 @@ interface ConfigurationService { /** Gets the content of the secure configuration with the given [type]. Should not be accessible to the users. */ fun getSecure(type: ConfigurationType): String + /** Gets the app's icon. */ + fun getConfiguredIcon(): Resource + + /** Gets the app's logo. */ + fun getConfiguredLogo(): Resource + /** Sets the content of each configuration in the given [configurations] list. */ fun set(configurations: List) @@ -47,20 +56,26 @@ interface ConfigurationService { /** Sets the content given [configuration]. */ fun set(configuration: Configuration) - /** Sets the content of the configuration matching the given [configuration] with a given image. */ - fun set(configuration: ConfigurationImageDto) + /** Sets the app's icon. */ + fun setConfiguredIcon(icon: MultipartFile) + + /** Sets the app's logo. */ + fun setConfiguredLogo(logo: MultipartFile) /** Initialize the properties matching the given [predicate]. */ fun initializeProperties(predicate: (ConfigurationType) -> Boolean) } +const val CONFIGURATION_LOGO_RESOURCE_PATH = "images/logo.png" const val CONFIGURATION_LOGO_FILE_PATH = "images/logo" +const val CONFIGURATION_ICON_RESOURCE_PATH = "images/icon.png" const val CONFIGURATION_ICON_FILE_PATH = "images/icon" const val CONFIGURATION_FORMATTED_LIST_DELIMITER = ';' @Service("configurationService") class ConfigurationServiceImpl( - @Lazy private val fileService: FileService, + @Lazy private val fileService: WriteableFileService, + private val resourceFileService: ResourceFileService, private val configurationSource: ConfigurationSource, private val securityProperties: CreSecurityProperties, private val logger: Logger @@ -121,6 +136,29 @@ class ConfigurationServiceImpl( return decryptConfiguration(configuration).content } + override fun getConfiguredIcon() = + getConfiguredImage( + type = ConfigurationType.INSTANCE_ICON_SET, + filePath = CONFIGURATION_ICON_FILE_PATH, + resourcePath = CONFIGURATION_ICON_RESOURCE_PATH + ) + + override fun getConfiguredLogo() = + getConfiguredImage( + type = ConfigurationType.INSTANCE_LOGO_SET, + filePath = CONFIGURATION_LOGO_FILE_PATH, + resourcePath = CONFIGURATION_LOGO_RESOURCE_PATH + ) + + private fun getConfiguredImage(type: ConfigurationType, filePath: String, resourcePath: String) = + with(get(type) as Configuration) { + if (this.content == true.toString()) { + fileService.read(filePath) + } else { + resourceFileService.read(resourcePath) + } + } + override fun set(configurations: List) { configurationSource.set( configurations @@ -136,14 +174,15 @@ class ConfigurationServiceImpl( configurationSource.set(encryptConfigurationIfSecure(configuration)) } - override fun set(configuration: ConfigurationImageDto) { - val filePath = when (val configurationType = configuration.key.toConfigurationType()) { - ConfigurationType.INSTANCE_LOGO_PATH -> CONFIGURATION_LOGO_FILE_PATH - ConfigurationType.INSTANCE_ICON_PATH -> CONFIGURATION_ICON_FILE_PATH - else -> throw InvalidImageConfigurationException(configurationType) - } + override fun setConfiguredIcon(icon: MultipartFile) = + setConfiguredImage(icon, CONFIGURATION_ICON_FILE_PATH, ConfigurationType.INSTANCE_ICON_SET) - fileService.write(configuration.image, filePath, true) + override fun setConfiguredLogo(logo: MultipartFile) = + setConfiguredImage(logo, CONFIGURATION_LOGO_FILE_PATH, ConfigurationType.INSTANCE_LOGO_SET) + + private fun setConfiguredImage(image: MultipartFile, path: String, type: ConfigurationType) { + fileService.write(image, path, true) + set(configuration(type, content = true.toString())) } override fun initializeProperties(predicate: (ConfigurationType) -> Boolean) { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationSource.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationSource.kt index 0b00a97..6971f9e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationSource.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/config/ConfigurationSource.kt @@ -8,7 +8,7 @@ import dev.fyloz.colorrecipesexplorer.model.Configuration import dev.fyloz.colorrecipesexplorer.model.ConfigurationType import dev.fyloz.colorrecipesexplorer.model.configuration import dev.fyloz.colorrecipesexplorer.repository.ConfigurationRepository -import dev.fyloz.colorrecipesexplorer.service.create +import dev.fyloz.colorrecipesexplorer.service.files.create import dev.fyloz.colorrecipesexplorer.utils.excludeAll import org.slf4j.Logger import org.springframework.boot.info.BuildProperties diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/FileService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt similarity index 96% rename from src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/FileService.kt rename to src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt index 69cd4aa..4136ebe 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/FileService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileService.kt @@ -1,9 +1,10 @@ -package dev.fyloz.colorrecipesexplorer.service +package dev.fyloz.colorrecipesexplorer.service.files import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.exception.RestException import org.slf4j.Logger import org.springframework.core.io.ByteArrayResource +import org.springframework.core.io.Resource import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile @@ -23,8 +24,13 @@ interface FileService { fun exists(path: String): Boolean /** Reads the file at the given [path]. */ - fun read(path: String): ByteArrayResource + fun read(path: String): Resource + /** Completes the path of the given [String] by adding the working directory. */ + fun String.fullPath(): FilePath +} + +interface WriteableFileService : FileService { /** Creates a file at the given [path]. */ fun create(path: String) @@ -36,16 +42,13 @@ interface FileService { /** Deletes the file at the given [path]. */ fun delete(path: String) - - /** Completes the path of the given [String] by adding the working directory. */ - fun String.fullPath(): FilePath } @Service class FileServiceImpl( private val creProperties: CreProperties, private val logger: Logger -) : FileService { +) : WriteableFileService { override fun exists(path: String) = withFileAt(path.fullPath()) { this.exists() && this.isFile } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt new file mode 100644 index 0000000..8ac67f5 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt @@ -0,0 +1,26 @@ +package dev.fyloz.colorrecipesexplorer.service.files + +import org.springframework.core.io.Resource +import org.springframework.core.io.ResourceLoader +import org.springframework.stereotype.Service + +@Service +class ResourceFileService( + private val resourceLoader: ResourceLoader +) : FileService { + override fun exists(path: String) = + read(path).exists() + + override fun read(path: String): Resource = + path.fullPath().resource.also { + if (!it.exists()) { + throw FileNotFoundException(path) + } + } + + override fun String.fullPath() = + FilePath("classpath:${this}") + + private val FilePath.resource: Resource + get() = resourceLoader.getResource(this.path) +} diff --git a/src/main/resources/images/favicon.png b/src/main/resources/images/icon.png similarity index 100% rename from src/main/resources/images/favicon.png rename to src/main/resources/images/icon.png From e1ca6a8d837e9376d79e6c9622914f8c5f1579d6 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Wed, 1 Sep 2021 17:59:53 -0400 Subject: [PATCH 3/5] #13 Update FileController to use new HTTP OK util function --- .../colorrecipesexplorer/rest/FileController.kt | 16 +++------------- .../fyloz/colorrecipesexplorer/rest/RestUtils.kt | 6 +++--- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt index c7879d6..b4cfd14 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt @@ -3,8 +3,6 @@ package dev.fyloz.colorrecipesexplorer.rest import dev.fyloz.colorrecipesexplorer.model.ConfigurationType import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService -import org.springframework.core.io.ByteArrayResource -import org.springframework.core.io.Resource import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize @@ -24,14 +22,7 @@ class FileController( fun upload( @RequestParam path: String, @RequestParam(required = false) mediaType: String? - ): ResponseEntity { - val file = fileService.read(path) - return ResponseEntity.ok() - .header("Content-Disposition", "filename=${getFileNameFromPath(path)}") - .contentLength(file.contentLength()) - .contentType(MediaType.parseMediaType(mediaType ?: DEFAULT_MEDIA_TYPE)) - .body(file) - } + ) = ok(fileService.read(path), mediaType) @PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) @PreAuthorize("hasAnyAuthority('WRITE_FILE')") @@ -46,11 +37,10 @@ class FileController( @DeleteMapping @PreAuthorize("hasAnyAuthority('WRITE_FILE')") - fun delete(@RequestParam path: String): ResponseEntity { - return noContent { + fun delete(@RequestParam path: String): ResponseEntity = + noContent { fileService.delete(path) } - } private fun created(path: String): ResponseEntity = ResponseEntity diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt index 36b892e..23f115d 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt @@ -26,13 +26,13 @@ fun ok(action: () -> Unit): ResponseEntity { return ResponseEntity.ok().build() } -fun ok(file: Resource, mediaType: String? = null): ResponseEntity { - return ResponseEntity.ok() +/** Creates a HTTP OK [ResponseEntity] for the given [file], with the given [mediaType]. */ +fun ok(file: Resource, mediaType: String? = null): ResponseEntity = + ResponseEntity.ok() .header("Content-Disposition", "filename=${file.filename}") .contentLength(file.contentLength()) .contentType(MediaType.parseMediaType(mediaType ?: DEFAULT_MEDIA_TYPE)) .body(file) -} /** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */ fun created(controllerPath: String, body: T): ResponseEntity = From 9a260b6edf81beec342cec589a0d52a6c0d62c99 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Tue, 7 Sep 2021 10:23:56 -0400 Subject: [PATCH 4/5] #13 Add tests --- build.gradle.kts | 6 +- .../rest/ConfigurationController.kt | 4 +- .../rest/FileController.kt | 2 +- .../colorrecipesexplorer/rest/RestUtils.kt | 2 +- .../service/files/ResourceFileService.kt | 4 +- .../service/ConfigurationServiceTest.kt | 144 ++++++++++++++++-- .../service/MaterialServiceTest.kt | 4 +- .../service/RecipeServiceTest.kt | 3 +- .../service/TouchUpKitServiceTest.kt | 6 +- .../service/{ => files}/FileServiceTest.kt | 2 +- .../service/files/ResourceFileServiceTest.kt | 114 ++++++++++++++ 11 files changed, 264 insertions(+), 27 deletions(-) rename src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/{ => files}/FileServiceTest.kt (99%) create mode 100644 src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 9912eaf..ee0689e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,10 +46,10 @@ dependencies { implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}") testImplementation("org.springframework:spring-test:5.1.6.RELEASE") - testImplementation("org.mockito:mockito-inline:3.11.2") + testImplementation("org.mockito:mockito-inline:3.12.4") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.2") - testImplementation("io.mockk:mockk:1.10.6") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.2") + testImplementation("io.mockk:mockk:1.12.0") testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}") testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt index 5e30ea6..db64365 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/ConfigurationController.kt @@ -44,7 +44,7 @@ class ConfigurationController(val configurationService: ConfigurationService) { @GetMapping("icon") fun getIcon() = - ok(configurationService.getConfiguredIcon(), MediaType.IMAGE_PNG_VALUE) + okFile(configurationService.getConfiguredIcon(), MediaType.IMAGE_PNG_VALUE) @PutMapping("icon") @PreAuthorize("hasAuthority('ADMIN')") @@ -56,7 +56,7 @@ class ConfigurationController(val configurationService: ConfigurationService) { @GetMapping("logo") fun getLogo() = - ok(configurationService.getConfiguredLogo(), MediaType.IMAGE_PNG_VALUE) + okFile(configurationService.getConfiguredLogo(), MediaType.IMAGE_PNG_VALUE) @PutMapping("logo") @PreAuthorize("hasAuthority('ADMIN')") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt index b4cfd14..92b078a 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/FileController.kt @@ -22,7 +22,7 @@ class FileController( fun upload( @RequestParam path: String, @RequestParam(required = false) mediaType: String? - ) = ok(fileService.read(path), mediaType) + ) = okFile(fileService.read(path), mediaType) @PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) @PreAuthorize("hasAnyAuthority('WRITE_FILE')") diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt index 23f115d..7147aa0 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/rest/RestUtils.kt @@ -27,7 +27,7 @@ fun ok(action: () -> Unit): ResponseEntity { } /** Creates a HTTP OK [ResponseEntity] for the given [file], with the given [mediaType]. */ -fun ok(file: Resource, mediaType: String? = null): ResponseEntity = +fun okFile(file: Resource, mediaType: String? = null): ResponseEntity = ResponseEntity.ok() .header("Content-Disposition", "filename=${file.filename}") .contentLength(file.contentLength()) diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt index 8ac67f5..be9ba6e 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileService.kt @@ -9,7 +9,7 @@ class ResourceFileService( private val resourceLoader: ResourceLoader ) : FileService { override fun exists(path: String) = - read(path).exists() + path.fullPath().resource.exists() override fun read(path: String): Resource = path.fullPath().resource.also { @@ -21,6 +21,6 @@ class ResourceFileService( override fun String.fullPath() = FilePath("classpath:${this}") - private val FilePath.resource: Resource + val FilePath.resource: Resource get() = resourceLoader.getResource(this.path) } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/ConfigurationServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/ConfigurationServiceTest.kt index e49fc60..ea4a73f 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/ConfigurationServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/ConfigurationServiceTest.kt @@ -5,23 +5,35 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.service.config.CONFIGURATION_FORMATTED_LIST_DELIMITER import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationServiceImpl import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationSource +import dev.fyloz.colorrecipesexplorer.service.files.ResourceFileService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import dev.fyloz.colorrecipesexplorer.utils.encrypt import io.mockk.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import kotlin.UnsupportedOperationException +import org.springframework.core.io.Resource +import org.springframework.web.multipart.MultipartFile import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class ConfigurationServiceTest { - private val fileService = mockk() + private val fileService = mockk() + private val resourceFileService = mockk() private val configurationSource = mockk() private val securityProperties = mockk { every { configSalt } returns "d32270943af7e1cc" } - private val service = spyk(ConfigurationServiceImpl(fileService, configurationSource, securityProperties, mockk())) + private val service = spyk( + ConfigurationServiceImpl( + fileService, + resourceFileService, + configurationSource, + securityProperties, + mockk() + ) + ) @AfterEach fun afterEach() { @@ -49,8 +61,8 @@ class ConfigurationServiceTest { fun `getAll() only returns set configurations`() { val unsetConfigurationTypes = listOf( ConfigurationType.INSTANCE_NAME, - ConfigurationType.INSTANCE_LOGO_PATH, - ConfigurationType.INSTANCE_ICON_PATH + ConfigurationType.INSTANCE_LOGO_SET, + ConfigurationType.INSTANCE_ICON_SET ) every { service.get(match { it in unsetConfigurationTypes }) } answers { @@ -82,8 +94,8 @@ class ConfigurationServiceTest { fun `getAll() only includes configurations matching the formatted formatted key list`() { val configurationTypes = listOf( ConfigurationType.INSTANCE_NAME, - ConfigurationType.INSTANCE_LOGO_PATH, - ConfigurationType.INSTANCE_ICON_PATH + ConfigurationType.INSTANCE_LOGO_SET, + ConfigurationType.INSTANCE_ICON_SET ) val formattedKeyList = configurationTypes .map { it.key } @@ -113,7 +125,7 @@ class ConfigurationServiceTest { @Test fun `get(key) calls get() with the ConfigurationType matching the given key`() { - val type = ConfigurationType.INSTANCE_ICON_PATH + val type = ConfigurationType.INSTANCE_ICON_SET val key = type.key every { service.get(type) } answers { @@ -132,7 +144,7 @@ class ConfigurationServiceTest { @Test fun `get(type) gets the configuration in the ConfigurationSource`() { - val type = ConfigurationType.INSTANCE_ICON_PATH + val type = ConfigurationType.INSTANCE_ICON_SET val configuration = configuration(type = type) every { configurationSource.get(type) } returns configuration @@ -144,7 +156,7 @@ class ConfigurationServiceTest { @Test fun `get(type) throws ConfigurationNotSetException when the given ConfigurationType has no set configuration`() { - val type = ConfigurationType.INSTANCE_ICON_PATH + val type = ConfigurationType.INSTANCE_ICON_SET every { configurationSource.get(type) } returns null @@ -228,6 +240,57 @@ class ConfigurationServiceTest { assertThrows { service.getSecure(type) } } + private fun getConfiguredImageTest( + configurationType: ConfigurationType, + imageSet: Boolean, + test: (Resource) -> Unit + ) { + val resource = mockk() + val configuration = configuration(configurationType, imageSet.toString()) + val imageService = if (imageSet) fileService else resourceFileService + + every { service.get(configurationType) } returns configuration + every { imageService.read(any()) } returns resource + + test(resource) + } + + @Test + fun `getConfiguredIcon() gets icon from resources when INSTANCE_ICON_SET configuration is false`() { + getConfiguredImageTest(ConfigurationType.INSTANCE_ICON_SET, false) { resource -> + val found = service.getConfiguredIcon() + + assertEquals(resource, found) + } + } + + @Test + fun `getConfiguredIcon() gets icon from files when INSTANCE_ICON_SET configuration is true`() { + getConfiguredImageTest(ConfigurationType.INSTANCE_ICON_SET, true) { resource -> + val found = service.getConfiguredIcon() + + assertEquals(resource, found) + } + } + + @Test + fun `getConfiguredLogo() gets logo from resources when INSTANCE_LOGO_SET is false`() { + getConfiguredImageTest(ConfigurationType.INSTANCE_LOGO_SET, false) { resource -> + val found = service.getConfiguredLogo() + + assertEquals(resource, found) + } + } + + @Test + fun `getConfiguredLogo() gets logo from files when INSTANCE_LOGO_SET is true`() { + getConfiguredImageTest(ConfigurationType.INSTANCE_LOGO_SET, true) { resource -> + val found = service.getConfiguredLogo() + + assertEquals(resource, found) + } + } + @Test fun `set(configuration) set configuration in ConfigurationSource`() { val configuration = configuration(type = ConfigurationType.INSTANCE_NAME) @@ -261,4 +324,65 @@ class ConfigurationServiceTest { }) } } + + private fun setConfiguredImageTest(test: (MultipartFile) -> Unit) { + val file = mockk() + + every { service.set(any()) } just runs + every { fileService.write(any(), any(), any()) } just runs + + test(file) + } + + @Test + fun `setConfiguredIcon() sets icon in files`() { + setConfiguredImageTest { file -> + service.setConfiguredIcon(file) + + verify { + fileService.write(file, any(), true) + } + } + } + + @Test + fun `setConfiguredIcon() sets INSTANCE_ICON_SET configuration to true`() { + val type = ConfigurationType.INSTANCE_ICON_SET + + setConfiguredImageTest { file -> + service.setConfiguredIcon(file) + + verify { + service.set(match { + it.key == type.key && it.content == true.toString() + }) + } + } + } + + @Test + fun `setConfiguredLogo() sets logo in files`() { + setConfiguredImageTest { file -> + service.setConfiguredLogo(file) + + verify { + fileService.write(file, any(), true) + } + } + } + + @Test + fun `setConfiguredLogo() sets INSTANCE_LOGO_SET configuration to true`() { + val type = ConfigurationType.INSTANCE_LOGO_SET + + setConfiguredImageTest { file -> + service.setConfiguredLogo(file) + + verify { + service.set(match { + it.key == type.key && it.content == true.toString() + }) + } + } + } } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt index 0962323..be7c476 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialServiceTest.kt @@ -4,7 +4,7 @@ import com.nhaarman.mockitokotlin2.* import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository -import dev.fyloz.colorrecipesexplorer.service.FileService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -21,7 +21,7 @@ class MaterialServiceTest : private val recipeService: RecipeService = mock() private val mixService: MixService = mock() private val materialTypeService: MaterialTypeService = mock() - private val fileService: FileService = mock() + private val fileService: WriteableFileService = mock() override val service: MaterialService = spy(MaterialServiceImpl(repository, recipeService, mixService, materialTypeService, fileService, mock())) diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt index 0e055ad..d27a78c 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/RecipeServiceTest.kt @@ -6,6 +6,7 @@ import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.model.account.group import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import io.mockk.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test @@ -263,7 +264,7 @@ class RecipeServiceTest : } private class RecipeImageServiceTestContext { - val fileService = mockk { + val fileService = mockk { every { write(any(), any(), any()) } just Runs every { delete(any()) } just Runs } diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitServiceTest.kt index f6b5ba2..51ef288 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/TouchUpKitServiceTest.kt @@ -1,11 +1,10 @@ package dev.fyloz.colorrecipesexplorer.service -import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import dev.fyloz.colorrecipesexplorer.model.ConfigurationType import dev.fyloz.colorrecipesexplorer.model.configuration import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository -import dev.fyloz.colorrecipesexplorer.service.* import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService +import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService import dev.fyloz.colorrecipesexplorer.utils.PdfDocument import dev.fyloz.colorrecipesexplorer.utils.toByteArrayResource import io.mockk.* @@ -16,10 +15,9 @@ import kotlin.test.assertEquals private class TouchUpKitServiceTestContext { val touchUpKitRepository = mockk() - val fileService = mockk { + val fileService = mockk { every { write(any(), any(), any()) } just Runs } - val creProperties = mockk() val configService = mockk(relaxed = true) val touchUpKitService = spyk(TouchUpKitServiceImpl(fileService, configService, touchUpKitRepository)) val pdfDocumentData = mockk() diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/FileServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt similarity index 99% rename from src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/FileServiceTest.kt rename to src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt index 8c4ca7a..936bf47 100644 --- a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/FileServiceTest.kt +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/FileServiceTest.kt @@ -1,4 +1,4 @@ -package dev.fyloz.colorrecipesexplorer.service +package dev.fyloz.colorrecipesexplorer.service.files import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties import io.mockk.* diff --git a/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt new file mode 100644 index 0000000..5c0d6ff --- /dev/null +++ b/src/test/kotlin/dev/fyloz/colorrecipesexplorer/service/files/ResourceFileServiceTest.kt @@ -0,0 +1,114 @@ +package dev.fyloz.colorrecipesexplorer.service.files + +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.springframework.core.io.Resource +import org.springframework.core.io.ResourceLoader +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ResourceFileServiceTest { + private val resourceLoader = mockk() + + private val service = spyk(ResourceFileService(resourceLoader)) + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + private fun existsTest(shouldExists: Boolean, test: (String) -> Unit) { + val path = "unit_test_resource" + with(service) { + every { path.fullPath() } returns mockk { + every { resource } returns mockk { + every { exists() } returns shouldExists + } + } + + test(path) + } + } + + @Test + fun `exists() returns true when a resource exists at the given path`() { + existsTest(true) { path -> + val found = service.exists(path) + + assertTrue { found } + } + } + + @Test + fun `exists() returns false when no resource exists at the given path`() { + existsTest(false) { path -> + val found = service.exists(path) + + assertFalse { found } + } + } + + private fun readTest(shouldExists: Boolean, test: (Resource, String) -> Unit) { + val mockResource = mockk { + every { exists() } returns shouldExists + } + val path = "unit_test_path" + with(service) { + every { path.fullPath() } returns mockk { + every { resource } returns mockResource + } + + test(mockResource, path) + } + } + + @Test + fun `read() returns the resource at the given path`() { + readTest(true) { resource, path -> + val found = service.read(path) + + assertEquals(resource, found) + } + } + + @Test + fun `read() throws FileNotFoundException when no resource exists at the given path`() { + readTest(false) { _, path -> + assertThrows { + service.read(path) + } + } + } + + @Test + fun `fullPath() returns the given path in the classpath`() { + val path = "unit_test_path" + val expectedPath = "classpath:$path" + + with(service) { + val found = path.fullPath() + + assertEquals(expectedPath, found.path) + } + } + + @Test + fun `resource returns a resource for the given path`() { + val filePath = FilePath("classpath:unit_test_path") + val resource = mockk() + + every { resourceLoader.getResource(filePath.path) } returns resource + + with(service) { + val found = filePath.resource + + assertEquals(resource, found) + } + } +} From f5355f044d0c6ae850f62e6707c8b0358bd606ac Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sat, 11 Sep 2021 23:50:07 -0400 Subject: [PATCH 5/5] Update maven repository location --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ee0689e..574ac80 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ repositories { mavenCentral() maven { - url = uri("https://git.fyloz.dev/api/v4/projects/40/packages/maven") + url = uri("https://archiva.fyloz.dev/repository/internal") } } @@ -35,7 +35,7 @@ dependencies { implementation("io.jsonwebtoken:jjwt:0.9.1") implementation("org.apache.poi:poi-ooxml:4.1.0") implementation("org.apache.pdfbox:pdfbox:2.0.4") - implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2") + implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2.1") implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}") implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}")