using FluentAssertions; using Workflow.Application.Form.Schema; using Xunit; namespace Workflow.Tests.Form; public class SchemaDifferTests { private const string OldSchema = """ { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "required": true, "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-component": "InputNumber" }, "gender": { "type": "string", "title": "性别", "x-component": "Select" } } } """; [Fact] public void Diff_NoChanges_ReturnsEmpty() { var diff = SchemaDiffer.Diff(OldSchema, OldSchema); diff.Added.Should().BeEmpty(); diff.Removed.Should().BeEmpty(); diff.Modified.Should().BeEmpty(); } [Fact] public void Diff_DetectsAddedField() { var newSchema = """ { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "required": true, "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-component": "InputNumber" }, "gender": { "type": "string", "title": "性别", "x-component": "Select" }, "email": { "type": "string", "title": "邮箱", "x-component": "Input" } } } """; var diff = SchemaDiffer.Diff(OldSchema, newSchema); diff.Added.Should().ContainSingle(f => f.Path == "email"); diff.Removed.Should().BeEmpty(); diff.Modified.Should().BeEmpty(); } [Fact] public void Diff_DetectsRemovedField() { var newSchema = """ { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "required": true, "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-component": "InputNumber" } } } """; var diff = SchemaDiffer.Diff(OldSchema, newSchema); diff.Removed.Should().ContainSingle(f => f.Path == "gender"); diff.Added.Should().BeEmpty(); } [Fact] public void Diff_DetectsComponentChange() { var newSchema = """ { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "required": true, "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-component": "Input" }, "gender": { "type": "string", "title": "性别", "x-component": "Select" } } } """; var diff = SchemaDiffer.Diff(OldSchema, newSchema); diff.Modified.Should().ContainSingle(f => f.Path == "age" && f.Change!.Contains("组件")); } [Fact] public void Diff_DetectsRequiredChange() { var newSchema = """ { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-component": "InputNumber" }, "gender": { "type": "string", "title": "性别", "x-component": "Select" } } } """; var diff = SchemaDiffer.Diff(OldSchema, newSchema); diff.Modified.Should().ContainSingle(f => f.Path == "name" && f.Change!.Contains("必填")); } [Fact] public void Diff_HandlesNullInputs() { var diff = SchemaDiffer.Diff(null, null); diff.Added.Should().BeEmpty(); diff.Removed.Should().BeEmpty(); diff.Modified.Should().BeEmpty(); } [Fact] public void Diff_HandlesInvalidJson() { var diff = SchemaDiffer.Diff("not json", "{ }"); // 无效的旧 schema 视为空,新 schema 也空,无差异 diff.Added.Should().BeEmpty(); diff.Removed.Should().BeEmpty(); } [Fact] public void Diff_HandlesNestedFields() { var oldNested = """ { "type": "object", "properties": { "card": { "type": "void", "x-component": "Card", "properties": { "name": { "type": "string", "title": "姓名", "x-component": "Input" } } } } } """; var newNested = """ { "type": "object", "properties": { "card": { "type": "void", "x-component": "Card", "properties": { "name": { "type": "string", "title": "姓名", "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-component": "InputNumber" } } } } } """; var diff = SchemaDiffer.Diff(oldNested, newNested); diff.Added.Should().ContainSingle(f => f.Path == "card.age"); } }