From e2e6151d6ee593b9b39261f31427f0a52a1814d4 Mon Sep 17 00:00:00 2001 From: KiKoS0 <22998716+KiKoS0@users.noreply.github.com> Date: Fri, 13 Sep 2024 02:07:12 +0100 Subject: [PATCH] Support App diagnostics endpoint features (#76) * Implement `isInngestEventKeySet` helper function * Implement `hasSigningKey` helper function I also added junit-pioneer as a test dependency to be able to set environment variables in certain tests. I followed this guide for now but we can look into mocking the environment ourselves later on: https://www.baeldung.com/java-unit-testing-environment-variables#setting-environment-variables-with-junit-pioneer * Add the Introspection classes I also left a few TODOs for unsupported fields: - capabilities - signing key fallback The `extra` field was also left out, the spec says it's optional and I think only the JS SDK uses it: https://github.com/inngest/inngest/blob/main/docs/SDK_SPEC.md#45-introspection-requests * Repurpose `introspect` to return the correct introspection payload * Adapt both adapters according to the new `introspect` signature * Add tests for cloud mode introspection It's a pain to mock the environment variables in the tests. I introduced a different `system-stubs-jupiter` library to help with that because `org.junitpioneer.jupiter` did not work as expected in a Spring Boot environment. Ideally I think we should have a mockable custom `Environment` interface that we use throughout the application, instead of reaching for `System.getenv()` directly. The method for mocking that worked is described in this guide: https://www.baeldung.com/java-system-stubs#environment-and-property-overrides-for-junit-5-spring-tests * Use the same signing key that was used by other tests * Change order of introspection fields to alphabetical order * Address PR feedback --- .../inngest/springboot/InngestController.java | 20 +-- inngest-spring-boot-demo/build.gradle.kts | 11 ++ .../CloudModeIntrospectionTest.java | 139 ++++++++++++++++++ ...est.java => DevModeIntrospectionTest.java} | 17 ++- inngest/build.gradle.kts | 1 + inngest/src/main/kotlin/com/inngest/Comm.kt | 59 +++++++- .../main/kotlin/com/inngest/Environment.kt | 11 +- .../main/kotlin/com/inngest/Introspection.kt | 43 ++++++ .../main/kotlin/com/inngest/ServeConfig.kt | 11 +- .../src/main/kotlin/com/inngest/ktor/Route.kt | 14 +- .../com/inngest/signingkey/BearerToken.kt | 2 +- .../kotlin/com/inngest/EnvironmentTest.kt | 15 ++ .../kotlin/com/inngest/ServeConfigTest.kt | 15 ++ 13 files changed, 325 insertions(+), 33 deletions(-) create mode 100644 inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/CloudModeIntrospectionTest.java rename inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/{DemoControllerTest.java => DevModeIntrospectionTest.java} (59%) create mode 100644 inngest/src/main/kotlin/com/inngest/Introspection.kt diff --git a/inngest-spring-boot-adapter/src/main/java/com/inngest/springboot/InngestController.java b/inngest-spring-boot-adapter/src/main/java/com/inngest/springboot/InngestController.java index 46c2678f..d43fd355 100644 --- a/inngest-spring-boot-adapter/src/main/java/com/inngest/springboot/InngestController.java +++ b/inngest-spring-boot-adapter/src/main/java/com/inngest/springboot/InngestController.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -27,22 +28,15 @@ private HttpHeaders getHeaders() { @Value("${inngest.serveOrigin:}") private String serveOrigin; - @GetMapping() + @GetMapping public ResponseEntity index( @RequestHeader(HttpHeaders.HOST) String hostHeader, - HttpServletRequest request + @RequestHeader(name = "X-Inngest-Signature", required = false) String signature, + @RequestHeader(name = "X-Inngest-Server-Kind", required = false) String serverKind ) { - if (commHandler.getClient().getEnv() != InngestEnv.Dev) { - // TODO: Return an UnauthenticatedIntrospection instead when app diagnostics are implemented - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body("Introspect endpoint is only available in development mode"); - } - String origin = String.format("%s://%s", request.getScheme(), hostHeader); - if (this.serveOrigin != null && !this.serveOrigin.isEmpty()) { - origin = this.serveOrigin; - } - String response = commHandler.introspect(origin); - return ResponseEntity.ok().headers(getHeaders()).body(response); + String requestBody = ""; + String response = commHandler.introspect(signature, requestBody, serverKind); + return ResponseEntity.ok().headers(getHeaders()).contentType(MediaType.APPLICATION_JSON).body(response); } @PutMapping() diff --git a/inngest-spring-boot-demo/build.gradle.kts b/inngest-spring-boot-demo/build.gradle.kts index ec94a5be..dcd6a283 100644 --- a/inngest-spring-boot-demo/build.gradle.kts +++ b/inngest-spring-boot-demo/build.gradle.kts @@ -26,6 +26,12 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") testImplementation("org.springframework.boot:spring-boot-starter-test") + + if (JavaVersion.current().isJava11Compatible) { + testImplementation("uk.org.webcompere:system-stubs-jupiter:2.1.6") + } else { + testImplementation("uk.org.webcompere:system-stubs-jupiter:1.2.1") + } } dependencyManagement { @@ -39,6 +45,11 @@ dependencyManagement { tasks.withType { useJUnitPlatform() systemProperty("junit.jupiter.execution.parallel.enabled", true) + systemProperty("test-group", "unit-test") + + // Required by `system-stubs-jupiter` for JDK 21+ compatibility + // https://github.com/raphw/byte-buddy/issues/1396 + jvmArgs = listOf("-Dnet.bytebuddy.experimental=true") testLogging { events = setOf( diff --git a/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/CloudModeIntrospectionTest.java b/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/CloudModeIntrospectionTest.java new file mode 100644 index 00000000..baa2c4d4 --- /dev/null +++ b/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/CloudModeIntrospectionTest.java @@ -0,0 +1,139 @@ +package com.inngest.springbootdemo; + +import com.inngest.*; +import com.inngest.signingkey.BearerTokenKt; +import com.inngest.signingkey.SignatureVerificationKt; +import com.inngest.springboot.InngestConfiguration; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.util.HashMap; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +class ProductionConfiguration extends InngestConfiguration { + + public static final String INNGEST_APP_ID = "spring_test_prod_demo"; + + @Override + protected HashMap functions() { + return new HashMap<>(); + } + + @Override + protected Inngest inngestClient() { + return new Inngest(INNGEST_APP_ID); + } + + @Override + protected ServeConfig serve(Inngest client) { + return new ServeConfig(client); + } + + @Bean + protected CommHandler commHandler(@Autowired Inngest inngestClient) { + ServeConfig serveConfig = new ServeConfig(inngestClient); + return new CommHandler(functions(), inngestClient, serveConfig, SupportedFrameworkName.SpringBoot); + } +} + +@ExtendWith(SystemStubsExtension.class) +public class CloudModeIntrospectionTest { + + private static final String productionSigningKey = "signkey-prod-b2ed992186a5cb19f6668aade821f502c1d00970dfd0e35128d51bac4649916c"; + private static final String productionEventKey = "test"; + @SystemStub + private static EnvironmentVariables environmentVariables; + + @BeforeAll + static void beforeAll() { + environmentVariables.set("INNGEST_DEV", "0"); + environmentVariables.set("INNGEST_SIGNING_KEY", productionSigningKey); + environmentVariables.set("INNGEST_EVENT_KEY", productionEventKey); + } + + // The nested class is useful for setting the environment variables before the configuration class (Beans) runs. + // https://www.baeldung.com/java-system-stubs#environment-and-property-overrides-for-junit-5-spring-tests + @Import(ProductionConfiguration.class) + @WebMvcTest(DemoController.class) + @Nested + @EnabledIfSystemProperty(named = "test-group", matches = "unit-test") + class InnerSpringTest { + @Autowired + private MockMvc mockMvc; + + @Test + public void shouldReturnInsecureIntrospectionWhenSignatureIsMissing() throws Exception { + mockMvc.perform(get("/api/inngest").header("Host", "localhost:8080")) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(header().string(InngestHeaderKey.Framework.getValue(), "springboot")) + .andExpect(jsonPath("$.authentication_succeeded").value(false)) + .andExpect(jsonPath("$.function_count").isNumber()) + .andExpect(jsonPath("$.has_event_key").value(true)) + .andExpect(jsonPath("$.has_signing_key").value(true)) + .andExpect(jsonPath("$.mode").value("cloud")) + .andExpect(jsonPath("$.schema_version").value("2024-05-24")); + } + + @Test + public void shouldReturnInsecureIntrospectionWhenSignatureIsInvalid() throws Exception { + mockMvc.perform(get("/api/inngest") + .header("Host", "localhost:8080") + .header(InngestHeaderKey.Signature.getValue(), "invalid-signature")) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(header().string(InngestHeaderKey.Framework.getValue(), "springboot")) + .andExpect(jsonPath("$.authentication_succeeded").value(false)) + .andExpect(jsonPath("$.function_count").isNumber()) + .andExpect(jsonPath("$.has_event_key").value(true)) + .andExpect(jsonPath("$.has_signing_key").value(true)) + .andExpect(jsonPath("$.mode").value("cloud")) + .andExpect(jsonPath("$.schema_version").value("2024-05-24")); + } + + @Test + public void shouldReturnSecureIntrospectionWhenSignatureIsValid() throws Exception { + long currentTimestamp = System.currentTimeMillis() / 1000; + + String signature = SignatureVerificationKt.signRequest("", currentTimestamp, productionSigningKey); + String formattedSignature = String.format("s=%s&t=%d", signature, currentTimestamp); + + String expectedSigningKeyHash = BearerTokenKt.hashedSigningKey(productionSigningKey); + + mockMvc.perform(get("/api/inngest") + .header("Host", "localhost:8080") + .header(InngestHeaderKey.Signature.getValue(), formattedSignature)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(header().string(InngestHeaderKey.Framework.getValue(), "springboot")) + .andExpect(jsonPath("$.authentication_succeeded").value(true)) + .andExpect(jsonPath("$.function_count").isNumber()) + .andExpect(jsonPath("$.has_event_key").value(true)) + .andExpect(jsonPath("$.has_signing_key").value(true)) + .andExpect(jsonPath("$.mode").value("cloud")) + .andExpect(jsonPath("$.schema_version").value("2024-05-24")) + .andExpect(jsonPath("$.api_origin").value("https://api.inngest.com/")) + .andExpect(jsonPath("$.app_id").value(ProductionConfiguration.INNGEST_APP_ID)) + .andExpect(jsonPath("$.env").value("prod")) + .andExpect(jsonPath("$.event_api_origin").value("https://inn.gs/")) + .andExpect(jsonPath("$.framework").value("springboot")) + .andExpect(jsonPath("$.sdk_language").value("java")) + .andExpect(jsonPath("$.event_key_hash").value("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")) + .andExpect(jsonPath("$.sdk_version").value(Version.Companion.getVersion())) + .andExpect(jsonPath("$.signing_key_hash").value(expectedSigningKeyHash)); + } + } +} diff --git a/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/DemoControllerTest.java b/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/DevModeIntrospectionTest.java similarity index 59% rename from inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/DemoControllerTest.java rename to inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/DevModeIntrospectionTest.java index b4816ec4..628c5779 100644 --- a/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/DemoControllerTest.java +++ b/inngest-spring-boot-demo/src/test/java/com/inngest/springbootdemo/DevModeIntrospectionTest.java @@ -3,6 +3,7 @@ import com.inngest.InngestHeaderKey; import com.inngest.Version; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.annotation.Import; @@ -13,21 +14,23 @@ @Import(DemoTestConfiguration.class) @WebMvcTest(DemoController.class) -public class DemoControllerTest { +public class DevModeIntrospectionTest { @Autowired private MockMvc mockMvc; @Test - public void shouldReturnSyncPayload() throws Exception { + @EnabledIfSystemProperty(named = "test-group", matches = "unit-test") + public void shouldReturnInsecureIntrospectPayload() throws Exception { mockMvc.perform(get("/api/inngest").header("Host", "localhost:8080")) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(header().string(InngestHeaderKey.Framework.getValue(), "springboot")) - .andExpect(jsonPath("$.appName").value("spring_test_demo")) - .andExpect(jsonPath("$.framework").value("springboot")) - .andExpect(jsonPath("$.v").value("0.1")) - .andExpect(jsonPath("$.url").value("http://localhost:8080/api/inngest")) - .andExpect(jsonPath("$.sdk").value(String.format("java:v%s", Version.Companion.getVersion()))); + .andExpect(jsonPath("$.authentication_succeeded").isEmpty()) + .andExpect(jsonPath("$.function_count").isNumber()) + .andExpect(jsonPath("$.has_event_key").value(false)) + .andExpect(jsonPath("$.has_signing_key").value(false)) + .andExpect(jsonPath("$.mode").value("dev")) + .andExpect(jsonPath("$.schema_version").value("2024-05-24")); } } diff --git a/inngest/build.gradle.kts b/inngest/build.gradle.kts index c627f72d..3b341992 100644 --- a/inngest/build.gradle.kts +++ b/inngest/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation("io.ktor:ktor-server-core:2.3.5") testImplementation(kotlin("test")) + testImplementation("org.junit-pioneer:junit-pioneer:1.9.1") } publishing { diff --git a/inngest/src/main/kotlin/com/inngest/Comm.kt b/inngest/src/main/kotlin/com/inngest/Comm.kt index e27e3cc2..0324302a 100644 --- a/inngest/src/main/kotlin/com/inngest/Comm.kt +++ b/inngest/src/main/kotlin/com/inngest/Comm.kt @@ -4,8 +4,11 @@ import com.beust.klaxon.Json import com.beust.klaxon.Klaxon import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature +import com.inngest.signingkey.checkHeadersAndValidateSignature import com.inngest.signingkey.getAuthorizationHeader +import com.inngest.signingkey.hashedSigningKey import java.io.IOException +import java.security.MessageDigest data class ExecutionRequestPayload( val ctx: ExecutionContext, @@ -177,8 +180,50 @@ class CommHandler( // TODO // fun sync(): Result = Result.success(InngestSyncResult.None) - fun introspect(origin: String): String { - val requestPayload = getRegistrationRequestPayload(origin) + fun introspect( + signature: String?, + requestBody: String, + serverKind: String?, + ): String { + val insecureIntrospection = + InsecureIntrospection( + functionCount = functions.size, + hasEventKey = Environment.isInngestEventKeySet(client.eventKey), + hasSigningKey = config.hasSigningKey(), + mode = if (client.env == InngestEnv.Dev) "dev" else "cloud", + ) + + val requestPayload = + when (client.env) { + InngestEnv.Dev -> insecureIntrospection + + else -> + runCatching { + checkHeadersAndValidateSignature(signature, requestBody, serverKind, config) + + SecureIntrospection( + functionCount = functions.size, + hasEventKey = Environment.isInngestEventKeySet(client.eventKey), + hasSigningKey = config.hasSigningKey(), + authenticationSucceeded = true, + mode = "cloud", + env = client.env.value, + appId = config.appId(), + apiOrigin = "${config.baseUrl()}/", + framework = framework.value, + sdkVersion = Version.getVersion(), + sdkLanguage = "java", + servePath = config.servePath(), + serveOrigin = config.serveOrigin(), + signingKeyHash = hashedSigningKey(config.signingKey()), + eventApiOrigin = "${Environment.inngestEventApiBaseUrl(client.env)}/", + eventKeyHash = if (config.hasSigningKey()) hashedEventKey(client.eventKey) else null, + ) + }.getOrElse { + insecureIntrospection.apply { authenticationSucceeded = false } + } + } + return serializePayload(requestPayload) } @@ -198,4 +243,14 @@ class CommHandler( val servePath = config.servePath() ?: "/api/inngest" return "$serveOrigin$servePath" } + + private fun hashedEventKey(eventKey: String): String? = + eventKey + .takeIf { Environment.isInngestEventKeySet(it) } + ?.let { + MessageDigest + .getInstance("SHA-256") + .digest(it.toByteArray()) + .joinToString("") { byte -> "%02x".format(byte) } + } } diff --git a/inngest/src/main/kotlin/com/inngest/Environment.kt b/inngest/src/main/kotlin/com/inngest/Environment.kt index 542e7974..ed0d0584 100644 --- a/inngest/src/main/kotlin/com/inngest/Environment.kt +++ b/inngest/src/main/kotlin/com/inngest/Environment.kt @@ -11,11 +11,20 @@ object Environment { ).filterValues { (it is String) }.entries.associate { (k, v) -> k to v!! } } + private const val DUMMY_KEY_EVENT = "NO_EVENT_KEY_SET" + fun inngestEventKey(key: String? = null): String { if (key != null) return key - return System.getenv(InngestSystem.EventKey.value) ?: "NO_EVENT_KEY_SET" + return System.getenv(InngestSystem.EventKey.value) ?: DUMMY_KEY_EVENT } + fun isInngestEventKeySet(value: String?) = + when { + value.isNullOrEmpty() -> false + value == DUMMY_KEY_EVENT -> false + else -> true + } + fun inngestEventApiBaseUrl( env: InngestEnv, url: String? = null, diff --git a/inngest/src/main/kotlin/com/inngest/Introspection.kt b/inngest/src/main/kotlin/com/inngest/Introspection.kt new file mode 100644 index 00000000..095e8244 --- /dev/null +++ b/inngest/src/main/kotlin/com/inngest/Introspection.kt @@ -0,0 +1,43 @@ +package com.inngest + +import com.beust.klaxon.Json + +abstract class Introspection( + @Json("authentication_succeeded") open val authenticationSucceeded: Boolean?, + open val functionCount: Int, + open val hasEventKey: Boolean, + open val hasSigningKey: Boolean, + open val mode: String, + @Json("schema_version") val schemaVersion: String = "2024-05-24", +) + +internal data class InsecureIntrospection( + @Json("authentication_succeeded") override var authenticationSucceeded: Boolean? = null, + @Json("function_count") override val functionCount: Int, + @Json("has_event_key") override val hasEventKey: Boolean, + @Json("has_signing_key") override val hasSigningKey: Boolean, + override val mode: String, +) : Introspection(authenticationSucceeded, functionCount, hasEventKey, hasSigningKey, mode) + +internal data class SecureIntrospection( + @Json("api_origin") val apiOrigin: String, + @Json("app_id") val appId: String, + @Json("authentication_succeeded") override val authenticationSucceeded: Boolean?, + // TODO: Add capabilities when adding the trust probe + // @Json("capabilities") val capabilities: Capabilities, + @Json("event_api_origin") val eventApiOrigin: String, + @Json("event_key_hash") val eventKeyHash: String?, + val env: String?, + val framework: String, + @Json("function_count") override val functionCount: Int, + @Json("has_event_key") override val hasEventKey: Boolean, + @Json("has_signing_key") override val hasSigningKey: Boolean, + @Json("has_signing_key_fallback") val hasSigningKeyFallback: Boolean = false, + override val mode: String, + @Json("sdk_language") val sdkLanguage: String, + @Json("sdk_version") val sdkVersion: String, + @Json("serve_origin") val serveOrigin: String?, + @Json("serve_path") val servePath: String?, + @Json("signing_key_fallback_hash") val signingKeyFallbackHash: String? = null, + @Json("signing_key_hash") val signingKeyHash: String?, +) : Introspection(authenticationSucceeded, functionCount, hasEventKey, hasSigningKey, mode) diff --git a/inngest/src/main/kotlin/com/inngest/ServeConfig.kt b/inngest/src/main/kotlin/com/inngest/ServeConfig.kt index a9a0352e..321357e5 100644 --- a/inngest/src/main/kotlin/com/inngest/ServeConfig.kt +++ b/inngest/src/main/kotlin/com/inngest/ServeConfig.kt @@ -1,5 +1,7 @@ package com.inngest +const val DUMMY_SIGNING_KEY = "test" + class ServeConfig @JvmOverloads constructor( @@ -21,7 +23,7 @@ class ServeConfig if (signingKey != null) return signingKey return when (client.env) { - InngestEnv.Dev -> "test" + InngestEnv.Dev -> DUMMY_SIGNING_KEY else -> { val signingKey = System.getenv(InngestSystem.SigningKey.value) @@ -31,6 +33,13 @@ class ServeConfig } } + fun hasSigningKey() = + when (signingKey()) { + DUMMY_SIGNING_KEY -> false + "" -> false + else -> true + } + fun baseUrl(): String { if (baseUrl != null) return baseUrl diff --git a/inngest/src/main/kotlin/com/inngest/ktor/Route.kt b/inngest/src/main/kotlin/com/inngest/ktor/Route.kt index e21b397a..297d04a7 100644 --- a/inngest/src/main/kotlin/com/inngest/ktor/Route.kt +++ b/inngest/src/main/kotlin/com/inngest/ktor/Route.kt @@ -43,15 +43,13 @@ fun Route.serve( route(path) { get("") { - if (client.env != InngestEnv.Dev) { - // TODO: Return an UnauthenticatedIntrospection instead when app diagnostics are implemented - call.respond(HttpStatusCode.Forbidden, "Introspect endpoint is only available in development mode") - return@get - } + val signature = call.request.headers[InngestHeaderKey.Signature.value] + val serverKind = call.request.headers[InngestHeaderKey.ServerKind.value] - val origin = getOrigin(call) - val resp = comm.introspect(origin) - call.respond(HttpStatusCode.OK, resp) + val requestBody = call.receiveText() + + val resp = comm.introspect(signature, requestBody, serverKind) + call.respondText(resp, ContentType.Application.Json, HttpStatusCode.OK) } post("") { diff --git a/inngest/src/main/kotlin/com/inngest/signingkey/BearerToken.kt b/inngest/src/main/kotlin/com/inngest/signingkey/BearerToken.kt index b0d162e5..101b31c3 100644 --- a/inngest/src/main/kotlin/com/inngest/signingkey/BearerToken.kt +++ b/inngest/src/main/kotlin/com/inngest/signingkey/BearerToken.kt @@ -15,7 +15,7 @@ val SIGNING_KEY_REGEX = Regex("""(?^signkey-\w+-)(?.*)""") * @throws InvalidSigningKeyException If signingKey is not in the form "signkey--" */ @OptIn(ExperimentalStdlibApi::class) -private fun hashedSigningKey(signingKey: String): String { +internal fun hashedSigningKey(signingKey: String): String { val matchResult = SIGNING_KEY_REGEX.matchEntire(signingKey) ?: throw InvalidSigningKeyException() // We aggressively assert non-null here because if `matchEntire` had failed (and thus these capture groups didn't diff --git a/inngest/src/test/kotlin/com/inngest/EnvironmentTest.kt b/inngest/src/test/kotlin/com/inngest/EnvironmentTest.kt index 746bd66e..fc9ddfaa 100644 --- a/inngest/src/test/kotlin/com/inngest/EnvironmentTest.kt +++ b/inngest/src/test/kotlin/com/inngest/EnvironmentTest.kt @@ -16,6 +16,21 @@ internal class EnvironmentTest { assertEquals(key, Environment.inngestEventKey(key)) } + @Test + fun `test isInngestEventKeySet returns false when it's null`() { + assertFalse(Environment.isInngestEventKeySet(null)) + } + + @Test + fun `test isInngestEventKeySet returns false when it's set to the dummy event key`() { + assertFalse(Environment.isInngestEventKeySet("NO_EVENT_KEY_SET")) + } + + @Test + fun `test isInngestEventKeySet returns true when it's set to a valid event key`() { + assertTrue(Environment.isInngestEventKeySet("event")) + } + // @Test // fun `test inngestEventKey with INNGEST_EVENT_KEY value`() {} diff --git a/inngest/src/test/kotlin/com/inngest/ServeConfigTest.kt b/inngest/src/test/kotlin/com/inngest/ServeConfigTest.kt index f93c4783..1fed1ddd 100644 --- a/inngest/src/test/kotlin/com/inngest/ServeConfigTest.kt +++ b/inngest/src/test/kotlin/com/inngest/ServeConfigTest.kt @@ -1,5 +1,6 @@ package com.inngest +import org.junitpioneer.jupiter.SetEnvironmentVariable import kotlin.test.* internal class ServeConfigTest { @@ -35,6 +36,20 @@ internal class ServeConfigTest { ) } + @Test + fun `should return false if not set - dev`() { + val config = ServeConfig(client = client) + assertFalse(config.hasSigningKey()) + } + + @Test + @SetEnvironmentVariable(key = "INNGEST_SIGNING_KEY", value = "signkey-prod-b2ed992186a5cb19f6668aade821f502c1d00970dfd0e35128d51bac4649916c") + fun `should return true if set - prod`() { + val prodClient = Inngest(appId = client.appId, env = "prod") + val config = ServeConfig(client = prodClient) + assertTrue(config.hasSigningKey()) + } + // @Test // fun `should return INNGEST_SIGNING_KEY value - prod`() {}