using FluentAssertions; using Microsoft.EntityFrameworkCore; using Workflow.Application.Form.FormData.Commands; using Workflow.Application.Form.FormData.Queries; using Workflow.Domain.Enums; using Workflow.Domain.Exceptions; using Workflow.Infrastructure.Persistence; using Xunit; namespace Workflow.Tests.Form; [Collection("FormTests")] public class FormDataTests { private readonly FormTestFixture _fixture; public FormDataTests(FormTestFixtureClassFixture fixture) { _fixture = fixture; } /// /// 辅助方法:创建一个已发布的表单定义(含 Formily Schema),用于 FormData 测试。 /// Schema 包含:name (string, required)、age (number)、gender (string, required)、resume (array)。 /// private static Domain.Entities.FormDefinition CreatePublishedFormDefinition(Guid formId) { return new Domain.Entities.FormDefinition { Id = formId, Name = "测试表单", Code = $"TEST_FORM_{formId:N}", Version = 1, Status = FormStatus.Published, SchemaJson = """ { "type": "object", "properties": { "name": { "type": "string", "title": "姓名", "required": true, "x-decorator": "FormItem", "x-component": "Input" }, "age": { "type": "number", "title": "年龄", "x-decorator": "FormItem", "x-component": "InputNumber", "x-component-props": { "min": 0, "max": 150 } }, "gender": { "type": "string", "title": "性别", "required": true, "enum": [ { "label": "男", "value": "male" }, { "label": "女", "value": "female" } ], "x-decorator": "FormItem", "x-component": "Radio.Group" }, "resume": { "type": "array", "title": "简历附件", "x-decorator": "FormItem", "x-component": "Upload", "x-component-props": { "maxSize": 10485760, "accept": ".pdf,.doc,.docx" } } } } """, }; } // ==================================================================== // SubmitFormData // ==================================================================== [Fact] public async Task SubmitFormData_StoresDataJson() { var formId = Guid.NewGuid(); var instanceId = Guid.NewGuid(); var dataJson = """{"name":"张三","age":25,"gender":"male"}"""; await using var db = await _fixture.CreateDbContextWithSeedAsync( testName: nameof(SubmitFormData_StoresDataJson), seedAction: ctx => ctx.FormDefinitions.Add(CreatePublishedFormDefinition(formId))); var handler = new SubmitFormDataCommandHandler(db); var formDataId = await handler.Handle( new SubmitFormDataCommand(FormDefinitionId: formId, InstanceId: instanceId, DataJson: dataJson), CancellationToken.None); formDataId.Should().NotBe(Guid.Empty); var savedData = await db.FormData.FindAsync(formDataId); savedData.Should().NotBeNull(); savedData!.DataJson.Should().Be(dataJson); } [Fact] public async Task SubmitFormData_AssociatesWithFormDefinitionAndInstance() { var formId = Guid.NewGuid(); var instanceId = Guid.NewGuid(); await using var db = await _fixture.CreateDbContextWithSeedAsync( testName: nameof(SubmitFormData_AssociatesWithFormDefinitionAndInstance), seedAction: ctx => ctx.FormDefinitions.Add(CreatePublishedFormDefinition(formId))); var handler = new SubmitFormDataCommandHandler(db); var formDataId = await handler.Handle( new SubmitFormDataCommand(FormDefinitionId: formId, InstanceId: instanceId, DataJson: """{"name":"李四","age":30,"gender":"female"}"""), CancellationToken.None); var savedData = await db.FormData.FindAsync(formDataId); savedData.Should().NotBeNull(); savedData!.FormDefinitionId.Should().Be(formId); savedData.InstanceId.Should().Be(instanceId); } [Fact] public async Task SubmitFormData_RequiredFieldMissing_ThrowsBusinessException() { var formId = Guid.NewGuid(); var instanceId = Guid.NewGuid(); var dataJson = """{"age":25,"gender":"male"}"""; await using var db = await _fixture.CreateDbContextWithSeedAsync( testName: nameof(SubmitFormData_RequiredFieldMissing_ThrowsBusinessException), seedAction: ctx => ctx.FormDefinitions.Add(CreatePublishedFormDefinition(formId))); var handler = new SubmitFormDataCommandHandler(db); var act = () => handler.Handle( new SubmitFormDataCommand(FormDefinitionId: formId, InstanceId: instanceId, DataJson: dataJson), CancellationToken.None); await act.Should().ThrowAsync() .WithMessage("*必填*姓名*"); var count = await db.FormData.CountAsync(); count.Should().Be(0); } [Fact] public async Task SubmitFormData_InvalidNumberType_ThrowsBusinessException() { var formId = Guid.NewGuid(); var instanceId = Guid.NewGuid(); var dataJson = """{"name":"赵六","age":"abc","gender":"male"}"""; await using var db = await _fixture.CreateDbContextWithSeedAsync( testName: nameof(SubmitFormData_InvalidNumberType_ThrowsBusinessException), seedAction: ctx => ctx.FormDefinitions.Add(CreatePublishedFormDefinition(formId))); var handler = new SubmitFormDataCommandHandler(db); var act = () => handler.Handle( new SubmitFormDataCommand(FormDefinitionId: formId, InstanceId: instanceId, DataJson: dataJson), CancellationToken.None); await act.Should().ThrowAsync() .WithMessage("*age*类型*"); var count = await db.FormData.CountAsync(); count.Should().Be(0); } // ==================================================================== // GetFormDataByInstance // ==================================================================== [Fact] public async Task GetFormDataByInstance_ReturnsFormData() { var formId = Guid.NewGuid(); var instanceId = Guid.NewGuid(); var formDataId = Guid.NewGuid(); var dataJson = """{"name":"钱七","age":35,"gender":"male"}"""; await using var db = await _fixture.CreateDbContextWithSeedAsync( testName: nameof(GetFormDataByInstance_ReturnsFormData), seedAction: ctx => { ctx.FormDefinitions.Add(CreatePublishedFormDefinition(formId)); ctx.FormData.Add(new Domain.Entities.FormData { Id = formDataId, FormDefinitionId = formId, InstanceId = instanceId, DataJson = dataJson, }); }); var handler = new GetFormDataByInstanceQueryHandler(db); var result = await handler.Handle(new GetFormDataByInstanceQuery(InstanceId: instanceId), CancellationToken.None); result.Should().NotBeNull(); result!.Id.Should().Be(formDataId); result.DataJson.Should().Be(dataJson); } [Fact] public async Task GetFormDataByInstance_NotFound_ReturnsNull() { await using var db = _fixture.CreateDbContext(testName: nameof(GetFormDataByInstance_NotFound_ReturnsNull)); var handler = new GetFormDataByInstanceQueryHandler(db); var result = await handler.Handle(new GetFormDataByInstanceQuery(InstanceId: Guid.NewGuid()), CancellationToken.None); result.Should().BeNull(); } }