From 9a260b6edf81beec342cec589a0d52a6c0d62c99 Mon Sep 17 00:00:00 2001 From: William Nolin Date: Tue, 7 Sep 2021 10:23:56 -0400 Subject: [PATCH] #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) + } + } +}