2026-07-24 09:00:09 +08:00

107 lines
3.6 KiB
C#

using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using Workflow.Application.BuildFlow.Common.Services;
using Workflow.Application.BuildFlow.Imports.Services;
using Workflow.Application.Form.Aggregates;
using Workflow.Application.Form.Derivation;
using Workflow.Application.Form.Schema;
using Xunit;
namespace BuildFlow.Workflow.Tests.Performance;
/// <summary>
/// 导入批次写入、错误隔离和确定性实例 ID 测试。
/// </summary>
public class ImportBatchWriterTests
{
[Fact]
public async Task WritesValidRowsAndReturnsInvalidRowsWithoutPerRowAggregateCalls()
{
await using var db = CreateDbContext();
var aggregates = Substitute.For<IFormAggregateRefreshService>();
var writer = new ImportBatchWriter(
db,
new FormValueDerivationService([], []),
aggregates);
var formDefinitionId = Guid.NewGuid();
var prepared = PreparedFormSchema.Create(Schema);
var result = await writer.WriteAsync(
Guid.NewGuid(),
formDefinitionId,
prepared,
[
new ImportBatchItem(0, ValidRow(2, "first")),
new ImportBatchItem(
1,
new ExcelParsedRow(
3,
"{}",
[new ExcelCellError("name", "必填字段为空")])),
new ImportBatchItem(2, ValidRow(4, "third")),
],
CancellationToken.None);
result.SuccessCount.Should().Be(2);
result.Errors.Should().ContainSingle(x => x.Row == 3);
(await db.FormData.CountAsync()).Should().Be(2);
await aggregates.Received(1).RefreshBatchAsync(
formDefinitionId,
Arg.Is<IReadOnlyCollection<Guid>>(ids => ids.Count == 2),
Arg.Any<CancellationToken>());
await aggregates.Received(1).RefreshAffectedBatchAsync(
formDefinitionId,
Arg.Is<IReadOnlyCollection<Guid>>(ids => ids.Count == 2),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task UsesFullJobAndRowIndexForDeterministicInstanceIds()
{
await using var db = CreateDbContext();
var aggregates = Substitute.For<IFormAggregateRefreshService>();
var writer = new ImportBatchWriter(
db,
new FormValueDerivationService([], []),
aggregates);
await writer.WriteAsync(
Guid.Parse("8e6fcacf-3bc1-4d5e-84c4-92be4208855c"),
Guid.NewGuid(),
PreparedFormSchema.Create(Schema),
[
new ImportBatchItem(1, ValidRow(2, "first")),
new ImportBatchItem(65537, ValidRow(3, "second")),
],
CancellationToken.None);
var instanceIds = await db.FormData
.Select(x => x.InstanceId)
.ToListAsync();
instanceIds.Should().OnlyHaveUniqueItems();
}
private static WorkflowDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<WorkflowDbContext>()
.UseInMemoryDatabase($"import-batch-writer-{Guid.NewGuid():N}")
.Options;
var db = new WorkflowDbContext(options);
db.SavingChanges += AuditStub.OnSave;
return db;
}
private static ExcelParsedRow ValidRow(int rowNumber, string name)
=> new(rowNumber, $$"""{"name":"{{name}}"}""", []);
private const string Schema = """
{
"type": "object",
"properties": {
"name": { "type": "string", "required": true, "x-component": "Input" }
}
}
""";
}