Files
XplorePlane/XplorePlane.Tests/Services/AppStateServiceTests.cs
T
2026-05-06 18:20:52 +08:00

279 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Moq;
using Prism.Events;
using System;
using System.Windows;
using XP.Common.Logging.Interfaces;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Abstractions.Events;
using XP.Hardware.Detector.Services;
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Abstractions.Events;
using XP.Hardware.MotionControl.Services;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
using Xunit;
using Xunit.Abstractions;
namespace XplorePlane.Tests.Services
{
/// <summary>
/// AppStateService unit tests.
/// Verifies default values, null guards, dispose behavior, and hardware-driven motion-state sync.
/// </summary>
public class AppStateServiceTests : IDisposable
{
private readonly AppStateService _service;
private readonly Mock<IRaySourceService> _mockRaySource;
private readonly Mock<IMotionSystem> _mockMotionSystem;
private readonly Mock<IMotionControlService> _mockMotionControlService;
private readonly Mock<IDetectorService> _mockDetectorService;
private readonly Mock<ILinearAxis> _mockStageX;
private readonly Mock<ILinearAxis> _mockStageY;
private readonly Mock<ILinearAxis> _mockSourceZ;
private readonly Mock<ILinearAxis> _mockDetectorZ;
private readonly Mock<IRotaryAxis> _mockDetectorSwing;
private readonly Mock<IRotaryAxis> _mockStageRotation;
private readonly Mock<IRotaryAxis> _mockFixtureRotation;
private readonly Mock<ILoggerService> _mockLogger;
private readonly EventAggregator _eventAggregator;
private readonly ITestOutputHelper _output;
public AppStateServiceTests(ITestOutputHelper output)
{
_output = output;
if (Application.Current == null)
{
new Application();
}
_mockRaySource = new Mock<IRaySourceService>();
_mockMotionSystem = new Mock<IMotionSystem>();
_mockMotionControlService = new Mock<IMotionControlService>();
_mockDetectorService = new Mock<IDetectorService>();
_mockStageX = CreateLinearAxis(AxisId.StageX, 0);
_mockStageY = CreateLinearAxis(AxisId.StageY, 0);
_mockSourceZ = CreateLinearAxis(AxisId.SourceZ, 0);
_mockDetectorZ = CreateLinearAxis(AxisId.DetectorZ, 0);
_mockDetectorSwing = CreateRotaryAxis(RotaryAxisId.DetectorSwing, 0);
_mockStageRotation = CreateRotaryAxis(RotaryAxisId.StageRotation, 0);
_mockFixtureRotation = CreateRotaryAxis(RotaryAxisId.FixtureRotation, 0);
_mockLogger = new Mock<ILoggerService>();
_eventAggregator = new EventAggregator();
_mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.StageX)).Returns(_mockStageX.Object);
_mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.StageY)).Returns(_mockStageY.Object);
_mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.SourceZ)).Returns(_mockSourceZ.Object);
_mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.DetectorZ)).Returns(_mockDetectorZ.Object);
_mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.DetectorSwing)).Returns(_mockDetectorSwing.Object);
_mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.StageRotation)).Returns(_mockStageRotation.Object);
_mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.FixtureRotation)).Returns(_mockFixtureRotation.Object);
_mockMotionControlService
.Setup(x => x.GetCurrentGeometry())
.Returns((0d, 0d, 1d));
// DetectorServiceGetInfo 在未初始化时抛出,模拟此行为
_mockDetectorService
.Setup(x => x.GetInfo())
.Throws(new InvalidOperationException("探测器未初始化"));
_mockDetectorService
.SetupGet(x => x.Status)
.Returns(DetectorStatus.Uninitialized);
_mockDetectorService
.SetupGet(x => x.IsConnected)
.Returns(false);
_mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object);
_service = new AppStateService(
_mockRaySource.Object,
_mockMotionSystem.Object,
_mockMotionControlService.Object,
_mockDetectorService.Object,
_eventAggregator,
_mockLogger.Object);
}
public void Dispose()
{
_service.Dispose();
}
[Fact]
public void DefaultState_MotionState_IsHardwareSnapshot()
{
Assert.Equal(0, _service.MotionState.StageX);
Assert.Equal(0, _service.MotionState.StageY);
Assert.Equal(0, _service.MotionState.SourceZ);
Assert.Equal(0, _service.MotionState.DetectorZ);
Assert.Equal(0, _service.MotionState.DetectorSwing);
Assert.Equal(0, _service.MotionState.FDD);
}
[Fact]
public void DefaultState_RaySourceState_IsDefault()
{
_output.WriteLine($"RaySourceState == RaySourceState.Default: {ReferenceEquals(RaySourceState.Default, _service.RaySourceState)}");
Assert.Same(RaySourceState.Default, _service.RaySourceState);
}
[Fact]
public void DefaultState_SystemState_IsDefault()
{
_output.WriteLine($"SystemState == SystemState.Default: {ReferenceEquals(SystemState.Default, _service.SystemState)}");
Assert.Same(SystemState.Default, _service.SystemState);
}
[Fact]
public void DefaultState_CalibrationMatrix_IsNull()
{
_output.WriteLine($"CalibrationMatrix: {_service.CalibrationMatrix?.ToString() ?? "null"}");
Assert.Null(_service.CalibrationMatrix);
}
[Fact]
public void UpdateMotionState_NullArgument_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateMotionState(null!));
_output.WriteLine($"UpdateMotionState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void UpdateRaySourceState_NullArgument_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateRaySourceState(null!));
_output.WriteLine($"UpdateRaySourceState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void UpdateDetectorState_NullArgument_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateDetectorState(null!));
_output.WriteLine($"UpdateDetectorState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void UpdateSystemState_NullArgument_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() => _service.UpdateSystemState(null!));
_output.WriteLine($"UpdateSystemState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
[Fact]
public void Dispose_ThenUpdate_IsIgnored()
{
var originalState = _service.MotionState;
_service.Dispose();
var newState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
_service.UpdateMotionState(newState);
Assert.Same(originalState, _service.MotionState);
}
[Fact]
public void RequestLinkedView_NoCalibrationMatrix_SetsErrorState()
{
Assert.Null(_service.CalibrationMatrix);
_service.RequestLinkedView(100.0, 200.0);
Assert.True(_service.SystemState.HasError);
Assert.NotEmpty(_service.SystemState.ErrorMessage);
}
[Fact]
public void GeometryUpdatedEvent_RefreshesMotionStateFromHardware()
{
_mockStageX.SetupGet(x => x.ActualPosition).Returns(12.5);
_mockStageY.SetupGet(x => x.ActualPosition).Returns(34.5);
_mockSourceZ.SetupGet(x => x.ActualPosition).Returns(56.5);
_mockDetectorZ.SetupGet(x => x.ActualPosition).Returns(78.5);
_mockDetectorSwing.SetupGet(x => x.ActualAngle).Returns(9.5);
_eventAggregator.GetEvent<GeometryUpdatedEvent>()
.Publish(new GeometryData(100, 222.2, 2.22));
Assert.Equal(12.5, _service.MotionState.StageX);
Assert.Equal(34.5, _service.MotionState.StageY);
Assert.Equal(56.5, _service.MotionState.SourceZ);
Assert.Equal(78.5, _service.MotionState.DetectorZ);
Assert.Equal(9.5, _service.MotionState.DetectorSwing);
Assert.Equal(222.2, _service.MotionState.FDD);
}
[Fact]
public void StatusChangedEvent_Acquiring_SyncsDetectorState()
{
// 模拟探测器进入采集状态
_mockDetectorService.SetupGet(x => x.Status).Returns(DetectorStatus.Acquiring);
_mockDetectorService.Setup(x => x.GetInfo()).Throws(new InvalidOperationException());
_eventAggregator.GetEvent<StatusChangedEvent>()
.Publish(DetectorStatus.Acquiring);
// 等待后台线程处理(BackgroundThread 订阅)
System.Threading.Thread.Sleep(100);
Assert.True(_service.DetectorState.IsConnected);
Assert.True(_service.DetectorState.IsAcquiring);
}
[Fact]
public void StatusChangedEvent_Uninitialized_SyncsDetectorStateDisconnected()
{
_eventAggregator.GetEvent<StatusChangedEvent>()
.Publish(DetectorStatus.Uninitialized);
System.Threading.Thread.Sleep(100);
Assert.False(_service.DetectorState.IsConnected);
Assert.False(_service.DetectorState.IsAcquiring);
}
[Fact]
public void ImageCapturedEvent_UpdatesLatestDetectorFrame()
{
Assert.Null(_service.LatestDetectorFrame);
var args = new XP.Hardware.Detector.Abstractions.ImageCapturedEventArgs
{
ImageData = new ushort[4],
Width = 2,
Height = 2,
FrameNumber = 1,
CaptureTime = DateTime.UtcNow
};
_eventAggregator.GetEvent<ImageCapturedEvent>().Publish(args);
// 等待后台线程处理
System.Threading.Thread.Sleep(100);
Assert.Same(args, _service.LatestDetectorFrame);
}
private static Mock<ILinearAxis> CreateLinearAxis(AxisId axisId, double position)
{
var axis = new Mock<ILinearAxis>();
axis.SetupGet(x => x.Id).Returns(axisId);
axis.SetupGet(x => x.ActualPosition).Returns(position);
axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
return axis;
}
private static Mock<IRotaryAxis> CreateRotaryAxis(RotaryAxisId axisId, double angle)
{
var axis = new Mock<IRotaryAxis>();
axis.SetupGet(x => x.Id).Returns(axisId);
axis.SetupGet(x => x.ActualAngle).Returns(angle);
axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
axis.SetupGet(x => x.Enabled).Returns(true);
return axis;
}
}
}