Skip to content

Commit 04f981e

Browse files
dmealingclaude
andcommitted
test: FR-010 template-output-{json,xml}-simple conformance fixtures + Java conformance test
Two new cross-port conformance fixtures document the FR-010 output codegen contract as data: template-output-json-simple (@Format:json, @PromptStyle:inline) and template-output-xml-simple (@Format:xml, @PromptStyle:guide), each with a SupportAnswer value-object carrying string, enum (@enumAlias, @enumDoc), and optional-string fields. TemplateOutputFixtureConformanceTest (parameterized, 2 cases) loads each fixture file from the repo-root fixtures/conformance/ directory, runs both SpringOutputPromptGenerator and SpringOutputParserGenerator, and asserts structural markers: OutputFormatRenderer.render(, both renderFormat() overloads, the per-fixture PromptStyle/Format constants, recover(, RecoverSchema RECOVER_SCHEMA, parse(, and the medium->OK enumAlias entry in the schema literal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1ad18ba commit 04f981e

7 files changed

Lines changed: 355 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# template-output-json-simple
2+
3+
Documents the FR-010 output codegen contract for a `template.output` with `@format: json`
4+
and `@promptStyle: inline`.
5+
6+
The fixture declares a `SupportAnswer` value-object with a string field, an enum field
7+
(with `@enumAlias` and `@enumDoc`), and an optional note field, wired to a
8+
`SupportAnswerOutput` template.
9+
10+
## Conformance notes
11+
12+
- **Java** asserts structurally: the generators emit `SupportAnswerOutputPrompt.java`
13+
(containing `OutputFormatRenderer.render(`, `PromptStyle.INLINE`, `Format.JSON`,
14+
and both `renderFormat()` overloads) and `SupportAnswerOutputParser.java`
15+
(containing `recover(`, `RecoverSchema RECOVER_SCHEMA`, `parse(`, and the
16+
`medium``OK` enumAlias entry).
17+
- Behavioral correctness is proven by the codegen-spring compile-run tests
18+
(`GeneratedOutputPromptCompileRunTest`, `GeneratedRecoverCompileRunTest`).
19+
- Byte-identical golden output is the TypeScript / cross-port concern (see
20+
`expected/` directories in sibling TS fixtures).
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::support",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "SupportAnswer",
8+
"children": [
9+
{
10+
"field.string": {
11+
"name": "text",
12+
"@example": "Your refund will appear in 3-5 days.",
13+
"@instruction": "One or two sentences to the customer.",
14+
"@required": true
15+
}
16+
},
17+
{
18+
"field.enum": {
19+
"name": "confidence",
20+
"@enumAlias": {
21+
"medium": "OK"
22+
},
23+
"@enumDoc": {
24+
"HIGH": "Directly supported.",
25+
"LOW": "A guess.",
26+
"OK": "Inference."
27+
},
28+
"@required": true,
29+
"@values": [
30+
"HIGH",
31+
"OK",
32+
"LOW"
33+
]
34+
}
35+
},
36+
{
37+
"field.string": {
38+
"name": "note"
39+
}
40+
}
41+
]
42+
}
43+
},
44+
{
45+
"template.output": {
46+
"name": "SupportAnswerOutput",
47+
"@format": "json",
48+
"@payloadRef": "SupportAnswer",
49+
"@promptStyle": "inline",
50+
"@textRef": "support/answer"
51+
}
52+
}
53+
]
54+
}
55+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::support",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "SupportAnswer",
8+
"children": [
9+
{ "field.string": { "name": "text", "@required": true, "@example": "Your refund will appear in 3-5 days.", "@instruction": "One or two sentences to the customer." } },
10+
{ "field.enum": { "name": "confidence", "@required": true, "@values": ["HIGH","OK","LOW"], "@enumAlias": { "medium": "OK" }, "@enumDoc": { "HIGH": "Directly supported.", "OK": "Inference.", "LOW": "A guess." } } },
11+
{ "field.string": { "name": "note" } }
12+
]
13+
}
14+
},
15+
{
16+
"template.output": {
17+
"name": "SupportAnswerOutput",
18+
"@payloadRef": "SupportAnswer",
19+
"@textRef": "support/answer",
20+
"@format": "json",
21+
"@promptStyle": "inline"
22+
}
23+
}
24+
]
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# template-output-xml-simple
2+
3+
Documents the FR-010 output codegen contract for a `template.output` with `@format: xml`
4+
and `@promptStyle: guide`.
5+
6+
The fixture declares the same `SupportAnswer` value-object as `template-output-json-simple`
7+
(string field, enum field with `@enumAlias` and `@enumDoc`, optional note), wired to a
8+
`SupportAnswerOutput` template with XML format and guide prompt style.
9+
10+
## Conformance notes
11+
12+
- **Java** asserts structurally: the generators emit `SupportAnswerOutputPrompt.java`
13+
(containing `OutputFormatRenderer.render(`, `PromptStyle.GUIDE`, `Format.XML`,
14+
and both `renderFormat()` overloads) and `SupportAnswerOutputParser.java`
15+
(containing `recover(`, `RecoverSchema RECOVER_SCHEMA`, `parse(`, and the
16+
`medium``OK` enumAlias entry).
17+
- Behavioral correctness is proven by the codegen-spring compile-run tests
18+
(`GeneratedOutputPromptCompileRunTest`, `GeneratedRecoverCompileRunTest`).
19+
- Byte-identical golden output is the TypeScript / cross-port concern (see
20+
`expected/` directories in sibling TS fixtures).
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::support",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "SupportAnswer",
8+
"children": [
9+
{
10+
"field.string": {
11+
"name": "text",
12+
"@example": "Your refund will appear in 3-5 days.",
13+
"@instruction": "One or two sentences to the customer.",
14+
"@required": true
15+
}
16+
},
17+
{
18+
"field.enum": {
19+
"name": "confidence",
20+
"@enumAlias": {
21+
"medium": "OK"
22+
},
23+
"@enumDoc": {
24+
"HIGH": "Directly supported.",
25+
"LOW": "A guess.",
26+
"OK": "Inference."
27+
},
28+
"@required": true,
29+
"@values": [
30+
"HIGH",
31+
"OK",
32+
"LOW"
33+
]
34+
}
35+
},
36+
{
37+
"field.string": {
38+
"name": "note"
39+
}
40+
}
41+
]
42+
}
43+
},
44+
{
45+
"template.output": {
46+
"name": "SupportAnswerOutput",
47+
"@format": "xml",
48+
"@payloadRef": "SupportAnswer",
49+
"@promptStyle": "guide",
50+
"@textRef": "support/answer"
51+
}
52+
}
53+
]
54+
}
55+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::support",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "SupportAnswer",
8+
"children": [
9+
{ "field.string": { "name": "text", "@required": true, "@example": "Your refund will appear in 3-5 days.", "@instruction": "One or two sentences to the customer." } },
10+
{ "field.enum": { "name": "confidence", "@required": true, "@values": ["HIGH","OK","LOW"], "@enumAlias": { "medium": "OK" }, "@enumDoc": { "HIGH": "Directly supported.", "OK": "Inference.", "LOW": "A guess." } } },
11+
{ "field.string": { "name": "note" } }
12+
]
13+
}
14+
},
15+
{
16+
"template.output": {
17+
"name": "SupportAnswerOutput",
18+
"@payloadRef": "SupportAnswer",
19+
"@textRef": "support/answer",
20+
"@format": "xml",
21+
"@promptStyle": "guide"
22+
}
23+
}
24+
]
25+
}
26+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package com.metaobjects.generator.spring;
2+
3+
import com.metaobjects.loader.MetaDataLoader;
4+
import com.metaobjects.registry.SharedRegistryTestBase;
5+
import org.junit.Rule;
6+
import org.junit.Test;
7+
import org.junit.rules.TemporaryFolder;
8+
import org.junit.runner.RunWith;
9+
import org.junit.runners.Parameterized;
10+
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.nio.file.Paths;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
18+
import static org.junit.Assert.assertTrue;
19+
20+
/**
21+
* Conformance test for the FR-010 output codegen contract, loading the cross-port
22+
* fixtures from {@code fixtures/conformance/template-output-{json,xml}-simple/input/meta.json}.
23+
*
24+
* <p>Asserts the key structural markers emitted by both
25+
* {@link SpringOutputPromptGenerator} and {@link SpringOutputParserGenerator} for each
26+
* fixture. Behavioral correctness is proven by the compile-run tests
27+
* ({@code GeneratedOutputPromptCompileRunTest}, {@code GeneratedRecoverCompileRunTest}).
28+
*
29+
* <p>Repo root is located by walking up from {@code user.dir} until the
30+
* {@code fixtures/conformance} directory is found — the same strategy used by
31+
* {@code RecoverConformanceTest} in the render module.
32+
*/
33+
@RunWith(Parameterized.class)
34+
public class TemplateOutputFixtureConformanceTest extends SharedRegistryTestBase {
35+
36+
// -------------------------------------------------------------------------
37+
// Locate repo root once at class-load time.
38+
// -------------------------------------------------------------------------
39+
40+
private static final Path CONFORMANCE_ROOT;
41+
static {
42+
Path p = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
43+
while (p != null && !Files.exists(p.resolve("fixtures/conformance"))) {
44+
p = p.getParent();
45+
}
46+
CONFORMANCE_ROOT = (p != null) ? p.resolve("fixtures/conformance") : null;
47+
}
48+
49+
// -------------------------------------------------------------------------
50+
// Parameterized cases: (fixtureName, promptStyleConstant, formatConstant)
51+
// -------------------------------------------------------------------------
52+
53+
@Parameterized.Parameters(name = "{0}")
54+
public static List<Object[]> cases() {
55+
return List.of(
56+
new Object[]{ "template-output-json-simple", "PromptStyle.INLINE", "Format.JSON" },
57+
new Object[]{ "template-output-xml-simple", "PromptStyle.GUIDE", "Format.XML" }
58+
);
59+
}
60+
61+
@Rule
62+
public TemporaryFolder tmp = new TemporaryFolder();
63+
64+
private final String fixtureName;
65+
private final String promptStyleConstant;
66+
private final String formatConstant;
67+
68+
public TemplateOutputFixtureConformanceTest(
69+
String fixtureName, String promptStyleConstant, String formatConstant) {
70+
this.fixtureName = fixtureName;
71+
this.promptStyleConstant = promptStyleConstant;
72+
this.formatConstant = formatConstant;
73+
}
74+
75+
// -------------------------------------------------------------------------
76+
// Test
77+
// -------------------------------------------------------------------------
78+
79+
@Test
80+
public void bothGeneratorsEmitExpectedStructuralMarkers() throws Exception {
81+
assertTrue("fixtures/conformance directory must be reachable; check user.dir = "
82+
+ System.getProperty("user.dir"), CONFORMANCE_ROOT != null);
83+
84+
Path fixtureInput = CONFORMANCE_ROOT.resolve(fixtureName).resolve("input").resolve("meta.json");
85+
assertTrue("fixture input must exist: " + fixtureInput, Files.exists(fixtureInput));
86+
87+
// Load the fixture file via a temp workspace (mirrors SpringTestFixtures.loadFixture).
88+
String fixtureJson = Files.readString(fixtureInput);
89+
Path workspace = tmp.newFolder("ws-" + fixtureName).toPath();
90+
MetaDataLoader loader = SpringTestFixtures.loadFixture(workspace, fixtureName, fixtureJson);
91+
92+
Path outDir = tmp.newFolder("out-" + fixtureName).toPath();
93+
Map<String, String> args = new HashMap<>();
94+
args.put("outputDir", outDir.toString());
95+
96+
// Run SpringOutputPromptGenerator.
97+
SpringOutputPromptGenerator promptGen = new SpringOutputPromptGenerator();
98+
promptGen.setArgs(args);
99+
promptGen.execute(loader);
100+
101+
// Run SpringOutputParserGenerator.
102+
SpringOutputParserGenerator parserGen = new SpringOutputParserGenerator();
103+
parserGen.setArgs(args);
104+
parserGen.execute(loader);
105+
106+
// ---- Fixtures: package acme::support, template.output name "SupportAnswerOutput"
107+
// outPkg = acme.support.prompts → path acme/support/prompts/
108+
Path promptFile = outDir.resolve("acme/support/prompts/SupportAnswerOutputPrompt.java");
109+
Path parserFile = outDir.resolve("acme/support/prompts/SupportAnswerOutputParser.java");
110+
111+
// ---- Prompt assertions ----
112+
113+
assertTrue("[" + fixtureName + "] expected SupportAnswerOutputPrompt.java at "
114+
+ promptFile, Files.exists(promptFile));
115+
String promptSrc = Files.readString(promptFile);
116+
117+
assertTrue("[" + fixtureName + "] expected `OutputFormatRenderer.render(`; saw:\n" + promptSrc,
118+
promptSrc.contains("OutputFormatRenderer.render("));
119+
120+
assertTrue("[" + fixtureName + "] expected `public static String renderFormat()` (no-arg overload); saw:\n" + promptSrc,
121+
promptSrc.contains("public static String renderFormat()"));
122+
123+
assertTrue("[" + fixtureName + "] expected `public static String renderFormat(PromptOverrides overrides)`; saw:\n" + promptSrc,
124+
promptSrc.contains("public static String renderFormat(PromptOverrides overrides)"));
125+
126+
assertTrue("[" + fixtureName + "] expected `" + promptStyleConstant + "` in prompt source; saw:\n" + promptSrc,
127+
promptSrc.contains(promptStyleConstant));
128+
129+
assertTrue("[" + fixtureName + "] expected `" + formatConstant + "` in prompt source; saw:\n" + promptSrc,
130+
promptSrc.contains(formatConstant));
131+
132+
// ---- Parser assertions ----
133+
134+
assertTrue("[" + fixtureName + "] expected SupportAnswerOutputParser.java at "
135+
+ parserFile, Files.exists(parserFile));
136+
String parserSrc = Files.readString(parserFile);
137+
138+
assertTrue("[" + fixtureName + "] expected `RecoverSchema RECOVER_SCHEMA`; saw:\n" + parserSrc,
139+
parserSrc.contains("RecoverSchema RECOVER_SCHEMA"));
140+
141+
assertTrue("[" + fixtureName + "] expected `recover(`; saw:\n" + parserSrc,
142+
parserSrc.contains("recover("));
143+
144+
assertTrue("[" + fixtureName + "] expected `parse(`; saw:\n" + parserSrc,
145+
parserSrc.contains("parse("));
146+
147+
// @enumAlias medium→OK must appear in the schema literal.
148+
assertTrue("[" + fixtureName + "] expected enumAlias `medium` key in RECOVER_SCHEMA literal; saw:\n" + parserSrc,
149+
parserSrc.contains("\"medium\""));
150+
assertTrue("[" + fixtureName + "] expected enumAlias value `OK` in RECOVER_SCHEMA literal; saw:\n" + parserSrc,
151+
parserSrc.contains("\"OK\""));
152+
}
153+
}

0 commit comments

Comments
 (0)