Files
XplorePlane/XplorePlane.Tests/Inspection/InspectionDefinitionRepositoryTests.cs
T
2026-08-10 14:31:44 +08:00

208 lines
8.8 KiB
C#

using Moq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Models.Inspection;
using XplorePlane.Services.Inspection;
using Xunit;
namespace XplorePlane.Tests.Inspection
{
public sealed class InspectionDefinitionRepositoryTests : IDisposable
{
private readonly string _directory;
private readonly JsonInspectionDefinitionRepository _repository;
public InspectionDefinitionRepositoryTests()
{
_directory = Path.Combine(Path.GetTempPath(), "XplorePlane.Tests", Guid.NewGuid().ToString("N"));
var logger = new Mock<ILoggerService>();
logger.Setup(x => x.ForModule<JsonInspectionDefinitionRepository>()).Returns(logger.Object);
_repository = new JsonInspectionDefinitionRepository(_directory, logger.Object);
}
[Fact]
public async Task SaveAndLoad_PreservesChineseAndDefinitionData()
{
var definition = CreateDefinition("焊球空洞检测");
await _repository.SaveAsync(definition);
var loaded = await _repository.LoadAsync(definition.Id);
Assert.Equal("焊球空洞检测", loaded.Name);
Assert.Equal(InspectionKind.Bga, loaded.Kind);
Assert.Equal(120, loaded.Acquisition.VoltageKv);
Assert.Equal(4, loaded.Geometry.BgaGrid.Rows);
Assert.Equal("BgaVoidRate", loaded.Analysis.AdvancedDetectorType);
var json = await File.ReadAllTextAsync(Path.Combine(_directory, $"{definition.Id:D}.xppool"));
Assert.Contains("焊球空洞检测", json);
Assert.DoesNotContain("\\u710a", json, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task ListCloneDelete_ImplementsSimpleCrud()
{
var source = CreateDefinition("原始项目");
await _repository.SaveAsync(source);
var clone = await _repository.CloneAsync(source.Id, "复制项目");
var listed = await _repository.ListAsync();
Assert.Equal(2, listed.Count);
Assert.NotEqual(source.Id, clone.Id);
Assert.Equal("复制项目", clone.Name);
Assert.Equal(source.Geometry.BgaGrid.Rows, clone.Geometry.BgaGrid.Rows);
Assert.Equal(source.Geometry.BgaGrid.Columns, clone.Geometry.BgaGrid.Columns);
Assert.Equal(source.Geometry.Regions.Select(r => r.Id), clone.Geometry.Regions.Select(r => r.Id));
await _repository.DeleteAsync(source.Id);
listed = await _repository.ListAsync();
Assert.Single(listed);
Assert.Equal(clone.Id, listed[0].Id);
}
[Fact]
public async Task ReferenceAsset_PersistsClonesAndDeletesWithDefinition()
{
var assets = new InspectionReferenceAssetService(_directory);
var source = CreateDefinition("参考图项目");
var image = System.Windows.Media.Imaging.BitmapSource.Create(
2, 2, 96, 96, System.Windows.Media.PixelFormats.Gray8, null,
new byte[] { 1, 2, 3, 4 }, 2);
image.Freeze();
var reference = await assets.SaveAsync(source.Id, image, DateTime.UtcNow);
source = source with { ReferenceImage = reference };
await _repository.SaveAsync(source);
var loadedImage = await assets.LoadAsync((await _repository.LoadAsync(source.Id)).ReferenceImage);
var clone = await _repository.CloneAsync(source.Id, "参考图副本");
Assert.Equal(2, loadedImage.PixelWidth);
Assert.NotEqual(source.ReferenceImage.RelativePath, clone.ReferenceImage.RelativePath);
Assert.True(File.Exists(Path.Combine(_directory, clone.ReferenceImage.RelativePath)));
await _repository.DeleteAsync(clone.Id);
Assert.False(Directory.Exists(Path.Combine(_directory, "References", clone.Id.ToString("D"))));
}
[Fact]
public async Task List_RemovesOrphanReferenceAssetDirectories()
{
var orphanId = Guid.NewGuid();
var orphanDirectory = Path.Combine(_directory, "References", orphanId.ToString("D"));
Directory.CreateDirectory(orphanDirectory);
await File.WriteAllTextAsync(Path.Combine(orphanDirectory, "reference.png"), "orphan");
await _repository.ListAsync();
Assert.False(Directory.Exists(orphanDirectory));
}
[Fact]
public async Task Save_InvalidDefinition_IsRejectedBeforeWritingFile()
{
var invalid = CreateDefinition(" ") with { Id = Guid.Empty };
await Assert.ThrowsAsync<InvalidDataException>(() => _repository.SaveAsync(invalid));
Assert.Empty(Directory.EnumerateFiles(_directory));
}
[Fact]
public void Validator_DoesNotRequireRegionCountToEqualBgaRowsTimesColumns()
{
var definition = CreateDefinition("BGA") with
{
Geometry = new InspectionGeometry(
new[] { CreateRectangle("BGA-ROI") },
new BgaGridGeometry(20, 20, 30, 30, Array.Empty<string>()))
};
var result = InspectionDefinitionValidator.Validate(definition);
Assert.True(result.IsValid, string.Join("; ", result.Errors));
}
[Fact]
public void InspectionPoolTaskNode_SerializesCompleteSnapshot()
{
var definition = CreateDefinition("CNC 检测项目");
CncNode node = new InspectionPoolTaskNode(
Guid.NewGuid(), 0, "检测_CNC 检测项目", definition.Id, definition);
var options = new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
};
var json = JsonSerializer.Serialize(node, options);
var restored = Assert.IsType<InspectionPoolTaskNode>(JsonSerializer.Deserialize<CncNode>(json, options));
Assert.Equal(definition.Id, restored.DefinitionId);
Assert.Equal("CNC 检测项目", restored.DefinitionSnapshot.Name);
Assert.Equal(120, restored.DefinitionSnapshot.Acquisition.VoltageKv);
Assert.Equal("BgaVoidRate", restored.DefinitionSnapshot.Analysis.AdvancedDetectorType);
}
[Fact]
public void SnapshotClone_IsIndependentFromMutablePipelineSource()
{
var pipeline = new PipelineModel { Name = "原流水线" };
pipeline.Nodes.Add(new PipelineNodeModel { OperatorKey = "Threshold", Order = 0 });
var source = CreateDefinition("Pipeline") with
{
Kind = InspectionKind.Pipeline,
Geometry = new InspectionGeometry(new[] { CreateRectangle("ROI") }),
Analysis = new AnalysisParameters(pipeline, string.Empty, new Dictionary<string, double>())
};
var snapshot = InspectionDefinitionSnapshot.Clone(source);
source.Analysis.PipelineSnapshot.Name = "已修改";
source.Analysis.PipelineSnapshot.Nodes.Clear();
Assert.Equal("原流水线", snapshot.Analysis.PipelineSnapshot.Name);
Assert.Single(snapshot.Analysis.PipelineSnapshot.Nodes);
}
public void Dispose()
{
if (Directory.Exists(_directory))
Directory.Delete(_directory, recursive: true);
}
private static InspectionDefinition CreateDefinition(string name)
{
return new InspectionDefinition(
Guid.NewGuid(),
name,
InspectionKind.Bga,
new MachineParameters(1, 2, 3, 4, 5, 6, 7, 100, 200, 2),
new AcquisitionParameters(120, 80, 100, 4, "HighResolution", false, false),
new InspectionGeometry(
new[] { CreateRectangle("ROI-1") },
new BgaGridGeometry(4, 4, 20, 20, Array.Empty<string>())),
new AnalysisParameters(
null,
"BgaVoidRate",
new Dictionary<string, double> { ["Threshold"] = 0.5 }),
new DecisionRules(
new[] { new MetricDecisionRule("VoidRatio", 0, 25) },
0,
0),
DateTime.UtcNow);
}
private static InspectionRegion CreateRectangle(string id) =>
new(id, RoiShapeKind.Rectangle, new[]
{
new RoiPoint(10, 20),
new RoiPoint(110, 120)
});
}
}