Files
XplorePlane/XplorePlane.Tests/Services/DebugPanelConfigServiceTests.cs
T
zhengxuan.zhang 0ccf9c529e 新增调试页面
2026-05-16 13:44:36 +08:00

339 lines
12 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 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));
}
}
}