feat(examples): end-to-end JamJet Cloud SDK example (Java)#8
Conversation
…ain-java receipt inline
📝 WalkthroughWalkthroughThis PR introduces ChangesJamJet Cloud SDK Demo Example Module
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.java (1)
64-89: ⚡ Quick winThis test currently passes even when no span is emitted.
spanReachesCloudAfterGovernedCallfalls back to non-null bean assertions, so a span regression won’t fail the test. Consider renaming to a probe-style test or asserting/aborting explicitly (e.g.,Assumptions.assumeTrue) to keep intent clear.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.java` around lines 64 - 89, The test spanReachesCloudAfterGovernedCall currently masks span regressions by falling back to non-null bean assertions; change it to either assert/abort explicitly when no span is observed or convert it to a probe-style (assumption) test: inside spanReachesCloudAfterGovernedCall replace the current if/else fallback with either a failing assertion like org.junit.jupiter.api.Assertions.assertTrue(gotSpan, "expected span POST to /v1/events/ingest") so the test fails on missing span, or use org.junit.jupiter.api.Assumptions.assumeTrue(gotSpan, "no span emitted; skipping probe") to mark it as skipped; update uses of chatClient and emitter only as context (do not keep the non-span assertions that hide failures).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/jamjet-cloud-sdk-demo/pom.xml`:
- Around line 62-65: The pom currently hard-pins langchain4j-core version
"0.36.2" in the examples/jamjet-cloud-sdk-demo POM; remove the literal
<version>0.36.2</version> from the dependency for artifactId langchain4j-core
and instead reference a centralized property (e.g. use
<version>${langchain4j.version}</version>) or rely on a parent/shared
dependencyManagement entry; ensure the examples POM still declares the
dependency for langchain4j-core (since jamjet-cloud-spring-boot-starter marks it
optional) but uses the centralized property or dependencyManagement entry so the
version is managed in one place.
In `@examples/jamjet-cloud-sdk-demo/README.md`:
- Around line 30-32: Replace the non-runnable example Java launch line in
README.md for PlainJavaCloudDemo with a concrete, copy-pasteable run approach:
either show running the demo via Maven’s exec plugin (calling main class
dev.jamjet.example.cloud.PlainJavaCloudDemo) or document how to generate an
explicit classpath (e.g., build target/classes plus resolved dependency JARs)
and use that concrete classpath in the java -cp invocation; update the README’s
example so the placeholder “...” is removed and the user can run the demo
directly.
In
`@examples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/ReceiptConfig.java`:
- Around line 16-18: The actionReceiptEmitter() currently hardcodes
Path.of(System.getProperty("user.home"), ".jamjet", "audit",
"cloud-sdk-demo.jsonl"), which breaks test isolation and leaks demo data; change
it to read a configurable property (e.g., receipt.file.path or similar) with a
safe default that matches the current home-based path, and pass that Path into
new FileActionReceiptEmitter(...) so tests can override it via
application-test.yml; locate and update the actionReceiptEmitter() factory
method (and any config class) to inject the property (using `@Value` or
ConfigurationProperties/Environment) and ensure the default is preserved if the
property is not supplied.
In
`@examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/FileActionReceiptEmitterTest.java`:
- Around line 50-54: The test only validates the first JSONL line but should
validate every emitted line; update the assertions to iterate over the lines
list, for each entry call MAPPER.readValue(...) to parse into a Map and then
call new ActionReceiptValidator().validate(...) on that Map (using the same
suppressed cast pattern), and assertThat(errors).isEmpty() for each parsed line
to ensure both lines are validated; reference variables/methods: lines, MAPPER,
ActionReceiptValidator.validate().
In
`@examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.java`:
- Around line 26-41: The DynamicPropertySource supplier may run before JUnit's
`@BeforeAll`, causing wireMock to be null; ensure WireMock is initialized and
started before props() runs by moving the startup and stub registration into a
static initializer (e.g., a static { ... } block) or other early hook so
wireMock = new WireMockServer(0); wireMock.start(); and wireMock.stubFor(...)
execute before props() is called; keep stopWireMock() for teardown but remove
reliance on `@BeforeAll` startWireMock() for providing the baseUrl in props().
In `@README.md`:
- Around line 144-145: Replace the plain text reference "See
examples/jamjet-cloud-sdk-demo for a runnable end-to-end example." with a
Markdown link so it becomes clickable in rendered docs; update the line that
contains "See examples/jamjet-cloud-sdk-demo" to use the link text
[examples/jamjet-cloud-sdk-demo](examples/jamjet-cloud-sdk-demo/) (including the
trailing slash) so readers can navigate directly to the demo folder.
---
Nitpick comments:
In
`@examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.java`:
- Around line 64-89: The test spanReachesCloudAfterGovernedCall currently masks
span regressions by falling back to non-null bean assertions; change it to
either assert/abort explicitly when no span is observed or convert it to a
probe-style (assumption) test: inside spanReachesCloudAfterGovernedCall replace
the current if/else fallback with either a failing assertion like
org.junit.jupiter.api.Assertions.assertTrue(gotSpan, "expected span POST to
/v1/events/ingest") so the test fails on missing span, or use
org.junit.jupiter.api.Assumptions.assumeTrue(gotSpan, "no span emitted; skipping
probe") to mark it as skipped; update uses of chatClient and emitter only as
context (do not keep the non-span assertions that hide failures).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 513516de-b0cb-499b-a499-42a7978e56a3
📒 Files selected for processing (17)
README.mdexamples/jamjet-cloud-sdk-demo/README.mdexamples/jamjet-cloud-sdk-demo/pom.xmlexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/ChatConfig.javaexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/CloudDemoApplication.javaexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/FileActionReceiptEmitter.javaexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/LookupTool.javaexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/PlainJavaCloudDemo.javaexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/ReceiptConfig.javaexamples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/package-info.javaexamples/jamjet-cloud-sdk-demo/src/main/resources/application-test.ymlexamples/jamjet-cloud-sdk-demo/src/main/resources/application.ymlexamples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/FileActionReceiptEmitterTest.javaexamples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/PlainJavaCloudDemoTest.javaexamples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.javaexamples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/StubChatModelConfig.javapom.xml
| <groupId>dev.langchain4j</groupId> | ||
| <artifactId>langchain4j-core</artifactId> | ||
| <version>0.36.2</version> | ||
| </dependency> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Demo module langchain4j-core declaration =="
rg -n -C2 '<artifactId>langchain4j-core</artifactId>|<version>0\.36\.2</version>' examples/jamjet-cloud-sdk-demo/pom.xml
echo
echo "== Starter module references to langchain4j (if any) =="
fd -i 'pom.xml' | xargs rg -n -C2 'langchain4j|JamjetChatModelListener|ConditionalOnClass' || true
echo
echo "== Parent/version properties that might manage langchain4j =="
rg -n -C2 'langchain4j|spring-ai.version|spring-boot.version' pom.xml examples/jamjet-cloud-sdk-demo/pom.xmlRepository: jamjet-labs/jamjet-runtime-java
Length of output: 6368
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== examples/jamjet-cloud-sdk-demo/pom.xml (around langchain4j-core) =="
nl -ba examples/jamjet-cloud-sdk-demo/pom.xml | sed -n '1,140p'
echo
echo "== jamjet-cloud-spring-boot-starter/pom.xml (around langchain4j-core) =="
nl -ba jamjet-cloud-spring-boot-starter/pom.xml | sed -n '1,140p'
echo
echo "== repository root pom.xml (dependencyManagement relevant bits) =="
nl -ba pom.xml | sed -n '1,220p'
echo
echo "== any dependencyManagement for langchain4j-core in repo =="
rg -n --glob 'pom.xml' 'dependencyManagement' pom.xml examples jamjet-cloud-* | head -n 50
echo
echo "== any centralized property for langchain4j-core version =="
rg -n --glob 'pom.xml' 'langchain4j.*version' pom.xml examples jamjet-cloud-* | head -n 50Repository: jamjet-labs/jamjet-runtime-java
Length of output: 207
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== examples/jamjet-cloud-sdk-demo/pom.xml (around langchain4j-core) =="
cat -n examples/jamjet-cloud-sdk-demo/pom.xml | sed -n '1,120p'
echo
echo "== jamjet-cloud-spring-boot-starter/pom.xml (around langchain4j-core) =="
cat -n jamjet-cloud-spring-boot-starter/pom.xml | sed -n '1,140p'
echo
echo "== repository root pom.xml (dependencyManagement relevant bits) =="
cat -n pom.xml | sed -n '1,240p'
echo
echo "== dependencyManagement blocks mentioning langchain4j-core =="
rg -n --glob 'pom.xml' 'dependencyManagement' pom.xml examples jamjet-cloud-* || true
echo
echo "== centralized properties for langchain4j version (if any) =="
rg -n --glob 'pom.xml' 'langchain4j.*version' pom.xml examples jamjet-cloud-* || true
echo
echo "== any direct langchain4j-core pinning =="
rg -n --glob 'pom.xml' '<artifactId>langchain4j-core</artifactId>' pom.xml examples jamjet-cloud-* | head -n 50Repository: jamjet-labs/jamjet-runtime-java
Length of output: 19231
Avoid duplicating/hard-pinning langchain4j-core in examples/jamjet-cloud-sdk-demo/pom.xml.
langchain4j-core is explicitly pinned to 0.36.2 at line 64, and the repo parent doesn’t centrally manage a langchain4j version. Since jamjet-cloud-spring-boot-starter also pins langchain4j-core 0.36.2 as <optional>true</optional>, the demo must keep an explicit dependency—but the version should be centralized/shared (e.g., parent property or shared dependencyManagement) to avoid updating both poms in lockstep later.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/jamjet-cloud-sdk-demo/pom.xml` around lines 62 - 65, The pom
currently hard-pins langchain4j-core version "0.36.2" in the
examples/jamjet-cloud-sdk-demo POM; remove the literal <version>0.36.2</version>
from the dependency for artifactId langchain4j-core and instead reference a
centralized property (e.g. use <version>${langchain4j.version}</version>) or
rely on a parent/shared dependencyManagement entry; ensure the examples POM
still declares the dependency for langchain4j-core (since
jamjet-cloud-spring-boot-starter marks it optional) but uses the centralized
property or dependencyManagement entry so the version is managed in one place.
| mvn -pl examples/jamjet-cloud-sdk-demo -am package -DskipTests | ||
| java -cp examples/jamjet-cloud-sdk-demo/target/classes:... dev.jamjet.example.cloud.PlainJavaCloudDemo | ||
| ``` |
There was a problem hiding this comment.
Plain-Java run command is not copy-paste runnable.
At Line 31, -cp ... contains an unresolved placeholder, so users can’t run the example as written. Provide a concrete command (e.g., via exec-maven-plugin or documented classpath generation) to keep the live-mode path executable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/jamjet-cloud-sdk-demo/README.md` around lines 30 - 32, Replace the
non-runnable example Java launch line in README.md for PlainJavaCloudDemo with a
concrete, copy-pasteable run approach: either show running the demo via Maven’s
exec plugin (calling main class dev.jamjet.example.cloud.PlainJavaCloudDemo) or
document how to generate an explicit classpath (e.g., build target/classes plus
resolved dependency JARs) and use that concrete classpath in the java -cp
invocation; update the README’s example so the placeholder “...” is removed and
the user can run the demo directly.
| public ActionReceiptEmitter actionReceiptEmitter() { | ||
| Path file = Path.of(System.getProperty("user.home"), ".jamjet", "audit", "cloud-sdk-demo.jsonl"); | ||
| return new FileActionReceiptEmitter(file); |
There was a problem hiding this comment.
Make receipt file path configurable instead of hardcoding ${user.home}.
Line 17 persists receipts to a fixed user-home path, which can leak unredacted demo/test data and breaks hermetic test isolation. Prefer a property-backed path with a safe default, then override it in application-test.yml.
Proposed diff
+import org.springframework.beans.factory.annotation.Value;
...
`@Bean`
- public ActionReceiptEmitter actionReceiptEmitter() {
- Path file = Path.of(System.getProperty("user.home"), ".jamjet", "audit", "cloud-sdk-demo.jsonl");
- return new FileActionReceiptEmitter(file);
+ public ActionReceiptEmitter actionReceiptEmitter(
+ `@Value`("${jamjet.cloud.receipt-file:${user.home}/.jamjet/audit/cloud-sdk-demo.jsonl}") String receiptFile) {
+ return new FileActionReceiptEmitter(Path.of(receiptFile));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public ActionReceiptEmitter actionReceiptEmitter() { | |
| Path file = Path.of(System.getProperty("user.home"), ".jamjet", "audit", "cloud-sdk-demo.jsonl"); | |
| return new FileActionReceiptEmitter(file); | |
| import org.springframework.beans.factory.annotation.Value; | |
| ... | |
| `@Bean` | |
| public ActionReceiptEmitter actionReceiptEmitter( | |
| `@Value`("${jamjet.cloud.receipt-file:${user.home}/.jamjet/audit/cloud-sdk-demo.jsonl}") String receiptFile) { | |
| return new FileActionReceiptEmitter(Path.of(receiptFile)); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@examples/jamjet-cloud-sdk-demo/src/main/java/dev/jamjet/example/cloud/ReceiptConfig.java`
around lines 16 - 18, The actionReceiptEmitter() currently hardcodes
Path.of(System.getProperty("user.home"), ".jamjet", "audit",
"cloud-sdk-demo.jsonl"), which breaks test isolation and leaks demo data; change
it to read a configurable property (e.g., receipt.file.path or similar) with a
safe default that matches the current home-based path, and pass that Path into
new FileActionReceiptEmitter(...) so tests can override it via
application-test.yml; locate and update the actionReceiptEmitter() factory
method (and any config class) to inject the property (using `@Value` or
ConfigurationProperties/Environment) and ensure the default is preserved if the
property is not supplied.
| // each line is valid JSON and the receipt validates against the v0.1 schema | ||
| var parsed = MAPPER.readValue(lines.get(0), java.util.Map.class); | ||
| @SuppressWarnings("unchecked") | ||
| List<String> errors = new ActionReceiptValidator().validate((java.util.Map<String, Object>) parsed); | ||
| assertThat(errors).isEmpty(); |
There was a problem hiding this comment.
Validate both emitted JSONL lines, not just the first.
Line 50 says “each line is valid,” but Lines 51–54 validate only lines.get(0). This can miss corruption in the appended line. Iterate over both lines and validate each parsed receipt.
Suggested patch
- // each line is valid JSON and the receipt validates against the v0.1 schema
- var parsed = MAPPER.readValue(lines.get(0), java.util.Map.class);
- `@SuppressWarnings`("unchecked")
- List<String> errors = new ActionReceiptValidator().validate((java.util.Map<String, Object>) parsed);
- assertThat(errors).isEmpty();
+ // each line is valid JSON and validates against the v0.1 schema
+ ActionReceiptValidator validator = new ActionReceiptValidator();
+ for (String line : lines) {
+ var parsed = MAPPER.readValue(line, java.util.Map.class);
+ `@SuppressWarnings`("unchecked")
+ List<String> errors = validator.validate((java.util.Map<String, Object>) parsed);
+ assertThat(errors).isEmpty();
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // each line is valid JSON and the receipt validates against the v0.1 schema | |
| var parsed = MAPPER.readValue(lines.get(0), java.util.Map.class); | |
| @SuppressWarnings("unchecked") | |
| List<String> errors = new ActionReceiptValidator().validate((java.util.Map<String, Object>) parsed); | |
| assertThat(errors).isEmpty(); | |
| // each line is valid JSON and validates against the v0.1 schema | |
| ActionReceiptValidator validator = new ActionReceiptValidator(); | |
| for (String line : lines) { | |
| var parsed = MAPPER.readValue(line, java.util.Map.class); | |
| `@SuppressWarnings`("unchecked") | |
| List<String> errors = validator.validate((java.util.Map<String, Object>) parsed); | |
| assertThat(errors).isEmpty(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/FileActionReceiptEmitterTest.java`
around lines 50 - 54, The test only validates the first JSONL line but should
validate every emitted line; update the assertions to iterate over the lines
list, for each entry call MAPPER.readValue(...) to parse into a Map and then
call new ActionReceiptValidator().validate(...) on that Map (using the same
suppressed cast pattern), and assertThat(errors).isEmpty() for each parsed line
to ensure both lines are validated; reference variables/methods: lines, MAPPER,
ActionReceiptValidator.validate().
| @BeforeAll | ||
| static void startWireMock() { | ||
| wireMock = new WireMockServer(0); | ||
| wireMock.start(); | ||
| wireMock.stubFor(post(urlEqualTo("/v1/events/ingest")).willReturn(ok())); | ||
| } | ||
|
|
||
| @AfterAll | ||
| static void stopWireMock() { | ||
| if (wireMock != null) wireMock.stop(); | ||
| } | ||
|
|
||
| @DynamicPropertySource | ||
| static void props(DynamicPropertyRegistry r) { | ||
| r.add("jamjet.cloud.api-url", () -> wireMock.baseUrl()); | ||
| r.add("jamjet.cloud.batch.size", () -> "1"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.java"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "== File (head 120 lines) =="
sed -n '1,120p' "$FILE" | cat -n
echo
echo "== Locate where wireMock is declared =="
rg -n "wireMock" "$FILE"
echo
echo "== Determine test framework versions (pom/gradle) =="
# Look for Maven pom first
if [ -f "examples/jamjet-cloud-sdk-demo/pom.xml" ]; then
echo "--- examples/jamjet-cloud-sdk-demo/pom.xml ---"
rg -n "spring-boot|junit-jupiter|junit|junit-platform|junit-jupiter-engine" examples/jamjet-cloud-sdk-demo/pom.xml || true
elif [ -f "pom.xml" ]; then
echo "--- root pom.xml ---"
rg -n "spring-boot|junit-jupiter|junit|junit-platform|junit-jupiter-engine" pom.xml || true
fi
# Look for Gradle build
if [ -f "examples/jamjet-cloud-sdk-demo/build.gradle" ] || [ -f "examples/jamjet-cloud-sdk-demo/build.gradle.kts" ]; then
echo "--- examples/jamjet-cloud-sdk-demo/build.gradle* ---"
ls -la examples/jamjet-cloud-sdk-demo/build.gradle* || true
rg -n "spring-boot|junit-jupiter|junit|junit-platform" examples/jamjet-cloud-sdk-demo/build.gradle* || true
elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
echo "--- root build.gradle* ---"
ls -la build.gradle* || true
rg -n "spring-boot|junit-jupiter|junit|junit-platform" build.gradle* || true
fiRepository: jamjet-labs/jamjet-runtime-java
Length of output: 5216
🌐 Web query:
Spring @DynamicPropertySourcelifecycle when invoked relative to JUnit5@BeforeAll called before or after
💡 Result:
In Spring integration tests, the @DynamicPropertySource annotation is processed as part of the ApplicationContext loading process [1][2]. Consequently, its invocation relative to JUnit 5's @BeforeAll lifecycle method depends on when that context is initialized [3][4]. Under standard conditions, where the ApplicationContext is loaded for a test class, @DynamicPropertySource methods are typically invoked before @BeforeAll methods [3][5][4]. This occurs because the Spring TestContext Framework must load the context—and therefore resolve dynamic properties—before the test class execution proceeds to user-defined lifecycle methods [4]. However, this order can be sensitive to specific configurations: 1. Lifecycle Semantic Influences: When using @TestInstance(Lifecycle.PER_CLASS), the SpringExtension may trigger ApplicationContext loading during the TestInstancePostProcessor phase [6]. This can result in Spring loading the context and executing @DynamicPropertySource logic before JUnit's @BeforeAll has an opportunity to run [6]. 2. Configuration-Specific Behavior: Certain test configurations, such as the use of @Sql with the execution phase set to BEFORE_TEST_CLASS, force an eager loading of the ApplicationContext [3][5]. In such cases, it is explicitly documented that @DynamicPropertySource methods will be invoked before @BeforeAll methods [3][4]. Because @DynamicPropertySource relies on the context loading lifecycle, it is generally considered unavailable for tasks that must strictly precede the initialization of the Spring container itself (e.g., if you need to start an external resource before Spring touches the property system). For such requirements, developers often use static initialization blocks or alternative manual lifecycle management [7][4].
Citations:
- 1: https://docs.spring.io/spring-framework/reference/testing/annotations/integration-spring/annotation-dynamicpropertysource.html
- 2: https://docs.spring.io/spring/reference/testing/testcontext-framework/ctx-management/dynamic-property-sources.html
- 3: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/jdbc/Sql.ExecutionPhase.html
- 4: Improve documentation for
@Sqlexecution phases regarding lifecycle spring-projects/spring-framework#32343 - 5: https://docs.spring.io/spring-framework/docs/6.2.11-SNAPSHOT/javadoc-api/org/springframework/test/context/jdbc/Sql.ExecutionPhase.html
- 6: Spring Boot Tests with JUnit5 Jupiter and Shared Containers testcontainers/testcontainers-java#2290
- 7: https://stackoverflow.com/questions/73234333/junit-per-class-lifecycle-causes-testcontainers-not-to-be-loaded-for-applicatio
Initialize WireMock before @DynamicPropertySource runs.
@DynamicPropertySource is evaluated during Spring ApplicationContext bootstrap and can execute before JUnit 5 @BeforeAll, so r.add("jamjet.cloud.api-url", () -> wireMock.baseUrl()) (line 40) may dereference wireMock while it’s still null. Start WireMock in a static initializer (or a JUnit extension hook that runs early enough) so the supplier never sees a null instance.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@examples/jamjet-cloud-sdk-demo/src/test/java/dev/jamjet/example/cloud/SpringCloudDemoTest.java`
around lines 26 - 41, The DynamicPropertySource supplier may run before JUnit's
`@BeforeAll`, causing wireMock to be null; ensure WireMock is initialized and
started before props() runs by moving the startup and stub registration into a
static initializer (e.g., a static { ... } block) or other early hook so
wireMock = new WireMockServer(0); wireMock.start(); and wireMock.stubFor(...)
execute before props() is called; keep stopWireMock() for teardown but remove
reliance on `@BeforeAll` startWireMock() for providing the baseUrl in props().
| See examples/jamjet-cloud-sdk-demo for a runnable end-to-end example. | ||
|
|
There was a problem hiding this comment.
Make the demo pointer a clickable Markdown link.
At Line 144, use a link ([examples/jamjet-cloud-sdk-demo](examples/jamjet-cloud-sdk-demo/)) so readers can navigate directly from rendered docs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 144 - 145, Replace the plain text reference "See
examples/jamjet-cloud-sdk-demo for a runnable end-to-end example." with a
Markdown link so it becomes clickable in rendered docs; update the line that
contains "See examples/jamjet-cloud-sdk-demo" to use the link text
[examples/jamjet-cloud-sdk-demo](examples/jamjet-cloud-sdk-demo/) (including the
trailing slash) so readers can navigate directly to the demo folder.
Summary
Adds
examples/jamjet-cloud-sdk-demo, the missing runnable example of a Java app using the JamJet Cloud SDK end to end. Two flavours:ActionReceiptAdvisor+ aFileActionReceiptEmitteradd AgentBoundary receipts for@Toolcalls. Spans are zero-config; receipts are one advisor + emitter bean.JamjetCloud/Span/ActionReceiptAPI.It is a reactor module so it builds against the in-repo cloud SDK (not yet published). Hermetic tests stub JamJet Cloud with WireMock and use a deterministic model, so they need no API keys. The README documents live mode (real key + model -> spans in the dashboard, receipts in a JSONL file) and is explicit about what the Java Cloud SDK does not yet do (local policy enforcement, PII redaction, approval UX, cache injection).
Tests
5 hermetic tests:
FileActionReceiptEmitterTest,PlainJavaCloudDemoTest(span + receipt),SpringCloudDemoTest(context wiring + a governed call + a span-emission probe). The plain-Java test proves the SDK span path reaches/v1/events/ingestvia WireMock and the receipt is schema-valid.Notes / follow-ups
0.2.0->0.3.1).langchain4j-core(compile) becausejamjet-cloud-spring-boot-starter'sJamjetCloudAutoConfigurationreferences a langchain4j type as a@Beanreturn type, so the method-level@ConditionalOnClassdoes not protect a non-langchain4j Spring user fromNoClassDefFoundErrorat startup. Worth a starter follow-up (move the langchain4j beans into a class-level@ConditionalOnClassnested config). The example's dependency + comment is the stopgap.ChatModel, so the Spring test documents that and relies on the plain-Java test for the SDK span path; live mode (a real model) exercises the full flow.Test plan
mvn -pl examples/jamjet-cloud-sdk-demo -am test-compilemvn -pl examples/jamjet-cloud-sdk-demo test(5 green)JJ_API_KEY+OPENAI_API_KEY,mvn -pl examples/jamjet-cloud-sdk-demo spring-boot:run-> span in the dashboard, receipt in~/.jamjet/audit/cloud-sdk-demo.jsonlSummary by CodeRabbit
Release Notes
Documentation
New Features