| 1 | using System.Text.Json; |
| 2 | using CascadeIDE.Services; |
| 3 | using Json.Schema; |
| 4 | using Xunit; |
| 5 | |
| 6 | namespace CascadeIDE.Tests; |
| 7 | |
| 8 | /// <summary>Profile <c>cide-intent-catalog</c>: bundled catalog is structurally valid and loads with prod semantics.</summary> |
| 9 | public sealed class IntentCatalogProfileSemanticsTests |
| 10 | { |
| 11 | [Fact] |
| 12 | public void Bundled_catalog_matches_json_schema() |
| 13 | { |
| 14 | Assert.True(File.Exists(IntentCatalogProfileSupport.SchemaPath), IntentCatalogProfileSupport.SchemaPath); |
| 15 | |
| 16 | var schema = JsonSchema.FromFile(IntentCatalogProfileSupport.SchemaPath); |
| 17 | var document = IntentCatalogProfileSupport.ToJsonNode(IntentCatalogProfileSupport.ReadSourceCatalogText()); |
| 18 | var element = document.Deserialize<JsonElement>(); |
| 19 | |
| 20 | var result = schema.Evaluate(element, new EvaluationOptions { OutputFormat = OutputFormat.List }); |
| 21 | Assert.True(result.IsValid, FormatSchemaErrors(result)); |
| 22 | } |
| 23 | |
| 24 | [Fact] |
| 25 | public void Bundled_catalog_loads_via_prod_loader() |
| 26 | { |
| 27 | IntentMelodyAliases.ResetForTests(); |
| 28 | var snapshot = IntentMelodyAliases.GetCatalogSnapshot(); |
| 29 | |
| 30 | Assert.NotEmpty(snapshot.SlashRoutes); |
| 31 | Assert.True(snapshot.Roots.Count + snapshot.SlashRoutes.Count > 0); |
| 32 | } |
| 33 | |
| 34 | [Fact] |
| 35 | public void Bundled_catalog_command_ids_are_registered_in_IdeCommands() |
| 36 | { |
| 37 | IntentMelodyAliases.ResetForTests(); |
| 38 | var snapshot = IntentMelodyAliases.GetCatalogSnapshot(); |
| 39 | var unknown = IntentCatalogProfileSupport.CollectCommandIds(snapshot) |
| 40 | .Where(id => !IntentCatalogProfileSupport.TryGetIdeCommandSummary(id, out _)) |
| 41 | .OrderBy(id => id, StringComparer.Ordinal) |
| 42 | .ToList(); |
| 43 | |
| 44 | Assert.True( |
| 45 | unknown.Count == 0, |
| 46 | "command_id not in IdeCommands contract: " + string.Join(", ", unknown)); |
| 47 | } |
| 48 | |
| 49 | [Fact] |
| 50 | public void Source_catalog_declares_schema_directive() |
| 51 | { |
| 52 | using var reader = File.OpenText(IntentCatalogProfileSupport.SourceCatalogPath); |
| 53 | for (var i = 0; i < 8 && !reader.EndOfStream; i++) |
| 54 | { |
| 55 | var line = reader.ReadLine(); |
| 56 | if (line is null) |
| 57 | break; |
| 58 | |
| 59 | if (line.Contains("#:schema", StringComparison.OrdinalIgnoreCase)) |
| 60 | return; |
| 61 | } |
| 62 | |
| 63 | Assert.Fail("Missing #:schema directive in IntentMelody/intent-catalog.toml"); |
| 64 | } |
| 65 | |
| 66 | private static string FormatSchemaErrors(EvaluationResults result) |
| 67 | { |
| 68 | var lines = new List<string>(); |
| 69 | WalkErrors(result, lines); |
| 70 | return lines.Count == 0 ? "JSON Schema validation failed." : string.Join(Environment.NewLine, lines); |
| 71 | } |
| 72 | |
| 73 | private static void WalkErrors(EvaluationResults node, List<string> lines) |
| 74 | { |
| 75 | if (node.Errors is { Count: > 0 }) |
| 76 | { |
| 77 | var location = string.IsNullOrWhiteSpace(node.EvaluationPath.ToString()) |
| 78 | ? "$" |
| 79 | : node.EvaluationPath.ToString(); |
| 80 | foreach (var (key, message) in node.Errors) |
| 81 | lines.Add($"{location}: {key} — {message}"); |
| 82 | } |
| 83 | |
| 84 | if (node.Details is null) |
| 85 | return; |
| 86 | |
| 87 | foreach (var child in node.Details) |
| 88 | WalkErrors(child, lines); |
| 89 | } |
| 90 | } |
| 91 | |