339 lines
12 KiB
C#
339 lines
12 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 测试 DebugPanelConfigService(任务 1.2)
|
||
/// Tests DebugPanelConfigService (Task 1.2)
|
||
///
|
||
/// **Validates: Requirements 12.1-12.7**
|
||
/// </summary>
|
||
public class DebugPanelConfigServiceTests : IDisposable
|
||
{
|
||
private readonly string _tempDir;
|
||
private readonly string _configPath;
|
||
private readonly Mock<ILoggerService> _mockLogger;
|
||
private readonly Mock<ILoggerService> _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<ILoggerService>();
|
||
_mockLogger = new Mock<ILoggerService>();
|
||
_mockLogger.Setup(l => l.ForModule<DebugPanelConfigService>())
|
||
.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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:当配置文件不存在时,LoadConfig 应返回默认配置
|
||
/// **Validates: Requirement 12.7**
|
||
/// </summary>
|
||
[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"]);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:SaveConfig 应成功保存配置到 JSON 文件
|
||
/// **Validates: Requirements 12.1-12.5**
|
||
/// </summary>
|
||
[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<string, bool>
|
||
{
|
||
["MotionState"] = false,
|
||
["RaySourceState"] = true
|
||
},
|
||
DockingLayout = "<Layout>Test</Layout>"
|
||
};
|
||
|
||
// 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\": \"<Layout>Test</Layout>\"", json);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:LoadConfig 应成功从 JSON 文件加载配置
|
||
/// **Validates: Requirement 12.6**
|
||
/// </summary>
|
||
[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<string, bool>
|
||
{
|
||
["MotionState"] = true,
|
||
["RaySourceState"] = false,
|
||
["DetectorState"] = true
|
||
},
|
||
DockingLayout = "<Layout>Custom</Layout>"
|
||
};
|
||
_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("<Layout>Custom</Layout>", loadedConfig.DockingLayout);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:SaveConfig 应创建不存在的目录
|
||
/// **Validates: Requirement 12.4**
|
||
/// </summary>
|
||
[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<string, bool>()
|
||
};
|
||
|
||
// Act
|
||
_service.SaveConfig(config);
|
||
|
||
// Assert
|
||
Assert.True(File.Exists(nestedPath));
|
||
Assert.True(Directory.Exists(Path.GetDirectoryName(nestedPath)));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:SaveConfig 使用 null 配置应记录警告并不抛出异常
|
||
/// **Validates: Requirement 12.7**
|
||
/// </summary>
|
||
[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<string>(), It.IsAny<object[]>()),
|
||
Times.Once);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:LoadConfig 遇到损坏的 JSON 文件应返回默认配置并记录警告
|
||
/// **Validates: Requirement 12.7**
|
||
/// </summary>
|
||
[Fact]
|
||
public void LoadConfig_WithCorruptedFile_ReturnsDefaultConfigAndLogsWarning()
|
||
{
|
||
// Arrange
|
||
File.WriteAllText(_configPath, "{ invalid json content }");
|
||
|
||
// Act
|
||
var config = _service.LoadConfig();
|
||
|
||
// Assert
|
||
Assert.NotNull(config);
|
||
Assert.Equal(1200, config.Window.Width);
|
||
Assert.Equal(800, config.Window.Height);
|
||
|
||
// 验证记录了错误日志
|
||
_mockModuleLogger.Verify(
|
||
l => l.Error(It.IsAny<Exception>(), It.IsAny<string>(), It.IsAny<object[]>()),
|
||
Times.Once);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:配置文件路径应使用 %AppData%\XplorePlane\DebugPanel.config
|
||
/// **Validates: Requirement 12.4**
|
||
/// </summary>
|
||
[Fact]
|
||
public void Constructor_SetsCorrectConfigPath()
|
||
{
|
||
// Arrange
|
||
var freshService = new DebugPanelConfigService(_mockLogger.Object);
|
||
var field = typeof(DebugPanelConfigService).GetField("_configPath",
|
||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
// Act
|
||
var actualPath = field?.GetValue(freshService) as string;
|
||
|
||
// Assert
|
||
Assert.NotNull(actualPath);
|
||
var expectedPath = Path.Combine(
|
||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||
"XplorePlane",
|
||
"DebugPanel.config");
|
||
Assert.Equal(expectedPath, actualPath);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:配置文件应使用 JSON 格式
|
||
/// **Validates: Requirement 12.5**
|
||
/// </summary>
|
||
[Fact]
|
||
public void SaveConfig_UsesJsonFormat()
|
||
{
|
||
// Arrange
|
||
var config = new DebugPanelConfig
|
||
{
|
||
Window = new WindowConfig { Width = 1200, Height = 800, State = WindowState.Normal },
|
||
EventFilters = new Dictionary<string, bool> { ["Test"] = true }
|
||
};
|
||
|
||
// Act
|
||
_service.SaveConfig(config);
|
||
|
||
// Assert
|
||
var json = File.ReadAllText(_configPath);
|
||
Assert.StartsWith("{", json.Trim());
|
||
Assert.EndsWith("}", json.Trim());
|
||
Assert.Contains("\"Window\":", json);
|
||
Assert.Contains("\"EventFilters\":", json);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:默认配置应包含所有 8 种状态类型的过滤器
|
||
/// **Validates: Requirement 12.3**
|
||
/// </summary>
|
||
[Fact]
|
||
public void GetDefaultConfig_ContainsAllStateTypeFilters()
|
||
{
|
||
// Act
|
||
var config = _service.LoadConfig();
|
||
|
||
// Assert
|
||
Assert.NotNull(config.EventFilters);
|
||
Assert.Equal(8, config.EventFilters.Count);
|
||
Assert.Contains("MotionState", config.EventFilters.Keys);
|
||
Assert.Contains("RaySourceState", config.EventFilters.Keys);
|
||
Assert.Contains("DetectorState", config.EventFilters.Keys);
|
||
Assert.Contains("SystemState", config.EventFilters.Keys);
|
||
Assert.Contains("CameraState", config.EventFilters.Keys);
|
||
Assert.Contains("LinkedViewState", config.EventFilters.Keys);
|
||
Assert.Contains("RecipeExecutionState", config.EventFilters.Keys);
|
||
Assert.Contains("CalibrationMatrix", config.EventFilters.Keys);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:默认窗口配置应为 1200×800 像素
|
||
/// **Validates: Requirements 12.1**
|
||
/// </summary>
|
||
[Fact]
|
||
public void GetDefaultConfig_HasCorrectWindowSize()
|
||
{
|
||
// Act
|
||
var config = _service.LoadConfig();
|
||
|
||
// Assert
|
||
Assert.Equal(1200, config.Window.Width);
|
||
Assert.Equal(800, config.Window.Height);
|
||
Assert.Equal(WindowState.Normal, config.Window.State);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试:构造函数应拒绝 null logger
|
||
/// </summary>
|
||
[Fact]
|
||
public void Constructor_WithNullLogger_ThrowsArgumentNullException()
|
||
{
|
||
// Act & Assert
|
||
Assert.Throws<ArgumentNullException>(() => new DebugPanelConfigService(null));
|
||
}
|
||
}
|
||
}
|