-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
13 changed files
with
325 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
...spring-boot-demo/src/test/java/com/inngest/springbootdemo/CloudModeIntrospectionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, InngestFunction> 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)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Oops, something went wrong.