using ScadaLink.CentralUI.Components.Shared; namespace ScadaLink.CentralUI.Tests.Shared; public class SchemaBuilderModelTests { // ── Parse ───────────────────────────────────────────────────────────────── [Fact] public void Parse_Empty_ReturnsFallback() { var fallback = SchemaBuilderModel.NewObject(); Assert.Same(fallback, SchemaBuilderModel.Parse(null, fallback)); Assert.Same(fallback, SchemaBuilderModel.Parse("", fallback)); Assert.Same(fallback, SchemaBuilderModel.Parse(" ", fallback)); } [Fact] public void Parse_Malformed_ReturnsFallback() { var fallback = SchemaBuilderModel.NewObject(); Assert.Same(fallback, SchemaBuilderModel.Parse("{not json", fallback)); Assert.Same(fallback, SchemaBuilderModel.Parse("42", fallback)); } [Fact] public void Parse_ObjectSchema_ExtractsPropertiesAndRequired() { const string json = """ {"type":"object","properties":{ "id":{"type":"integer"}, "label":{"type":"string"}, "active":{"type":"boolean"} },"required":["id","active"]} """; var node = SchemaBuilderModel.Parse(json, SchemaBuilderModel.NewObject()); Assert.Equal("object", node.Type); Assert.Collection(node.Properties, p => { Assert.Equal("id", p.Name); Assert.Equal("integer", p.Schema.Type); Assert.True(p.Required); }, p => { Assert.Equal("label", p.Name); Assert.Equal("string", p.Schema.Type); Assert.False(p.Required); }, p => { Assert.Equal("active", p.Name); Assert.Equal("boolean", p.Schema.Type); Assert.True(p.Required); }); } [Fact] public void Parse_ArrayOfPrimitive_PreservesItemType() { var node = SchemaBuilderModel.Parse( @"{""type"":""array"",""items"":{""type"":""integer""}}", SchemaBuilderModel.NewValue()); Assert.Equal("array", node.Type); Assert.NotNull(node.Items); Assert.Equal("integer", node.Items!.Type); } [Fact] public void Parse_LegacyFlatArray_TranslatedToObjectSchema() { const string json = """[{"name":"x","type":"Integer"},{"name":"y","type":"String","required":false}]"""; var node = SchemaBuilderModel.Parse(json, SchemaBuilderModel.NewObject()); Assert.Equal("object", node.Type); Assert.Collection(node.Properties, p => { Assert.Equal("x", p.Name); Assert.Equal("integer", p.Schema.Type); Assert.True(p.Required); }, p => { Assert.Equal("y", p.Name); Assert.Equal("string", p.Schema.Type); Assert.False(p.Required); }); } [Fact] public void Parse_NestedObjects_Recurses() { const string json = """ {"type":"object","properties":{ "outer":{"type":"object","properties":{ "inner":{"type":"integer"} },"required":["inner"]} }} """; var node = SchemaBuilderModel.Parse(json, SchemaBuilderModel.NewObject()); var outer = Assert.Single(node.Properties); Assert.Equal("outer", outer.Name); Assert.Equal("object", outer.Schema.Type); var inner = Assert.Single(outer.Schema.Properties); Assert.Equal("inner", inner.Name); Assert.Equal("integer", inner.Schema.Type); Assert.True(inner.Required); } // ── Serialize ───────────────────────────────────────────────────────────── [Fact] public void Serialize_EmptyObject_OmitsRequired() { var node = new SchemaNode { Type = "object" }; var json = SchemaBuilderModel.Serialize(node); Assert.Equal("""{"type":"object","properties":{}}""", json); } [Fact] public void Serialize_ObjectWithMixedRequired_EmitsOnlyRequiredNames() { var node = new SchemaNode { Type = "object" }; node.Properties.Add(new SchemaProperty { Name = "id", Required = true, Schema = new SchemaNode { Type = "integer" } }); node.Properties.Add(new SchemaProperty { Name = "label", Required = false, Schema = new SchemaNode { Type = "string" } }); var json = SchemaBuilderModel.Serialize(node); Assert.Equal( """{"type":"object","properties":{"id":{"type":"integer"},"label":{"type":"string"}},"required":["id"]}""", json); } [Fact] public void Serialize_Array_IncludesItems() { var node = new SchemaNode { Type = "array", Items = new SchemaNode { Type = "string" } }; Assert.Equal("""{"type":"array","items":{"type":"string"}}""", SchemaBuilderModel.Serialize(node)); } [Fact] public void Serialize_PropertiesWithBlankName_Skipped() { var node = new SchemaNode { Type = "object" }; node.Properties.Add(new SchemaProperty { Name = "", Schema = new SchemaNode { Type = "integer" } }); node.Properties.Add(new SchemaProperty { Name = "valid", Schema = new SchemaNode { Type = "string" } }); var json = SchemaBuilderModel.Serialize(node); Assert.Equal("""{"type":"object","properties":{"valid":{"type":"string"}},"required":["valid"]}""", json); } // ── Round-trip ──────────────────────────────────────────────────────────── [Fact] public void RoundTrip_Parse_Then_Serialize_Stable() { const string original = """{"type":"object","properties":{"id":{"type":"integer"},"tags":{"type":"array","items":{"type":"string"}}},"required":["id"]}"""; var node = SchemaBuilderModel.Parse(original, SchemaBuilderModel.NewObject()); var roundTripped = SchemaBuilderModel.Serialize(node); Assert.Equal(original, roundTripped); } }