using AxiOmron.PcbCheck.Models; using AxiOmron.PcbCheck.Options; using AxiOmron.PcbCheck.Services.Implementations; using AxiOmron.PcbCheck.Services.Interfaces; using Xunit; namespace AxiOmron.PcbCheck.Tests; /// /// 验证本地磁盘文件校验提供程序的目录检查与文件匹配行为。 /// public sealed class LocalFileCheckProviderTests : IDisposable { private readonly string _rootPath; /// /// 初始化临时测试目录。 /// public LocalFileCheckProviderTests() { _rootPath = Path.Combine(Path.GetTempPath(), "AxiOmronLocalFileCheckTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_rootPath); } /// /// 清理临时测试目录。 /// public void Dispose() { if (Directory.Exists(_rootPath)) { Directory.Delete(_rootPath, recursive: true); } } /// /// 本地根目录为空时,应返回配置异常。 /// /// 异步测试任务。 [Fact] public async Task TestConnectionAsync_ShouldReturnConfigurationError_WhenRootPathIsEmpty() { var provider = CreateProvider(); var config = new AppConfig { FileCheck = new FileCheckOptions { Local = new LocalFileCheckOptions { RootPath = string.Empty } } }; FileCheckTestOutcome outcome = await provider.TestConnectionAsync(config, CancellationToken.None); Assert.True(outcome.IsConfigurationError); Assert.Contains("本地文件校验目录未配置", outcome.StatusMessage); } /// /// 本地根目录不存在时,应返回配置异常。 /// /// 异步测试任务。 [Fact] public async Task TestConnectionAsync_ShouldReturnConfigurationError_WhenRootPathDoesNotExist() { var provider = CreateProvider(); var config = CreateConfig(Path.Combine(_rootPath, "missing")); FileCheckTestOutcome outcome = await provider.TestConnectionAsync(config, CancellationToken.None); Assert.True(outcome.IsConfigurationError); Assert.Contains("本地文件校验目录不存在", outcome.StatusMessage); } /// /// 文件名精确匹配模板时,应返回命中文件路径。 /// /// 异步测试任务。 [Fact] public async Task CheckFileAsync_ShouldMatchExactFileName() { string filePath = Path.Combine(_rootPath, "PCB-001.txt"); await File.WriteAllTextAsync(filePath, "ok"); var provider = CreateProvider(); FileCheckOutcome outcome = await provider.CheckFileAsync("PCB-001", CreateConfig(_rootPath), CancellationToken.None); Assert.True(outcome.Exists); Assert.Equal(filePath, outcome.MatchedFilePath); } /// /// 文件名只包含条码但不等于模板生成文件名时,不应命中。 /// /// 异步测试任务。 [Fact] public async Task CheckFileAsync_ShouldNotMatchFileNameContainingBarcodeOnly() { string filePath = Path.Combine(_rootPath, "abcd10.txt"); await File.WriteAllTextAsync(filePath, "ok"); var provider = CreateProvider(); FileCheckOutcome outcome = await provider.CheckFileAsync("abcd1", CreateConfig(_rootPath), CancellationToken.None); Assert.False(outcome.Exists); Assert.Empty(outcome.MatchedFilePath); } /// /// 文件名模板包含通配符时,应按通配符匹配。 /// /// 异步测试任务。 [Fact] public async Task CheckFileAsync_ShouldMatchWildcardPattern() { string filePath = Path.Combine(_rootPath, "PCB-003_result.txt"); await File.WriteAllTextAsync(filePath, "ok"); var provider = CreateProvider(); AppConfig config = CreateConfig(_rootPath); config.FileCheck.FileNamePattern = "${barcode}_*.txt"; FileCheckOutcome outcome = await provider.CheckFileAsync("PCB-003", config, CancellationToken.None); Assert.True(outcome.Exists); Assert.Equal(filePath, outcome.MatchedFilePath); } /// /// 创建本地文件校验提供程序。 /// /// 本地文件校验提供程序。 private static LocalFileCheckProvider CreateProvider() { return new LocalFileCheckProvider(new FakeAppLogger()); } /// /// 创建本地文件校验测试配置。 /// /// 本地根目录。 /// 测试配置。 private static AppConfig CreateConfig(string rootPath) { return new AppConfig { FileCheck = new FileCheckOptions { Provider = FileCheckProvider.Local, FileNamePattern = "${barcode}.txt", Local = new LocalFileCheckOptions { RootPath = rootPath } } }; } /// /// 提供本地文件校验测试用的空日志实现。 /// /// 日志分类类型。 private sealed class FakeAppLogger : IAppLogger { public void LogError(string message, bool showInUi = true, params object?[] args) { } public void LogError(Exception exception, string message, bool showInUi = true, params object?[] args) { } public void LogInformation(string message, bool showInUi = true, params object?[] args) { } public void LogWarning(string message, bool showInUi = true, params object?[] args) { } public void LogWarning(Exception exception, string message, bool showInUi = true, params object?[] args) { } } }