using Moq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using Xunit;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services.Debug;
namespace XplorePlane.Tests.Services
{
///
/// 测试 DebugPanelConfigService(任务 1.2)
/// Tests DebugPanelConfigService (Task 1.2)
///
/// **Validates: Requirements 12.1-12.7**
///
public class DebugPanelConfigServiceTests : IDisposable
{
private readonly string _tempDir;
private readonly string _configPath;
private readonly Mock _mockLogger;
private readonly Mock _mockModuleLogger;
private readonly DebugPanelConfigService _service;
public DebugPanelConfigServiceTests()
{
// 创建临时测试目录
_tempDir = Path.Combine(Path.GetTempPath(), "XplorePlaneTests", Guid.NewGuid().ToString());
Directory.CreateDirectory(_tempDir);
_configPath = Path.Combine(_tempDir, "DebugPanel.config");
// 设置 mock logger
_mockModuleLogger = new Mock();
_mockLogger = new Mock();
_mockLogger.Setup(l => l.ForModule())
.Returns(_mockModuleLogger.Object);
// 使用反射创建服务实例并设置配置路径
_service = new DebugPanelConfigService(_mockLogger.Object);
// 使用反射修改私有字段 _configPath
var field = typeof(DebugPanelConfigService).GetField("_configPath",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
field?.SetValue(_service, _configPath);
}
public void Dispose()
{
// 清理临时目录
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, true);
}
}
///
/// 测试:当配置文件不存在时,LoadConfig 应返回默认配置
/// **Validates: Requirement 12.7**
///
[Fact]
public void LoadConfig_WhenFileDoesNotExist_ReturnsDefaultConfig()
{
// Act
var config = _service.LoadConfig();
// Assert
Assert.NotNull(config);
Assert.NotNull(config.Window);
Assert.Equal(1200, config.Window.Width);
Assert.Equal(800, config.Window.Height);
Assert.Equal(WindowState.Normal, config.Window.State);
Assert.NotNull(config.EventFilters);
Assert.Equal(8, config.EventFilters.Count);
Assert.True(config.EventFilters["MotionState"]);
Assert.True(config.EventFilters["RaySourceState"]);
Assert.True(config.EventFilters["DetectorState"]);
Assert.True(config.EventFilters["SystemState"]);
Assert.True(config.EventFilters["CameraState"]);
Assert.True(config.EventFilters["LinkedViewState"]);
Assert.True(config.EventFilters["RecipeExecutionState"]);
Assert.True(config.EventFilters["CalibrationMatrix"]);
}
///
/// 测试:SaveConfig 应成功保存配置到 JSON 文件
/// **Validates: Requirements 12.1-12.5**
///
[Fact]
public void SaveConfig_WithValidConfig_SavesSuccessfully()
{
// Arrange
var config = new DebugPanelConfig
{
Window = new WindowConfig
{
Left = 150,
Top = 200,
Width = 1400,
Height = 900,
State = WindowState.Maximized
},
EventFilters = new Dictionary
{
["MotionState"] = false,
["RaySourceState"] = true
},
DockingLayout = "Test"
};
// Act
_service.SaveConfig(config);
// Assert
Assert.True(File.Exists(_configPath));
var json = File.ReadAllText(_configPath);
Assert.Contains("\"Width\": 1400", json);
Assert.Contains("\"Height\": 900", json);
Assert.Contains("\"State\": 2", json); // WindowState.Maximized = 2
Assert.Contains("\"MotionState\": false", json);
Assert.Contains("\"RaySourceState\": true", json);
Assert.Contains("\"DockingLayout\": \"Test\"", json);
}
///
/// 测试:LoadConfig 应成功从 JSON 文件加载配置
/// **Validates: Requirement 12.6**
///
[Fact]
public void LoadConfig_WithExistingFile_LoadsSuccessfully()
{
// Arrange
var originalConfig = new DebugPanelConfig
{
Window = new WindowConfig
{
Left = 250,
Top = 300,
Width = 1600,
Height = 1000,
State = WindowState.Normal
},
EventFilters = new Dictionary
{
["MotionState"] = true,
["RaySourceState"] = false,
["DetectorState"] = true
},
DockingLayout = "Custom"
};
_service.SaveConfig(originalConfig);
// Act
var loadedConfig = _service.LoadConfig();
// Assert
Assert.NotNull(loadedConfig);
Assert.NotNull(loadedConfig.Window);
Assert.Equal(250, loadedConfig.Window.Left);
Assert.Equal(300, loadedConfig.Window.Top);
Assert.Equal(1600, loadedConfig.Window.Width);
Assert.Equal(1000, loadedConfig.Window.Height);
Assert.Equal(WindowState.Normal, loadedConfig.Window.State);
Assert.NotNull(loadedConfig.EventFilters);
Assert.True(loadedConfig.EventFilters["MotionState"]);
Assert.False(loadedConfig.EventFilters["RaySourceState"]);
Assert.True(loadedConfig.EventFilters["DetectorState"]);
Assert.Equal("Custom", loadedConfig.DockingLayout);
}
///
/// 测试:SaveConfig 应创建不存在的目录
/// **Validates: Requirement 12.4**
///
[Fact]
public void SaveConfig_WhenDirectoryDoesNotExist_CreatesDirectory()
{
// Arrange
var nestedPath = Path.Combine(_tempDir, "nested", "path", "DebugPanel.config");
var field = typeof(DebugPanelConfigService).GetField("_configPath",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
field?.SetValue(_service, nestedPath);
var config = new DebugPanelConfig
{
Window = new WindowConfig { Width = 1200, Height = 800, State = WindowState.Normal },
EventFilters = new Dictionary()
};
// Act
_service.SaveConfig(config);
// Assert
Assert.True(File.Exists(nestedPath));
Assert.True(Directory.Exists(Path.GetDirectoryName(nestedPath)));
}
///
/// 测试:SaveConfig 使用 null 配置应记录警告并不抛出异常
/// **Validates: Requirement 12.7**
///
[Fact]
public void SaveConfig_WithNullConfig_LogsWarningAndDoesNotThrow()
{
// Act & Assert
var exception = Record.Exception(() => _service.SaveConfig(null));
Assert.Null(exception);
// 验证记录了警告日志
_mockModuleLogger.Verify(
l => l.Warn(It.IsAny(), It.IsAny