Skip to content

Commit a29d7a2

Browse files
test(runtime-e2e): runtime-e2e/x-client-id/ proves SDK emits v9 header
Per CLAUDE.md HARD RULE #0: gate requires runtime-e2e/<feature>/ test for user-facing surface changes. Adds the v9 X-Client-ID counterpart to x-axonflow-client/ runner — spawns an in-process forwarding proxy (JDK HttpServer + HttpClient) that captures the outbound X-Client-ID off the wire and forwards to the real agent. Asserts captured value equals the configured clientId. Prereqs: AXONFLOW_AGENT_URL, AXONFLOW_TENANT_ID, AXONFLOW_TENANT_SECRET. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
1 parent f3451e0 commit a29d7a2

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* runtime-e2e/x-client-id/SdkXClientIdTest.java
3+
*
4+
* Per CLAUDE.md HARD RULE #0: real-wire test of the SDK's v9 X-Client-ID
5+
* header emission to a real running AxonFlow agent.
6+
*
7+
* Approach: the SDK does not expose its internal OkHttpClient, so we
8+
* run a tiny in-process forwarding proxy (Java's HttpServer + the JDK's
9+
* built-in HttpClient) that inspects every request, captures the
10+
* X-Client-ID header, and forwards to the real agent. The SDK is
11+
* pointed at the proxy. Bytes flow real → real.
12+
*
13+
* Run:
14+
* AXONFLOW_AGENT_URL=http://localhost:8080 \
15+
* AXONFLOW_TENANT_ID=cs_... AXONFLOW_TENANT_SECRET=... \
16+
* java -cp "<sdk-jar>:<deps>" runtime-e2e/x-client-id/SdkXClientIdTest.java
17+
*/
18+
import com.getaxonflow.sdk.AxonFlow;
19+
import com.getaxonflow.sdk.AxonFlowConfig;
20+
import com.getaxonflow.sdk.types.MCPCheckInputResponse;
21+
import com.sun.net.httpserver.HttpServer;
22+
import java.net.InetSocketAddress;
23+
import java.net.URI;
24+
import java.net.http.HttpClient;
25+
import java.net.http.HttpRequest;
26+
import java.net.http.HttpResponse;
27+
import java.util.concurrent.atomic.AtomicReference;
28+
29+
public class SdkXClientIdTest {
30+
public static void main(String[] args) throws Exception {
31+
String endpoint = System.getenv().getOrDefault("AXONFLOW_AGENT_URL", "http://localhost:8080");
32+
String tenant = System.getenv("AXONFLOW_TENANT_ID");
33+
String secret = System.getenv("AXONFLOW_TENANT_SECRET");
34+
if (tenant == null || secret == null) {
35+
System.err.println(
36+
"AXONFLOW_TENANT_ID + AXONFLOW_TENANT_SECRET must be set; see ../README.md");
37+
System.exit(2);
38+
}
39+
40+
final URI target = URI.create(endpoint);
41+
final HttpClient forwarder = HttpClient.newHttpClient();
42+
final AtomicReference<String> sawClientId = new AtomicReference<>("");
43+
44+
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
45+
server.createContext(
46+
"/",
47+
ex -> {
48+
sawClientId.set(ex.getRequestHeaders().getFirst("X-Client-ID"));
49+
byte[] body = ex.getRequestBody().readAllBytes();
50+
HttpRequest.Builder b =
51+
HttpRequest.newBuilder(target.resolve(ex.getRequestURI()))
52+
.method(ex.getRequestMethod(), HttpRequest.BodyPublishers.ofByteArray(body));
53+
ex.getRequestHeaders()
54+
.forEach(
55+
(k, vs) -> {
56+
if (!k.equalsIgnoreCase("host") && !k.equalsIgnoreCase("content-length")) {
57+
vs.forEach(v -> b.header(k, v));
58+
}
59+
});
60+
try {
61+
HttpResponse<byte[]> resp =
62+
forwarder.send(b.build(), HttpResponse.BodyHandlers.ofByteArray());
63+
byte[] respBody = resp.body();
64+
ex.sendResponseHeaders(resp.statusCode(), respBody.length);
65+
ex.getResponseBody().write(respBody);
66+
ex.getResponseBody().close();
67+
} catch (Exception e) {
68+
ex.sendResponseHeaders(502, 0);
69+
ex.close();
70+
}
71+
});
72+
server.start();
73+
String proxyUrl = "http://127.0.0.1:" + server.getAddress().getPort();
74+
75+
AxonFlow client =
76+
AxonFlow.create(
77+
AxonFlowConfig.builder()
78+
.agentUrl(proxyUrl)
79+
.clientId(tenant)
80+
.clientSecret(secret)
81+
.build());
82+
83+
System.out.println("Asserting wire X-Client-ID = " + tenant);
84+
try {
85+
MCPCheckInputResponse r = client.mcpCheckInput("postgres", "SELECT 1");
86+
// outcome doesn't matter; only the captured header
87+
} catch (Exception ignored) {
88+
// outcome doesn't matter; only the captured header
89+
}
90+
server.stop(0);
91+
92+
String got = sawClientId.get();
93+
if (!tenant.equals(got)) {
94+
System.err.println("FAIL: wire X-Client-ID = \"" + got + "\", want \"" + tenant + "\"");
95+
System.exit(1);
96+
}
97+
System.out.println("PASS: wire X-Client-ID = \"" + got + "\"");
98+
}
99+
}

0 commit comments

Comments
 (0)