SchemaTests.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. using System.Globalization;
  2. using System.Text.Json;
  3. using System.Text.Json.Nodes;
  4. using Json.Schema;
  5. using YamlDotNet.RepresentationModel;
  6. namespace Tests.Yaml;
  7. public class SchemaConformanceTests {
  8. private static JsonSchema LoadSchema(int version) {
  9. var schemaText = File.ReadAllText($"schemas/schema.v{version}.json");
  10. return JsonSchema.FromText(schemaText);
  11. }
  12. private static JsonElement ConvertYamlToJsonElement(string yaml) {
  13. // Load YAML into YAML DOM
  14. var yamlStream = new YamlStream();
  15. yamlStream.Load(new StringReader(yaml));
  16. YamlNode root = yamlStream.Documents[0].RootNode;
  17. // Convert YAML node → JSON string
  18. var json = ConvertYamlNodeToJson(root);
  19. using var document = JsonDocument.Parse(json);
  20. return document.RootElement.Clone();
  21. }
  22. private static string ConvertYamlNodeToJson(YamlNode node) {
  23. if (node is YamlScalarNode scalar) {
  24. // Try numeric
  25. if (int.TryParse(scalar.Value, out var i))
  26. return i.ToString();
  27. if (double.TryParse(scalar.Value, out var d))
  28. return d.ToString(CultureInfo.InvariantCulture);
  29. if (bool.TryParse(scalar.Value, out var b))
  30. return b.ToString().ToLowerInvariant();
  31. // Otherwise string
  32. return JsonSerializer.Serialize(scalar.Value);
  33. }
  34. if (node is YamlSequenceNode sequence) {
  35. IEnumerable<string> items = sequence.Children
  36. .Select(ConvertYamlNodeToJson);
  37. return "[" + string.Join(",", items) + "]";
  38. }
  39. if (node is YamlMappingNode mapping) {
  40. IEnumerable<string> props = mapping.Children
  41. .Select(kvp =>
  42. JsonSerializer.Serialize(((YamlScalarNode)kvp.Key).Value)
  43. + ":"
  44. + ConvertYamlNodeToJson(kvp.Value));
  45. return "{" + string.Join(",", props) + "}";
  46. }
  47. return "null";
  48. }
  49. /// <summary>
  50. /// The schema is published three times: the repo root copy tests validate
  51. /// against, and the copies the web app and the viewer serve at
  52. /// /schemas/v{n}/schema.v{n}.json. They are hand-synced, and #310/#311 were
  53. /// what happens when a sync is missed — this pins them together for good.
  54. /// </summary>
  55. [Theory]
  56. [InlineData(1)]
  57. [InlineData(2)]
  58. [InlineData(3)]
  59. [InlineData(4)]
  60. public void The_served_schema_copies_never_drift_from_the_published_one(int version) {
  61. var published = JsonNode.Parse(
  62. File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "schemas", $"schema.v{version}.json")));
  63. foreach (var host in new[] { "web", "viewer" }) {
  64. var served = JsonNode.Parse(File.ReadAllText(
  65. Path.Combine(AppContext.BaseDirectory, "wwwroot-schemas", host, $"schema.v{version}.json")));
  66. Assert.True(
  67. JsonNode.DeepEquals(published, served),
  68. $"The {host} wwwroot copy of schema.v{version}.json differs from schemas/ — " +
  69. "update both together, or documents RackPeek writes will fail the schema it serves.");
  70. }
  71. }
  72. [Theory]
  73. [InlineData(1)]
  74. [InlineData(2)]
  75. [InlineData(3)]
  76. [InlineData(4)]
  77. public void All_yaml_files_conform_to_schema(int version) {
  78. // Arrange
  79. JsonSchema schema = LoadSchema(version);
  80. var yamlFolder = Path.Combine(
  81. AppContext.BaseDirectory,
  82. "TestConfigs",
  83. $"v{version}");
  84. var yamlFiles = Directory
  85. .EnumerateFiles(yamlFolder, "*.yaml", SearchOption.AllDirectories)
  86. .ToList();
  87. Assert.NotEmpty(yamlFiles);
  88. var failures = new List<string>();
  89. // Act
  90. foreach (var file in yamlFiles) {
  91. var yaml = File.ReadAllText(file);
  92. JsonElement jsonNode = ConvertYamlToJsonElement(yaml);
  93. var options = new EvaluationOptions {
  94. OutputFormat = OutputFormat.Hierarchical
  95. };
  96. EvaluationResults result = schema.Evaluate(jsonNode, options);
  97. if (!result.IsValid) {
  98. var errors = new List<string>();
  99. void CollectErrors(EvaluationResults node) {
  100. if (node.Errors != null)
  101. foreach (KeyValuePair<string, string> error in node.Errors)
  102. errors.Add($"{error.Key}: {error.Value}");
  103. if (node.Details != null)
  104. foreach (EvaluationResults child in node.Details)
  105. CollectErrors(child);
  106. }
  107. CollectErrors(result);
  108. failures.Add(
  109. $"File: {file}{Environment.NewLine}" +
  110. string.Join(Environment.NewLine, errors));
  111. }
  112. }
  113. // Assert
  114. if (failures.Any()) {
  115. var message = string.Join(
  116. $"{Environment.NewLine}--------------------{Environment.NewLine}",
  117. failures);
  118. Assert.Fail(message);
  119. }
  120. }
  121. }