Skip to content

Commit

Permalink
Add tests for cloud mode introspection
Browse files Browse the repository at this point in the history
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
  • Loading branch information
KiKoS0 committed Sep 7, 2024
1 parent 6c473fb commit 925105c
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 1 deletion.
9 changes: 9 additions & 0 deletions inngest-spring-boot-demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -39,6 +45,9 @@ dependencyManagement {
tasks.withType<Test> {
useJUnitPlatform()
systemProperty("junit.jupiter.execution.parallel.enabled", true)
systemProperty("test-group", "unit-test")

jvmArgs = listOf("-Dnet.bytebuddy.experimental=true")
testLogging {
events =
setOf(
Expand Down
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-2a89e554826a40672684e75eee6e34909b45aa4fd04fff5ff49bbe28c24ef424";
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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.inngest.InngestHeaderKey;
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;
Expand All @@ -12,12 +13,13 @@

@Import(DemoTestConfiguration.class)
@WebMvcTest(DemoController.class)
public class IntrospectTest {
public class DevModeIntrospectionTest {

@Autowired
private MockMvc mockMvc;

@Test
@EnabledIfSystemProperty(named = "test-group", matches = "unit-test")
public void shouldReturnInsecureIntrospectPayload() throws Exception {
mockMvc.perform(get("/api/inngest").header("Host", "localhost:8080"))
.andExpect(status().isOk())
Expand Down

0 comments on commit 925105c

Please sign in to comment.