Repository navigation
test: bring Response.java StubResponse key sets under the schema drift check #315
Description
Problem
rift-java-core/src/main/java/io/github/achirdlabs/rift/model/Response.java keeps the StubResponse wire key sets inline, one Set.of(...) per variant (e.g. Set.of("is", "_behaviors", "behaviors", "_rift", "repeat") around lines 42, 63, 91, 115, 142, and 162-191), rather than in named constants. SchemaConformanceTest.modeledKeysAreKeysTheEngineReads (added in #314 for #300) only checks keys held in a named MODELED_KEYS set, so these inline keys are not covered. A key the engine never reads could be modeled in Response and no gate would notice.
Evidence
- Found in review of PR adopt: validate DSL output against the engine's imposter JSON Schema (sdk-conformance schema/imposter.schema.json) #314 (code-reviewer, confidence 60).
- Engine schema
$defs/StubResponse(sdk-conformanceschema/imposter.schema.json, rift 0.22.0) lists the properties:_behaviors,_mode,_rift,behaviors,body,fault,headers,inject,is,proxy,repeat,statusCode.
Proposed fix
- Lift the per-variant key sets in
Response.javainto namedprivate static finalconstants (one per variant, or a single unionMODELED_KEYS). - Add
Response(or each variant) toMODELED_KEYS_DEFSinSchemaConformanceTest, mapped toStubResponse, so the drift check covers it.
No behaviour change and no public API change.
Should this exist?
SETTLED: this is test-coverage hardening for the drift check introduced in #300. It adds no new public surface.
Provenance
Found by an agent while reviewing PR #314 (issue #300). Out of scope for that work, filed as a follow-up.
Triage
Verdict: build now. This is a test-only hardening with no engine gate. The premise is correct, with one design correction: the constants cannot live on Response itself, because interface fields are implicitly public. Route: Sonnet.
Engine facts (checked against v0.22.0 @ 3f47706; origin/master @ 2d27036 is identical for this def)
$defs/StubResponse(sdk-conformance/schema/imposter.schema.json:1499) isadditionalProperties: false, with properties_behaviors, _mode, _rift, behaviors, body, fault, headers, inject, is, proxy, repeat, statusCode.origin/masterdeclares the same property set; its 6-line schema diff touches other defs.- The schema is generated from
StubResponseRaw(crates/rift-mock-core/src/imposter/types.rs:1029-1090, wired in at:3068), so the 12 keys are exactly the serde fields the engine reads.
rift-java today
Response.javarepeats each variant's key set inline, twice: once in the canonical constructor and once inread().- Is:
:42,:162 - Proxy:
:63,:168 - Inject:
:91,:174 - Fault:
:115,:180 - RiftScript:
:142,:191 - Behavior-key subsets:
:200(flat form),:277(withoutBehaviorKeys)
- Is:
Response.javais the only model file left with inline sets. Every other model type has aprivate static final Set<String> MODELED_KEYS(for exampleStub.java:36,IsResponse.java:36).SchemaConformanceTest.java:58-68maps model types to schema defs.Responseis not in that map.modeledKeys()(:179-183) reflectsClass.forName("io.github.achirdlabs.rift.model." + type).getDeclaredField("MODELED_KEYS").- No drift exists today: every inline key is in
StubResponse. The gap is coverage only.
Decision
- Build it. It adds no public surface and changes no behaviour, so the should-question is settled.
- Correction to fix step 1: a "single union
MODELED_KEYS" onResponseis not possible as a private constant.Responseis an interface, so any field on it ispublic static final, which would add public API. Put aprivate static final Set<String> MODELED_KEYSin each nested record instead. Records allow private static fields, andResponse.read()can read them through nestmate access. - The 5 variant constants are what the test needs. Also lift the repeated behavior keys into a package-private-by-nesting constant. That removes the
:200/:277literals and keeps the five sets in step.
Design
record Is(...) implements Response {
private static final Set<String> MODELED_KEYS = Set.of("is", "_behaviors", "behaviors", "_rift", "repeat");
public Is { ...; JsonSupport.rejectModeledExtraKeys(extra, MODELED_KEYS, "is response"); ... }
}
record Proxy(...) { private static final Set<String> MODELED_KEYS = Set.of("proxy", "_behaviors", "behaviors", "repeat"); }
record Inject(...) { private static final Set<String> MODELED_KEYS = Set.of("inject", "_behaviors", "behaviors", "repeat"); }
record Fault(...) { private static final Set<String> MODELED_KEYS = Set.of("fault", "_behaviors", "behaviors", "repeat"); }
record RiftScript(...) { private static final Set<String> MODELED_KEYS = Set.of("_rift", "_behaviors", "behaviors", "repeat"); }
// read(): JsonSupport.extraFields(obj, Is.MODELED_KEYS) etc.Test map (Class.forName takes the binary name, so the key is Response$Is):
MODELED_KEYS_DEFS.put("Response$Is", "StubResponse");
MODELED_KEYS_DEFS.put("Response$Proxy", "StubResponse");
MODELED_KEYS_DEFS.put("Response$Inject", "StubResponse");
MODELED_KEYS_DEFS.put("Response$Fault", "StubResponse");
MODELED_KEYS_DEFS.put("Response$RiftScript", "StubResponse");Optional: the flat form reads IsResponse.MODELED_KEYS at stub-response level (Response.java:182,200). Covering it would mean also checking IsResponse against StubResponse, but MODELED_KEYS_DEFS is keyed by type, so that needs a separate assertion or a list of pairs. Leave it out unless it stays a one-liner.
Implementation plan
-
rift-java-core/src/main/java/io/github/achirdlabs/rift/model/Response.java: add per-recordMODELED_KEYS, and replace the 10 inlineSet.ofuses at:42,63,91,115,142,162,168,174,180,191. Optionally add a shared behavior-key constant for:200,:277. Keep the existing Javadoc. No signature changes. -
rift-java-conformance/src/test/java/io/github/achirdlabs/rift/conformance/SchemaConformanceTest.java: add the 5Response$…entries to the static block at:60-68.
Tests
- Unit (core): the existing response tests must pass unchanged:
ResponseFieldPreservationTest,ResponseLevelRepeatTest,FlatResponseFormTest,ProxyResponseRoundTripTest,FaultResponseRoundTripTest,RiftScriptResponseRoundTripTest. Since behaviour does not change, no new core test is needed. - Conformance: run
modeledKeysAreKeysTheEngineReadsagainst the 0.22.0 corpus, usingRIFT_CORPUS_ROOT=<…>/sdk-conformance-v0.22.0or-Drift.corpus.root. Run it withRIFT_IT=1in the CI lane. Without a corpus the test skips, so a skip does not count as a pass. - Prove the gate bites: temporarily add a bogus key (for example
"isx") toIs.MODELED_KEYS. The test must fail withResponse$Is=[isx]. Revert the key afterwards. - Embedded and testcontainers ITs are not relevant (no wire change).
Acceptance criteria
Response.javahas no inlineSet.of(...)key literals. Each variant's constructor and itsread()branch share oneprivate static final MODELED_KEYS.SchemaConformanceTest.MODELED_KEYS_DEFSmaps all 5Responsevariants toStubResponse, and the test passes against the rift 0.22.0 schema.- There is no public API or binary change. japicmp/revapi, if configured, is clean, and no new public field exists on
Response. - rift-scala impact: none. The new constants are private and the wire output is unchanged.
Gating
None. Everything can land now against the pinned 0.22.0 corpus, and StubResponse is unchanged on rift origin/master.
Activity