using Moq; using System; using System.IO; using System.Threading.Tasks; using XP.Common.Logging.Interfaces; using XplorePlane.Models; using XplorePlane.Services; using XplorePlane.Services.AppState; using XplorePlane.Services.Recipe; using Xunit; using Xunit.Abstractions; namespace XplorePlane.Tests.Services { /// /// RecipeService 单元测试。 /// 验证配方创建、步骤记录、文件加载错误处理和名称校验。 /// public class RecipeServiceTests { private readonly Mock _mockAppState; private readonly Mock _mockPipeline; private readonly Mock _mockLogger; private readonly RecipeService _service; private readonly ITestOutputHelper _output; public RecipeServiceTests(ITestOutputHelper output) { _output = output; _mockAppState = new Mock(); _mockPipeline = new Mock(); _mockLogger = new Mock(); _mockLogger.Setup(l => l.ForModule()).Returns(_mockLogger.Object); // Setup default state returns _mockAppState.Setup(s => s.MotionState).Returns(MotionState.Default); _mockAppState.Setup(s => s.RaySourceState).Returns(RaySourceState.Default); _mockAppState.Setup(s => s.DetectorState).Returns(DetectorState.Default); _service = new RecipeService( _mockAppState.Object, _mockPipeline.Object, _mockLogger.Object); } // ── CreateRecipe 验证 ── [Fact] public void CreateRecipe_ValidName_ReturnsEmptyRecipe() { var recipe = _service.CreateRecipe("TestRecipe"); _output.WriteLine($"CreateRecipe('TestRecipe'): Name={recipe.Name}, Id={recipe.Id}, Steps.Count={recipe.Steps.Count}, CreatedAt={recipe.CreatedAt}"); Assert.Equal("TestRecipe", recipe.Name); Assert.Empty(recipe.Steps); Assert.NotEqual(Guid.Empty, recipe.Id); Assert.True(recipe.CreatedAt <= DateTime.UtcNow); Assert.True(recipe.UpdatedAt <= DateTime.UtcNow); } [Fact] public void CreateRecipe_EmptyName_ThrowsArgumentException() { var ex = Assert.Throws(() => _service.CreateRecipe(string.Empty)); _output.WriteLine($"CreateRecipe('') threw: {ex.GetType().Name}, Message={ex.Message}"); } [Fact] public void CreateRecipe_NullName_ThrowsArgumentException() { var ex = Assert.Throws(() => _service.CreateRecipe(null!)); _output.WriteLine($"CreateRecipe(null) threw: {ex.GetType().Name}, Message={ex.Message}"); } [Fact] public void CreateRecipe_WhitespaceName_ThrowsArgumentException() { var ex = Assert.Throws(() => _service.CreateRecipe(" ")); _output.WriteLine($"CreateRecipe(' ') threw: {ex.GetType().Name}, Message={ex.Message}"); } // ── RecordCurrentStep 验证 ── [Fact] public void RecordCurrentStep_CapturesCurrentState() { var motionState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); var raySourceState = new RaySourceState(true, 120.0, 80.0); var detectorState = new DetectorState(true, true, 30.0, "2048x2048"); _mockAppState.Setup(s => s.MotionState).Returns(motionState); _mockAppState.Setup(s => s.RaySourceState).Returns(raySourceState); _mockAppState.Setup(s => s.DetectorState).Returns(detectorState); var recipe = _service.CreateRecipe("TestRecipe"); var pipeline = new PipelineModel { Name = "TestPipeline" }; var step = _service.RecordCurrentStep(recipe, pipeline); _output.WriteLine($"RecordCurrentStep: StepIndex={step.StepIndex}, MotionState.XM={step.MotionState.XM}, RaySource.Voltage={step.RaySourceState.Voltage}, Detector.Resolution={step.DetectorState.Resolution}, Pipeline={step.Pipeline.Name}"); Assert.Equal(0, step.StepIndex); Assert.Same(motionState, step.MotionState); Assert.Same(raySourceState, step.RaySourceState); Assert.Same(detectorState, step.DetectorState); Assert.Same(pipeline, step.Pipeline); } // ── LoadAsync 错误处理 ── [Fact] public async Task LoadAsync_FileNotExists_ThrowsFileNotFoundException() { var nonExistentPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".json"); _output.WriteLine($"LoadAsync 不存在的文件: {nonExistentPath}"); var ex = await Assert.ThrowsAsync( () => _service.LoadAsync(nonExistentPath)); _output.WriteLine($"抛出异常: {ex.GetType().Name}, Message={ex.Message}"); } [Fact] public async Task LoadAsync_InvalidJson_ThrowsInvalidDataException() { var tempFile = Path.GetTempFileName(); try { await File.WriteAllTextAsync(tempFile, "{ this is not valid json !!! }"); _output.WriteLine($"LoadAsync 无效JSON文件: {tempFile}"); var ex = await Assert.ThrowsAsync( () => _service.LoadAsync(tempFile)); _output.WriteLine($"抛出异常: {ex.GetType().Name}, Message={ex.Message}"); } finally { if (File.Exists(tempFile)) File.Delete(tempFile); } } } }