Skip to content

Commit a70081c

Browse files
dmealingclaude
andcommitted
test(fr-033): Java — gate structural-child placement enforcement
Pins that a misplaced structural child is rejected at strict load: a relationship.association under object.projection (projection's strict child set omits relationship) and a field under an attr-only validator.required (empty strict child set). Java already enforces this eagerly at MetaData.addChild via MetaDataRegistry.acceptsChild against the strict child graph (S-B2a), throwing ERR_CHILD_NOT_ALLOWED — this test pins it against regression. Mirrors the TS child-placement-enforcement test. Additive (test only); registry-conformance unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9b204a7 commit a70081c

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.metaobjects.loader.parser.json;
2+
3+
import com.metaobjects.MetaDataException;
4+
import com.metaobjects.loader.InMemoryStringSource;
5+
import com.metaobjects.loader.MetaDataLoader;
6+
import com.metaobjects.registry.SharedRegistryTestBase;
7+
import org.junit.Test;
8+
9+
import java.util.Collections;
10+
import java.util.List;
11+
12+
import static org.junit.Assert.*;
13+
14+
/**
15+
* FR-033 (sub-step B2a) — structural-child placement enforcement at strict load.
16+
*
17+
* <p>The strict per-subtype constraint model (read from {@code spec/metamodel/*.json})
18+
* replaced Java's broad child rules with the same strict child graph TS enforces. A
19+
* STRUCTURAL child (field/identity/source/validator/relationship/… — NOT an attr) placed
20+
* under a parent whose registered child rules do NOT admit it must be rejected at load —
21+
* the structural analogue of {@code ERR_UNKNOWN_ATTR} for misplaced attrs.</p>
22+
*
23+
* <p>This is the Java mirror of the TS gating test
24+
* {@code server/typescript/packages/metadata/test/child-placement-enforcement.test.ts}.
25+
* Java enforces placement eagerly at {@code MetaData.addChild} via
26+
* {@code MetaDataRegistry.acceptsChild} (which consults the strict child graph), throwing
27+
* an {@link com.metaobjects.InvalidMetaDataException} (a {@link MetaDataException}) during
28+
* the parse. The thrown error carries {@link com.metaobjects.ErrorCode#ERR_CHILD_NOT_ALLOWED}
29+
* and a detail naming the parent, the rejected child ({@code type.subType 'name'}), and
30+
* the supported children — matching the cross-port {@code ERR_CHILD_NOT_ALLOWED} contract
31+
* in {@code fixtures/conformance/ERROR-CODES.json}.</p>
32+
*
33+
* <p>Two genuinely-misplaced cases are pinned:</p>
34+
* <ol>
35+
* <li>A {@code relationship.*} child under an {@code object.projection} — the strict
36+
* projection child set is field/identity/validator/layout/source, with NO
37+
* relationship (a projection expresses derivation via {@code @via}, never a
38+
* relationship child; see ADR-0028 / {@code spec/metamodel/object.json}).</li>
39+
* <li>A structural child (a {@code field}) under an attr-only type
40+
* ({@code validator.required}, whose strict child set is empty).</li>
41+
* </ol>
42+
*/
43+
public class StrictChildPlacementTest extends SharedRegistryTestBase {
44+
45+
private MetaDataLoader newTestLoader() {
46+
return createTestLoader("StrictChildPlacement", Collections.emptyList());
47+
}
48+
49+
/** Attempt a strict load of canonical JSON; returns the thrown exception (or null). */
50+
private MetaDataException loadExpectingThrow(String canonical, String id) {
51+
MetaDataLoader loader = newTestLoader();
52+
try {
53+
loader.load(List.of(new InMemoryStringSource(canonical, id)));
54+
return null;
55+
} catch (MetaDataException e) {
56+
return e;
57+
}
58+
}
59+
60+
// -----------------------------------------------------------------------
61+
// 1 — A relationship child under object.projection is NOT admitted by the
62+
// strict projection child graph → rejected at load.
63+
// -----------------------------------------------------------------------
64+
65+
@Test
66+
public void relationshipUnderProjectionIsRejected() {
67+
// A projection's strict children omit relationship.* entirely.
68+
String canonical =
69+
"{ \"metadata.root\": { \"package\": \"acme\", \"children\": ["
70+
+ " { \"object.entity\": { \"name\": \"Order\", \"children\": ["
71+
+ " { \"field.long\": { \"name\": \"id\" } },"
72+
+ " { \"identity.primary\": { \"name\": \"pk\", \"@fields\": \"id\" } }"
73+
+ " ] } },"
74+
+ " { \"object.projection\": { \"name\": \"OrderView\", \"extends\": \"Order\","
75+
+ " \"children\": ["
76+
+ " { \"relationship.association\": { \"name\": \"bogus\","
77+
+ " \"@cardinality\": \"one\", \"@objectRef\": \"Order\" } }"
78+
+ " ] } }"
79+
+ "] } }";
80+
81+
MetaDataException e = loadExpectingThrow(canonical, "rel-under-projection.json");
82+
assertNotNull("a relationship child under object.projection must be REJECTED at "
83+
+ "strict load (projection's strict child set has no relationship)", e);
84+
String msg = e.getMessage();
85+
assertTrue("error must name the misplaced relationship child; got: " + msg,
86+
msg != null && msg.contains("relationship"));
87+
assertTrue("error must name the projection parent; got: " + msg,
88+
msg != null && msg.contains("projection"));
89+
}
90+
91+
// -----------------------------------------------------------------------
92+
// 2 — A structural (field) child under an attr-only type (validator.required,
93+
// strict child set = []) → rejected at load.
94+
// -----------------------------------------------------------------------
95+
96+
@Test
97+
public void fieldUnderValidatorIsRejected() {
98+
// validator.required admits no structural children — a field child is misplaced.
99+
String canonical =
100+
"{ \"metadata.root\": { \"package\": \"acme\", \"children\": ["
101+
+ " { \"object.entity\": { \"name\": \"Widget\", \"children\": ["
102+
+ " { \"field.long\": { \"name\": \"id\" } },"
103+
+ " { \"field.string\": { \"name\": \"code\", \"children\": ["
104+
+ " { \"validator.required\": { \"name\": \"req\", \"children\": ["
105+
+ " { \"field.string\": { \"name\": \"bogus\" } }"
106+
+ " ] } }"
107+
+ " ] } },"
108+
+ " { \"identity.primary\": { \"name\": \"pk\", \"@fields\": \"id\" } }"
109+
+ " ] } }"
110+
+ "] } }";
111+
112+
MetaDataException e = loadExpectingThrow(canonical, "field-under-validator.json");
113+
assertNotNull("a field child under an attr-only validator.required must be REJECTED "
114+
+ "at strict load (validator's strict child set is empty)", e);
115+
String msg = e.getMessage();
116+
assertTrue("error must name the misplaced field child; got: " + msg,
117+
msg != null && msg.contains("field"));
118+
assertTrue("error must name the validator parent; got: " + msg,
119+
msg != null && msg.contains("validator"));
120+
}
121+
}

0 commit comments

Comments
 (0)