✨ feat(*): 添加管理员解锁与 SFTP 探活能力
* 新增管理员解锁弹窗、手动操作权限控制与倒计时状态 * 支持系统设置页测试 SFTP 连接,并在启动时执行探活 * 补充设计时服务、全局异常兜底与相关单元测试
This commit is contained in:
26
tests/AxiOmron.PcbCheck.Tests/AxiOmron.PcbCheck.Tests.csproj
Normal file
26
tests/AxiOmron.PcbCheck.Tests/AxiOmron.PcbCheck.Tests.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\AxiOmron.PcbCheck\AxiOmron.PcbCheck.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
95
tests/AxiOmron.PcbCheck.Tests/SystemSettingViewModelTests.cs
Normal file
95
tests/AxiOmron.PcbCheck.Tests/SystemSettingViewModelTests.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using AxiOmron.PcbCheck.Models;
|
||||
using AxiOmron.PcbCheck.Options;
|
||||
using AxiOmron.PcbCheck.Services.Interfaces;
|
||||
using AxiOmron.PcbCheck.ViewModels;
|
||||
|
||||
namespace AxiOmron.PcbCheck.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 验证系统设置页视图模型中的 SFTP 测试连接行为。
|
||||
/// </summary>
|
||||
public sealed class SystemSettingViewModelTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 当 SFTP 测试成功时,应更新成功状态文本。
|
||||
/// </summary>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
[Fact]
|
||||
public async Task TestSftpConnectionAsync_ShouldReportSuccess_WhenConnectionSucceeds()
|
||||
{
|
||||
var configService = new FakeAppConfigService();
|
||||
var sftpLookupService = new FakeSftpLookupService
|
||||
{
|
||||
TestOutcome = new SftpConnectionTestOutcome
|
||||
{
|
||||
IsSuccess = true,
|
||||
RootPathAccessible = true,
|
||||
StatusMessage = "SFTP 连接成功,根目录可访问。"
|
||||
}
|
||||
};
|
||||
var viewModel = new SystemSettingViewModel(configService, sftpLookupService);
|
||||
|
||||
await viewModel.TestSftpConnectionCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Equal("SFTP 连接成功,根目录可访问。", viewModel.StatusMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当 SFTP 测试失败时,应将失败原因展示到状态文本。
|
||||
/// </summary>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
[Fact]
|
||||
public async Task TestSftpConnectionAsync_ShouldReportFailure_WhenConnectionFails()
|
||||
{
|
||||
var configService = new FakeAppConfigService();
|
||||
var sftpLookupService = new FakeSftpLookupService
|
||||
{
|
||||
TestOutcome = new SftpConnectionTestOutcome
|
||||
{
|
||||
IsSuccess = false,
|
||||
IsSystemError = true,
|
||||
StatusMessage = "SFTP 连接失败: timeout"
|
||||
}
|
||||
};
|
||||
var viewModel = new SystemSettingViewModel(configService, sftpLookupService);
|
||||
|
||||
await viewModel.TestSftpConnectionCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Equal("SFTP 连接失败: timeout", viewModel.StatusMessage);
|
||||
}
|
||||
|
||||
private sealed class FakeAppConfigService : IAppConfigService
|
||||
{
|
||||
public AppConfig Config { get; } = new();
|
||||
|
||||
public AppConfig Load()
|
||||
{
|
||||
return Config;
|
||||
}
|
||||
|
||||
public void Save(AppConfig config)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(config);
|
||||
}
|
||||
|
||||
public string GetConfigPath()
|
||||
{
|
||||
return "appConfig.json";
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeSftpLookupService : ISftpLookupService
|
||||
{
|
||||
public SftpConnectionTestOutcome TestOutcome { get; set; } = new();
|
||||
|
||||
public Task<SftpCheckOutcome> CheckFileAsync(string barcode, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public Task<SftpConnectionTestOutcome> TestConnectionAsync(SftpOptions? options, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(TestOutcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
tests/AxiOmron.PcbCheck.Tests/WorkflowHostedServiceTests.cs
Normal file
86
tests/AxiOmron.PcbCheck.Tests/WorkflowHostedServiceTests.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using AxiOmron.PcbCheck.Models;
|
||||
using AxiOmron.PcbCheck.Options;
|
||||
using AxiOmron.PcbCheck.Services.Implementations;
|
||||
using AxiOmron.PcbCheck.Services.Interfaces;
|
||||
|
||||
namespace AxiOmron.PcbCheck.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// 验证流程后台服务的 SFTP 启动探活行为。
|
||||
/// </summary>
|
||||
public sealed class WorkflowHostedServiceTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动探活失败时,不应抛出异常,且应写入运行态状态。
|
||||
/// </summary>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
[Fact]
|
||||
public async Task ProbeSftpOnStartupAsync_ShouldUpdateSnapshot_WhenConnectionFails()
|
||||
{
|
||||
var stateStore = new AppStateStore();
|
||||
var service = new WorkflowHostedService(
|
||||
new FakePlcService(),
|
||||
new FakeScannerService(),
|
||||
new FakeSftpLookupService
|
||||
{
|
||||
TestOutcome = new SftpConnectionTestOutcome
|
||||
{
|
||||
IsSuccess = false,
|
||||
IsSystemError = true,
|
||||
StatusMessage = "启动探活失败"
|
||||
}
|
||||
},
|
||||
new FakeAndonService(),
|
||||
stateStore,
|
||||
new AppConfig(),
|
||||
new FakeAppLogger<WorkflowHostedService>());
|
||||
|
||||
await service.ProbeSftpOnStartupAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal("异常", stateStore.GetSnapshot().SftpStatus);
|
||||
}
|
||||
|
||||
private sealed class FakePlcService : IPlcService
|
||||
{
|
||||
public Task ForceReconnectAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task<PlcSignalSnapshot> ReadSignalsAsync(CancellationToken cancellationToken) => Task.FromResult(new PlcSignalSnapshot());
|
||||
public Task WriteStateAsync(PlcProcessState state, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class FakeScannerService : IScannerService
|
||||
{
|
||||
public Task ForceReconnectAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task<bool> TestConnectionAsync(CancellationToken cancellationToken) => Task.FromResult(true);
|
||||
public Task<ScanOperationResult> TriggerScanAsync(CancellationToken cancellationToken) => Task.FromResult(new ScanOperationResult());
|
||||
}
|
||||
|
||||
private sealed class FakeSftpLookupService : ISftpLookupService
|
||||
{
|
||||
public SftpConnectionTestOutcome TestOutcome { get; set; } = new();
|
||||
|
||||
public Task<SftpCheckOutcome> CheckFileAsync(string barcode, CancellationToken cancellationToken) => Task.FromResult(new SftpCheckOutcome());
|
||||
|
||||
public Task<SftpConnectionTestOutcome> TestConnectionAsync(SftpOptions? options, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(TestOutcome);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeAndonService : IAndonService
|
||||
{
|
||||
public Task<AndonOperationResult> RaiseAlarmAsync(AndonAlarmRequest request, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(new AndonOperationResult());
|
||||
|
||||
public Task<AndonOperationResult> TestAsync(CancellationToken cancellationToken)
|
||||
=> Task.FromResult(new AndonOperationResult());
|
||||
}
|
||||
|
||||
private sealed class FakeAppLogger<TCategoryName> : IAppLogger<TCategoryName>
|
||||
{
|
||||
public void LogError(string message, bool showInUi = false, params object?[] args) { }
|
||||
public void LogError(Exception exception, string message, bool showInUi = false, params object?[] args) { }
|
||||
public void LogInformation(string message, bool showInUi = false, params object?[] args) { }
|
||||
public void LogWarning(string message, bool showInUi = false, params object?[] args) { }
|
||||
public void LogWarning(Exception exception, string message, bool showInUi = false, params object?[] args) { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user