|
| 1 | +using MetaObjects.Cli; |
| 2 | +using Xunit; |
| 3 | + |
| 4 | +namespace MetaObjects.Cli.Tests; |
| 5 | + |
| 6 | +/// <summary> |
| 7 | +/// #96 — `dotnet meta verify` is strict-by-default (ADR-0023), with a `--lax` |
| 8 | +/// escape. An undeclared / typo'd own `@attr` is <c>ERR_UNKNOWN_ATTR</c> and verify |
| 9 | +/// surfaces it as a load error (non-zero exit); `--lax` restores the legacy |
| 10 | +/// open-attr load so the same metadata passes. |
| 11 | +/// |
| 12 | +/// This closes the port-dependent verdict where an unregistered attr silently |
| 13 | +/// passed verify in C# (lax load) but was rejected by Java's force-strict Maven |
| 14 | +/// goal. The loader-level error code/text is unchanged — only the verify CLI's |
| 15 | +/// default strictness moves (and only verify; gen/docs/agent-docs stay lax). |
| 16 | +/// </summary> |
| 17 | +public sealed class VerifyStrictTests : IDisposable |
| 18 | +{ |
| 19 | + private readonly string _tmp = Path.Combine(Path.GetTempPath(), "meta-verify-strict-" + Guid.NewGuid().ToString("N")); |
| 20 | + private string MetaDir => Path.Combine(_tmp, "metaobjects"); |
| 21 | + private string TplDir => Path.Combine(_tmp, "templates"); |
| 22 | + |
| 23 | + // A registered node (object.entity / field.string) carrying ONE made-up own |
| 24 | + // attribute (@madeUpAttr) that no metamodel provider declares. Strict load → |
| 25 | + // ERR_UNKNOWN_ATTR; lax load → accepted. |
| 26 | + private const string MetadataWithUnknownAttr = """ |
| 27 | + { "metadata.root": { "package": "acme", "children": [ |
| 28 | + { "object.entity": { "name": "Subscriber", "children": [ |
| 29 | + { "source.rdb": { "@table": "subscribers" } }, |
| 30 | + { "field.long": { "name": "id" } }, |
| 31 | + { "field.string": { "name": "email", "@madeUpAttr": "oops" } }, |
| 32 | + { "identity.primary": { "@fields": "id" } } |
| 33 | + ]}} |
| 34 | + ]}} |
| 35 | + """; |
| 36 | + |
| 37 | + public VerifyStrictTests() |
| 38 | + { |
| 39 | + Directory.CreateDirectory(MetaDir); |
| 40 | + Directory.CreateDirectory(TplDir); |
| 41 | + File.WriteAllText(Path.Combine(MetaDir, "meta.acme.json"), MetadataWithUnknownAttr); |
| 42 | + } |
| 43 | + |
| 44 | + public void Dispose() { try { Directory.Delete(_tmp, recursive: true); } catch { } } |
| 45 | + |
| 46 | + private VerifyCommand.Options Opts(bool strict) => |
| 47 | + new() |
| 48 | + { |
| 49 | + MetadataDir = MetaDir, |
| 50 | + TemplatesRoot = TplDir, |
| 51 | + Templates = true, |
| 52 | + Strict = strict, |
| 53 | + }; |
| 54 | + |
| 55 | + [Fact] |
| 56 | + public void Verify_strict_by_default_rejects_an_unregistered_attr() |
| 57 | + { |
| 58 | + // Default Options.Strict is true → the made-up @attr is ERR_UNKNOWN_ATTR. |
| 59 | + var opts = new VerifyCommand.Options { MetadataDir = MetaDir, TemplatesRoot = TplDir, Templates = true }; |
| 60 | + Assert.True(opts.Strict); // strict is the default |
| 61 | + |
| 62 | + var r = VerifyCommand.RunSubverbs(opts); |
| 63 | + Assert.NotEqual(0, r.ExitCode); |
| 64 | + Assert.NotNull(r.Templates); |
| 65 | + Assert.Contains(ErrorCode.ERR_UNKNOWN_ATTR.ToString(), r.Templates!.LoadErrors); |
| 66 | + } |
| 67 | + |
| 68 | + [Fact] |
| 69 | + public void Verify_lax_accepts_an_unregistered_attr() |
| 70 | + { |
| 71 | + var r = VerifyCommand.RunSubverbs(Opts(strict: false)); |
| 72 | + // No load errors under lax — the made-up attr is tolerated. |
| 73 | + Assert.DoesNotContain(ErrorCode.ERR_UNKNOWN_ATTR.ToString(), r.Templates!.LoadErrors); |
| 74 | + Assert.Empty(r.Templates!.LoadErrors); |
| 75 | + } |
| 76 | + |
| 77 | + [Fact] |
| 78 | + public void Run_strict_surfaces_unknown_attr_as_a_load_error() |
| 79 | + { |
| 80 | + var strict = VerifyCommand.Run(MetaDir, TplDir, strict: true); |
| 81 | + Assert.Contains(ErrorCode.ERR_UNKNOWN_ATTR.ToString(), strict.LoadErrors); |
| 82 | + Assert.False(strict.Ok); |
| 83 | + |
| 84 | + var lax = VerifyCommand.Run(MetaDir, TplDir, strict: false); |
| 85 | + Assert.DoesNotContain(ErrorCode.ERR_UNKNOWN_ATTR.ToString(), lax.LoadErrors); |
| 86 | + } |
| 87 | + |
| 88 | + [Fact] |
| 89 | + public void Codegen_gate_is_strict_by_default_and_reports_the_unknown_attr() |
| 90 | + { |
| 91 | + var opts = new VerifyCommand.Options |
| 92 | + { |
| 93 | + MetadataDir = MetaDir, |
| 94 | + OutDir = Path.Combine(_tmp, "generated"), |
| 95 | + Codegen = true, |
| 96 | + // Strict defaults true. |
| 97 | + }; |
| 98 | + var r = VerifyCommand.RunSubverbs(opts); |
| 99 | + // Strict load fails before any diff → codegen error (exit 2), naming the code. |
| 100 | + Assert.NotEqual(0, r.ExitCode); |
| 101 | + Assert.NotNull(r.Codegen!.Error); |
| 102 | + Assert.Contains(ErrorCode.ERR_UNKNOWN_ATTR.ToString(), r.Codegen!.Error); |
| 103 | + } |
| 104 | +} |
0 commit comments