Files
XplorePlane/XplorePlane.Tests/Inspection/RealInspectionAcquisitionServiceTests.cs
T
2026-08-10 14:13:32 +08:00

217 lines
10 KiB
C#

using Moq;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using XP.Common.Logging.Interfaces;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Services;
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Services;
using XP.Hardware.RaySource.Abstractions;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models.Inspection;
using XplorePlane.Services.AppState;
using XplorePlane.Services.Inspection;
using Xunit;
namespace XplorePlane.Tests.Inspection
{
public sealed class RealInspectionAcquisitionServiceTests
{
[Fact]
public async Task AcquireAsync_AppliesHardwareAndReturnsNewDetectorFrame()
{
var harness = CreateHarness();
harness.PublishFrameOnAcquire();
var result = await harness.Service.AcquireAsync(
Machine(), Acquisition(), CancellationToken.None);
Assert.Equal("Detector", result.Source);
Assert.Equal(2, result.Image.PixelWidth);
harness.Motion.Verify(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false), Times.Once);
harness.Detector.Verify(x => x.ApplyParametersAsync(0, 1, 10m, 2, It.IsAny<CancellationToken>()), Times.Once);
harness.Detector.Verify(x => x.AcquireSingleFrameAsync(It.IsAny<CancellationToken>()), Times.Once);
harness.Ray.Verify(x => x.SetVoltage(120), Times.Once);
harness.Ray.Verify(x => x.SetCurrent(80), Times.Once);
harness.Ray.Verify(x => x.TurnOn(), Times.Once);
harness.Ray.Verify(x => x.TurnOff(), Times.Once);
harness.Motion.Verify(x => x.StopAll(), Times.Never);
}
[Fact]
public async Task AcquireAsync_CancellationStopsMotionAndTurnsOffRay()
{
var harness = CreateHarness(new InspectionAcquisitionOptions
{
RayStabilizationDelay = TimeSpan.FromSeconds(5),
MotionTimeout = TimeSpan.FromSeconds(1),
AcquisitionTimeout = TimeSpan.FromSeconds(1),
CleanupTimeout = TimeSpan.FromMilliseconds(100)
});
using var cancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(30));
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
harness.Service.AcquireAsync(Machine(), Acquisition(), cancellation.Token));
harness.Motion.Verify(x => x.StopAll(), Times.Once);
harness.Ray.Verify(x => x.TurnOff(), Times.Once);
}
[Fact]
public async Task AcquireAsync_OpenSafetyDoorRejectsBeforeMotionAndRay()
{
var harness = CreateHarness(doorStatus: DoorStatus.Open);
var error = await Assert.ThrowsAsync<InspectionAcquisitionException>(() =>
harness.Service.AcquireAsync(Machine(), Acquisition(), CancellationToken.None));
Assert.Contains("安全门", error.Message);
harness.Motion.Verify(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false), Times.Never);
harness.Ray.Verify(x => x.TurnOn(), Times.Never);
harness.Motion.Verify(x => x.StopAll(), Times.Once);
}
[Fact]
public async Task AcquireAsync_DisconnectedDetectorFailsBeforeHardwareCommands()
{
var harness = CreateHarness(detectorConnected: false);
var error = await Assert.ThrowsAsync<InspectionAcquisitionException>(() =>
harness.Service.AcquireAsync(Machine(), Acquisition(), CancellationToken.None));
Assert.Contains("探测器未连接", error.Message);
harness.Motion.Verify(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false), Times.Never);
harness.Ray.Verify(x => x.TurnOn(), Times.Never);
}
[Fact]
public async Task AcquireAsync_MissingNewFrameTimesOutAndCleansUp()
{
var harness = CreateHarness(new InspectionAcquisitionOptions
{
RayStabilizationDelay = TimeSpan.Zero,
MotionTimeout = TimeSpan.FromSeconds(1),
AcquisitionTimeout = TimeSpan.FromMilliseconds(40),
CleanupTimeout = TimeSpan.FromMilliseconds(100)
});
var error = await Assert.ThrowsAsync<InspectionAcquisitionException>(() =>
harness.Service.AcquireAsync(Machine(), Acquisition(), CancellationToken.None));
Assert.Contains("超时", error.Message);
harness.Motion.Verify(x => x.StopAll(), Times.Once);
harness.Ray.Verify(x => x.TurnOff(), Times.Once);
}
private static Harness CreateHarness(
InspectionAcquisitionOptions options = null,
DoorStatus doorStatus = DoorStatus.Closed,
bool detectorConnected = true)
{
var motion = new Mock<IMotionControlService>();
motion.Setup(x => x.MoveAllToTarget(It.IsAny<Dictionary<AxisId, double>>(), false))
.Returns(MotionResult.Ok());
motion.Setup(x => x.MoveRotaryToTarget(It.IsAny<RotaryAxisId>(), It.IsAny<double>(), It.IsAny<double?>()))
.Returns(MotionResult.Ok());
motion.Setup(x => x.StopAll()).Returns(MotionResult.Ok());
var safetyDoor = new Mock<ISafetyDoor>();
safetyDoor.SetupGet(x => x.Status).Returns(doorStatus);
var linearAxes = new Dictionary<AxisId, ILinearAxis>();
foreach (var id in Enum.GetValues<AxisId>())
{
var axis = new Mock<ILinearAxis>();
axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
linearAxes[id] = axis.Object;
}
var rotaryAxes = new Dictionary<RotaryAxisId, IRotaryAxis>();
foreach (var id in Enum.GetValues<RotaryAxisId>())
{
var axis = new Mock<IRotaryAxis>();
axis.SetupGet(x => x.Enabled).Returns(true);
axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
rotaryAxes[id] = axis.Object;
}
var motionSystem = new Mock<IMotionSystem>();
motionSystem.SetupGet(x => x.SafetyDoor).Returns(safetyDoor.Object);
motionSystem.SetupGet(x => x.LinearAxes).Returns(linearAxes);
motionSystem.SetupGet(x => x.RotaryAxes).Returns(rotaryAxes);
var ray = new Mock<IRaySourceService>();
ray.SetupGet(x => x.IsInitialized).Returns(true);
ray.SetupGet(x => x.IsConnected).Returns(true);
ray.SetupGet(x => x.IsXRayOn).Returns(false);
ray.Setup(x => x.SetVoltage(It.IsAny<float>())).Returns(XRayResult.Ok());
ray.Setup(x => x.SetCurrent(It.IsAny<float>())).Returns(XRayResult.Ok());
ray.Setup(x => x.TurnOn()).Returns(XRayResult.Ok());
ray.Setup(x => x.TurnOff()).Returns(XRayResult.Ok());
ray.Setup(x => x.EmergencyShutdown()).Returns(XRayResult.Ok());
var detector = new Mock<IDetectorService>();
detector.SetupGet(x => x.IsConnected).Returns(detectorConnected);
detector.SetupGet(x => x.Status).Returns(DetectorStatus.Ready);
detector.Setup(x => x.ApplyParametersAsync(
It.IsAny<int>(), It.IsAny<int>(), It.IsAny<decimal>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(DetectorResult.Success());
detector.Setup(x => x.AcquireSingleFrameAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(DetectorResult.Success());
ImageCapturedEventArgs latestFrame = null;
var appState = new Mock<IAppStateService>();
appState.SetupGet(x => x.LatestDetectorFrame).Returns(() => latestFrame);
var logger = new Mock<ILoggerService>();
logger.Setup(x => x.ForModule<RealInspectionAcquisitionService>()).Returns(logger.Object);
options ??= new InspectionAcquisitionOptions
{
RayStabilizationDelay = TimeSpan.Zero,
MotionTimeout = TimeSpan.FromSeconds(1),
AcquisitionTimeout = TimeSpan.FromSeconds(1),
CleanupTimeout = TimeSpan.FromMilliseconds(100)
};
var service = new RealInspectionAcquisitionService(
motion.Object,
motionSystem.Object,
ray.Object,
detector.Object,
appState.Object,
options,
logger.Object);
return new Harness(service, motion, ray, detector, frame => latestFrame = frame);
}
private static MachineParameters Machine() =>
new(1, 2, 3, 4, 5, 6, 7, 100, 200, 2);
private static AcquisitionParameters Acquisition() =>
new(120, 80, 100, 2, "Default", false, false, 0, 1, 10);
private sealed record Harness(
RealInspectionAcquisitionService Service,
Mock<IMotionControlService> Motion,
Mock<IRaySourceService> Ray,
Mock<IDetectorService> Detector,
Action<ImageCapturedEventArgs> SetFrame)
{
public void PublishFrameOnAcquire()
{
Detector.Setup(x => x.AcquireSingleFrameAsync(It.IsAny<CancellationToken>()))
.Callback(() => SetFrame(new ImageCapturedEventArgs
{
ImageData = new ushort[] { 0, 1000, 2000, ushort.MaxValue },
Width = 2,
Height = 2,
FrameNumber = 1,
CaptureTime = DateTime.UtcNow
}))
.ReturnsAsync(DetectorResult.Success());
}
}
}
}