From 1017c7dff5b215b3e7fb5545e9af5db06320c0e2 Mon Sep 17 00:00:00 2001 From: alyssa Date: Tue, 10 Sep 2024 08:59:31 +0100 Subject: [PATCH] tests for some of the generic route handling --- client/src/main/kotlin/screen/HelpDialog.kt | 4 +- .../test/kotlin/bean/HyperlinkAdaptorTest.kt | 2 +- client/src/test/kotlin/bean/LinkLabelTest.kt | 2 +- .../test/kotlin/help/AbstractHelpPanelTest.kt | 2 +- client/src/test/kotlin/help/HelpPanelTest.kt | 2 +- .../src/test/kotlin/util/DialogUtilNewTest.kt | 2 +- .../src/test/kotlin/util/UpdateManagerTest.kt | 2 +- client/src/test/kotlin/util/UrlUtilTest.kt | 2 +- .../test/kotlin/bean/FocusableWindowTest.kt | 2 +- .../logging/LogDestinationSystemOutTest.kt | 2 +- core/src/test/kotlin/logging/LoggerTest.kt | 2 +- .../LoggerUncaughtExceptionHandlerTest.kt | 2 +- .../test/kotlin/logging/LoggingConsoleTest.kt | 2 +- .../test/kotlin/logging/LoggingUtilsTest.kt | 2 +- core/src/test/kotlin/utils/MathsUtilTest.kt | 2 +- server/src/main/kotlin/plugins/Routing.kt | 21 ++++++- .../src/main/kotlin/routes/ClientException.kt | 10 +++ server/src/test/kotlin/TestUtils.kt | 13 ---- server/src/test/kotlin/plugins/RoutingTest.kt | 61 +++++++++++++++++++ .../health/HealthCheckControllerTest.kt | 4 +- test-core/build.gradle.kts | 1 + .../src/main/kotlin/testCore/AbstractTest.kt | 4 +- .../testCore/BeforeAllTestsExtension.kt | 1 + .../src/main/kotlin/testCore/JsonUtils.kt | 7 +++ 24 files changed, 121 insertions(+), 33 deletions(-) create mode 100644 server/src/main/kotlin/routes/ClientException.kt delete mode 100644 server/src/test/kotlin/TestUtils.kt create mode 100644 server/src/test/kotlin/plugins/RoutingTest.kt create mode 100644 test-core/src/main/kotlin/testCore/JsonUtils.kt diff --git a/client/src/main/kotlin/screen/HelpDialog.kt b/client/src/main/kotlin/screen/HelpDialog.kt index ca9811e..73ea62b 100644 --- a/client/src/main/kotlin/screen/HelpDialog.kt +++ b/client/src/main/kotlin/screen/HelpDialog.kt @@ -172,7 +172,7 @@ class HelpDialog : JFrame(), TreeSelectionListener, WindowListener, Registry { populateTools("") populateMisc("") - for (i in 0 ..< tree.rowCount) { + for (i in 0.. : AbstractTest() { diff --git a/client/src/test/kotlin/help/HelpPanelTest.kt b/client/src/test/kotlin/help/HelpPanelTest.kt index 6c0a379..6a06b87 100644 --- a/client/src/test/kotlin/help/HelpPanelTest.kt +++ b/client/src/test/kotlin/help/HelpPanelTest.kt @@ -4,8 +4,8 @@ import io.kotest.matchers.shouldBe import java.awt.Color import java.awt.Font import javax.swing.JTextPane -import main.kotlin.testCore.AbstractTest import org.junit.jupiter.api.Test +import testCore.AbstractTest import util.EntropyColour class HelpPanelTest : AbstractTest() { diff --git a/client/src/test/kotlin/util/DialogUtilNewTest.kt b/client/src/test/kotlin/util/DialogUtilNewTest.kt index 7e6219d..1b7d34f 100644 --- a/client/src/test/kotlin/util/DialogUtilNewTest.kt +++ b/client/src/test/kotlin/util/DialogUtilNewTest.kt @@ -11,12 +11,12 @@ import io.kotest.matchers.shouldBe import javax.swing.JDialog import javax.swing.SwingUtilities import logging.Severity -import main.kotlin.testCore.AbstractTest import main.kotlin.testCore.getErrorDialog import main.kotlin.testCore.getInfoDialog import main.kotlin.testCore.getQuestionDialog import main.kotlin.testCore.runAsync import org.junit.jupiter.api.Test +import testCore.AbstractTest class DialogUtilNewTest : AbstractTest() { @Test diff --git a/client/src/test/kotlin/util/UpdateManagerTest.kt b/client/src/test/kotlin/util/UpdateManagerTest.kt index 3456f33..2e90d58 100644 --- a/client/src/test/kotlin/util/UpdateManagerTest.kt +++ b/client/src/test/kotlin/util/UpdateManagerTest.kt @@ -24,7 +24,6 @@ import kong.unirest.UnirestException import kong.unirest.json.JSONException import kong.unirest.json.JSONObject import logging.Severity -import main.kotlin.testCore.AbstractTest import main.kotlin.testCore.getDialogMessage import main.kotlin.testCore.getErrorDialog import main.kotlin.testCore.getInfoDialog @@ -34,6 +33,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import screen.LoadingDialog +import testCore.AbstractTest import testCore.assertDoesNotExit import testCore.assertExits diff --git a/client/src/test/kotlin/util/UrlUtilTest.kt b/client/src/test/kotlin/util/UrlUtilTest.kt index e6625df..d426447 100644 --- a/client/src/test/kotlin/util/UrlUtilTest.kt +++ b/client/src/test/kotlin/util/UrlUtilTest.kt @@ -6,8 +6,8 @@ import io.mockk.mockk import io.mockk.verify import java.io.IOException import logging.Severity -import main.kotlin.testCore.AbstractTest import org.junit.jupiter.api.Test +import testCore.AbstractTest class UrlUtilTest : AbstractTest() { @Test diff --git a/core/src/test/kotlin/bean/FocusableWindowTest.kt b/core/src/test/kotlin/bean/FocusableWindowTest.kt index bebfbbe..7297db9 100644 --- a/core/src/test/kotlin/bean/FocusableWindowTest.kt +++ b/core/src/test/kotlin/bean/FocusableWindowTest.kt @@ -3,8 +3,8 @@ package bean import io.kotest.matchers.shouldBe import io.mockk.mockk import logging.KEY_ACTIVE_WINDOW -import main.kotlin.testCore.AbstractTest import org.junit.jupiter.api.Test +import testCore.AbstractTest import utils.InjectedThings.logger class FocusableWindowTest : AbstractTest() { diff --git a/core/src/test/kotlin/logging/LogDestinationSystemOutTest.kt b/core/src/test/kotlin/logging/LogDestinationSystemOutTest.kt index 92e4ddf..b9fefb9 100644 --- a/core/src/test/kotlin/logging/LogDestinationSystemOutTest.kt +++ b/core/src/test/kotlin/logging/LogDestinationSystemOutTest.kt @@ -3,12 +3,12 @@ package logging import io.kotest.matchers.string.shouldContain import java.io.ByteArrayOutputStream import java.io.PrintStream -import main.kotlin.testCore.AbstractTest import main.kotlin.testCore.CURRENT_TIME_STRING import main.kotlin.testCore.makeLogRecord import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import testCore.AbstractTest class LogDestinationSystemOutTest : AbstractTest() { private val originalOut = System.out diff --git a/core/src/test/kotlin/logging/LoggerTest.kt b/core/src/test/kotlin/logging/LoggerTest.kt index 52c0abb..63c3fed 100644 --- a/core/src/test/kotlin/logging/LoggerTest.kt +++ b/core/src/test/kotlin/logging/LoggerTest.kt @@ -5,11 +5,11 @@ import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.mockk.mockk import io.mockk.verify -import main.kotlin.testCore.AbstractTest import main.kotlin.testCore.CURRENT_TIME import main.kotlin.testCore.FakeLogDestination import main.kotlin.testCore.shouldContainKeyValues import org.junit.jupiter.api.Test +import testCore.AbstractTest class LoggerTest : AbstractTest() { @Test diff --git a/core/src/test/kotlin/logging/LoggerUncaughtExceptionHandlerTest.kt b/core/src/test/kotlin/logging/LoggerUncaughtExceptionHandlerTest.kt index 0d207e0..e2ca895 100644 --- a/core/src/test/kotlin/logging/LoggerUncaughtExceptionHandlerTest.kt +++ b/core/src/test/kotlin/logging/LoggerUncaughtExceptionHandlerTest.kt @@ -2,9 +2,9 @@ package logging import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import main.kotlin.testCore.AbstractTest import main.kotlin.testCore.shouldContainKeyValues import org.junit.jupiter.api.Test +import testCore.AbstractTest class LoggerUncaughtExceptionHandlerTest : AbstractTest() { @Test diff --git a/core/src/test/kotlin/logging/LoggingConsoleTest.kt b/core/src/test/kotlin/logging/LoggingConsoleTest.kt index 305b582..aaf9674 100644 --- a/core/src/test/kotlin/logging/LoggingConsoleTest.kt +++ b/core/src/test/kotlin/logging/LoggingConsoleTest.kt @@ -9,9 +9,9 @@ import io.kotest.matchers.string.shouldContain import java.awt.Color import javax.swing.JLabel import javax.swing.text.StyleConstants -import main.kotlin.testCore.AbstractTest import main.kotlin.testCore.makeLogRecord import org.junit.jupiter.api.Test +import testCore.AbstractTest import utils.getAllChildComponentsForType class LoggingConsoleTest : AbstractTest() { diff --git a/core/src/test/kotlin/logging/LoggingUtilsTest.kt b/core/src/test/kotlin/logging/LoggingUtilsTest.kt index 125e412..3c32ff2 100644 --- a/core/src/test/kotlin/logging/LoggingUtilsTest.kt +++ b/core/src/test/kotlin/logging/LoggingUtilsTest.kt @@ -2,8 +2,8 @@ package logging import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import main.kotlin.testCore.AbstractTest import org.junit.jupiter.api.Test +import testCore.AbstractTest class LoggingUtilsTest : AbstractTest() { @Test diff --git a/core/src/test/kotlin/utils/MathsUtilTest.kt b/core/src/test/kotlin/utils/MathsUtilTest.kt index 07eea9a..1096a76 100644 --- a/core/src/test/kotlin/utils/MathsUtilTest.kt +++ b/core/src/test/kotlin/utils/MathsUtilTest.kt @@ -2,8 +2,8 @@ package utils import getPercentage import io.kotest.matchers.shouldBe -import main.kotlin.testCore.AbstractTest import org.junit.jupiter.api.Test +import testCore.AbstractTest class MathsUtilTest : AbstractTest() { @Test diff --git a/server/src/main/kotlin/plugins/Routing.kt b/server/src/main/kotlin/plugins/Routing.kt index fd9ba8c..94ecb01 100644 --- a/server/src/main/kotlin/plugins/Routing.kt +++ b/server/src/main/kotlin/plugins/Routing.kt @@ -1,15 +1,34 @@ package plugins +import dto.ClientErrorResponse import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.logging.toLogString import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* +import routes.ClientException import routes.health.HealthCheckController +import utils.InjectedThings.logger fun Application.configureRouting() { install(StatusPages) { + exception { call, cause -> + logger.info( + "clientError", + "Error handling ${call.request.toLogString()}: ${cause.message}", + "clientErrorCode" to cause.errorCode, + "clientErrorMessage" to cause.message + ) + call.respond(cause.statusCode, ClientErrorResponse(cause.errorCode, cause.message)) + } + exception { call, cause -> - call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError) + val errorMessage = "Error handling ${call.request.toLogString()}" + logger.error("internalServerError", errorMessage, cause) + call.respond( + HttpStatusCode.InternalServerError, + ClientErrorResponse("internalServerError", errorMessage) + ) } } diff --git a/server/src/main/kotlin/routes/ClientException.kt b/server/src/main/kotlin/routes/ClientException.kt new file mode 100644 index 0000000..04e2da3 --- /dev/null +++ b/server/src/main/kotlin/routes/ClientException.kt @@ -0,0 +1,10 @@ +package routes + +import io.ktor.http.HttpStatusCode + +class ClientException( + val statusCode: HttpStatusCode, + val errorCode: String, + override val message: String, + override val cause: Throwable? = null +) : RuntimeException(message) diff --git a/server/src/test/kotlin/TestUtils.kt b/server/src/test/kotlin/TestUtils.kt deleted file mode 100644 index 80eeadc..0000000 --- a/server/src/test/kotlin/TestUtils.kt +++ /dev/null @@ -1,13 +0,0 @@ -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import io.kotest.matchers.shouldBe - -infix fun String.shouldMatchJson(expected: String) { - val mapper = jacksonObjectMapper() - - val parsed = mapper.readValue(this) - val expectedParsed = mapper.readValue(expected) - - parsed shouldBe expectedParsed -} diff --git a/server/src/test/kotlin/plugins/RoutingTest.kt b/server/src/test/kotlin/plugins/RoutingTest.kt new file mode 100644 index 0000000..41afe6e --- /dev/null +++ b/server/src/test/kotlin/plugins/RoutingTest.kt @@ -0,0 +1,61 @@ +package plugins + +import io.kotest.matchers.shouldBe +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.routing.get +import io.ktor.server.routing.routing +import io.ktor.server.testing.testApplication +import org.junit.jupiter.api.Test +import routes.ClientException +import testCore.AbstractTest +import testCore.shouldMatchJson + +class RoutingTest : AbstractTest() { + @Test + fun `Should handle client errors`() = testApplication { + application { ErrorThrowingController.installRoutes(this) } + + val response = client.get("/client-error") + response.status shouldBe HttpStatusCode.Conflict + response.bodyAsText() shouldMatchJson + """{ + "errorCode": "conflictingEntity", + "errorMessage": "Entity conflicts with another" + }""" + .trimIndent() + } + + @Test + fun `Should handle unexpected errors`() = testApplication { + application { ErrorThrowingController.installRoutes(this) } + + val response = client.get("/internal-error") + response.status shouldBe HttpStatusCode.InternalServerError + response.bodyAsText() shouldMatchJson + """{ + "errorCode": "internalServerError", + "errorMessage": "Error handling GET - /internal-error" + }""" + .trimIndent() + + errorLogged() shouldBe true + } +} + +private object ErrorThrowingController { + fun installRoutes(application: Application) { + application.routing { + get("/internal-error") { throw NullPointerException("Test error") } + get("/client-error") { + throw ClientException( + HttpStatusCode.Conflict, + "conflictingEntity", + "Entity conflicts with another" + ) + } + } + } +} diff --git a/server/src/test/kotlin/routes/health/HealthCheckControllerTest.kt b/server/src/test/kotlin/routes/health/HealthCheckControllerTest.kt index ec5e9a5..daffc30 100644 --- a/server/src/test/kotlin/routes/health/HealthCheckControllerTest.kt +++ b/server/src/test/kotlin/routes/health/HealthCheckControllerTest.kt @@ -5,9 +5,9 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.testing.* -import main.kotlin.testCore.AbstractTest import org.junit.jupiter.api.Test -import shouldMatchJson +import testCore.AbstractTest +import testCore.shouldMatchJson import util.OnlineConstants class HealthCheckControllerTest : AbstractTest() { diff --git a/test-core/build.gradle.kts b/test-core/build.gradle.kts index 2cf9417..c1655c0 100644 --- a/test-core/build.gradle.kts +++ b/test-core/build.gradle.kts @@ -12,4 +12,5 @@ dependencies { implementation("io.mockk:mockk:1.13.4") implementation("io.kotest:kotest-assertions-core:5.5.4") implementation("com.github.alexburlton:swing-test:4.0.0") + implementation("org.skyscreamer:jsonassert:1.5.3") } diff --git a/test-core/src/main/kotlin/testCore/AbstractTest.kt b/test-core/src/main/kotlin/testCore/AbstractTest.kt index bf874bb..90684ac 100644 --- a/test-core/src/main/kotlin/testCore/AbstractTest.kt +++ b/test-core/src/main/kotlin/testCore/AbstractTest.kt @@ -1,4 +1,4 @@ -package main.kotlin.testCore +package testCore import com.github.alyssaburlton.swingtest.purgeWindows import io.kotest.assertions.fail @@ -9,6 +9,8 @@ import logging.LogDestinationSystemOut import logging.LogRecord import logging.Logger import logging.Severity +import main.kotlin.testCore.BeforeAllTestsExtension +import main.kotlin.testCore.FakeLogDestination import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.extension.ExtendWith diff --git a/test-core/src/main/kotlin/testCore/BeforeAllTestsExtension.kt b/test-core/src/main/kotlin/testCore/BeforeAllTestsExtension.kt index 33088e0..da14066 100644 --- a/test-core/src/main/kotlin/testCore/BeforeAllTestsExtension.kt +++ b/test-core/src/main/kotlin/testCore/BeforeAllTestsExtension.kt @@ -5,6 +5,7 @@ import java.time.ZoneId import logging.LoggerUncaughtExceptionHandler import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.ExtensionContext +import testCore.logger import utils.InjectedThings var doneOneTimeSetup = false diff --git a/test-core/src/main/kotlin/testCore/JsonUtils.kt b/test-core/src/main/kotlin/testCore/JsonUtils.kt new file mode 100644 index 0000000..f2f02c0 --- /dev/null +++ b/test-core/src/main/kotlin/testCore/JsonUtils.kt @@ -0,0 +1,7 @@ +package testCore + +import org.skyscreamer.jsonassert.JSONAssert + +infix fun String.shouldMatchJson(expected: String) { + JSONAssert.assertEquals(expected, this, false) +}