|
| 1 | +package com.metaobjects.field; |
| 2 | + |
| 3 | +import org.junit.Test; |
| 4 | + |
| 5 | +import java.util.Arrays; |
| 6 | + |
| 7 | +import static org.junit.Assert.assertEquals; |
| 8 | +import static org.junit.Assert.assertTrue; |
| 9 | + |
| 10 | +/** |
| 11 | + * FR-037 R1 — the {@code @mutability} tightening order is load-bearing, so it is pinned. |
| 12 | + * |
| 13 | + * <p>{@code ValidationPhase.mutabilityRank()} is an INDEX comparison over |
| 14 | + * {@link MetaField#MUTABILITY_MODES} ("declaration order IS the tightening order", as the |
| 15 | + * constant's own javadoc says), so reordering that list silently inverts "may only tighten" |
| 16 | + * with nothing to catch it. Kotlin inherits this same constant through the JVM loader, so |
| 17 | + * this pin covers both JVM ports. |
| 18 | + * |
| 19 | + * <p>The shared conformance corpus cannot stand in for this. Its inheritance fixtures pair |
| 20 | + * only {@code readOnly} with {@code readWrite} — the two ENDPOINTS — so a full reversal is |
| 21 | + * caught but a reorder that moves ONLY {@code writeOnce} is not. |
| 22 | + * {@code error-field-mutability-downgrade-writeonce} closes the behavioural half cross-port; |
| 23 | + * this closes the structural half in the port whose rank function reads the list. |
| 24 | + * |
| 25 | + * <p>Mirrors the TypeScript pin (metadata/test/fr037-field-mutability.test.ts), which was the |
| 26 | + * only such pin in any port until now. |
| 27 | + */ |
| 28 | +public class FieldMutabilityOrderTest { |
| 29 | + |
| 30 | + @Test |
| 31 | + public void declarationOrderIsTheTighteningOrder() { |
| 32 | + // Loosest first. The downgrade rule is rank(child) >= rank(parent), so this |
| 33 | + // order IS the rule. |
| 34 | + assertEquals( |
| 35 | + Arrays.asList("readWrite", "writeOnce", "readOnly"), |
| 36 | + MetaField.MUTABILITY_MODES); |
| 37 | + } |
| 38 | + |
| 39 | + @Test |
| 40 | + public void modeSpellings() { |
| 41 | + // The wire spellings travel cross-port; a typo here is a silent divergence. |
| 42 | + assertEquals("readWrite", MetaField.MUTABILITY_READ_WRITE); |
| 43 | + assertEquals("writeOnce", MetaField.MUTABILITY_WRITE_ONCE); |
| 44 | + assertEquals("readOnly", MetaField.MUTABILITY_READ_ONLY); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * The specific relationship the corpus never exercises. Stated as an explicit rank |
| 49 | + * comparison rather than inferred from the list above, so a future change that keeps the |
| 50 | + * list's CONTENTS but alters how rank is derived still fails here. |
| 51 | + */ |
| 52 | + @Test |
| 53 | + public void writeOnceRanksBetweenTheTwoEndpoints() { |
| 54 | + int readWrite = MetaField.MUTABILITY_MODES.indexOf(MetaField.MUTABILITY_READ_WRITE); |
| 55 | + int writeOnce = MetaField.MUTABILITY_MODES.indexOf(MetaField.MUTABILITY_WRITE_ONCE); |
| 56 | + int readOnly = MetaField.MUTABILITY_MODES.indexOf(MetaField.MUTABILITY_READ_ONLY); |
| 57 | + assertTrue("readWrite must rank looser than writeOnce", readWrite < writeOnce); |
| 58 | + assertTrue("writeOnce must rank looser than readOnly", writeOnce < readOnly); |
| 59 | + } |
| 60 | +} |
0 commit comments