✨ feat(file-check): 添加 PLC 应答后命中文件删除
* 新增默认关闭的删除开关,仅在收到 PLC 放行应答后执行 * 本地与 SFTP Provider 校验根目录并支持幂等删除 * 补充工作流、设置页和文件删除相关测试与文档
This commit is contained in:
@@ -157,6 +157,178 @@ public sealed class WorkflowHostedServiceTests
|
||||
Assert.All(snapshot.PlcMonitorItems, item => Assert.NotEqual(default, item.LastUpdatedAt));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用应答后删除功能时,收到 PLC 放行应答后应删除本次精确命中的产品 ID 文件。
|
||||
/// </summary>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldDeleteMatchedFile_WhenReleaseAckReceivedAndDeletionEnabled()
|
||||
{
|
||||
const string matchedFilePath = "/pcb/PCB-DELETE-001.txt";
|
||||
var fileCheckService = new FakeFileCheckService
|
||||
{
|
||||
CheckOutcome = new FileCheckOutcome
|
||||
{
|
||||
Exists = true,
|
||||
ConnectionSucceeded = true,
|
||||
MatchedFilePath = matchedFilePath
|
||||
}
|
||||
};
|
||||
var service = new WorkflowHostedService(
|
||||
new SequencedPlcService(new[]
|
||||
{
|
||||
new PlcSignalSnapshot { PcbArrived = true, PlcAckRelease = true },
|
||||
new PlcSignalSnapshot { PcbArrived = false, PlcAckRelease = true }
|
||||
}),
|
||||
new CountingScannerService
|
||||
{
|
||||
Result = new ScanOperationResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
DeviceConnected = true,
|
||||
Barcode = "PCB-DELETE-001"
|
||||
}
|
||||
},
|
||||
fileCheckService,
|
||||
new FakeAndonService(),
|
||||
new AppStateStore(),
|
||||
new AppConfig
|
||||
{
|
||||
Plc = new PlcOptions
|
||||
{
|
||||
PollIntervalMs = 20,
|
||||
ReleaseAckTimeoutMs = 100,
|
||||
ReleasePulseMs = 10
|
||||
},
|
||||
FileCheck = new FileCheckOptions
|
||||
{
|
||||
MaxRetryCount = 0,
|
||||
DeleteMatchedFileAfterPlcAck = true
|
||||
}
|
||||
},
|
||||
new FakeAppLogger<WorkflowHostedService>());
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
await Task.Delay(200);
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(matchedFilePath, Assert.Single(fileCheckService.DeletedFilePaths));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未收到 PLC 放行应答时,即使启用删除功能也不得删除产品 ID 文件。
|
||||
/// </summary>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldNotDeleteMatchedFile_WhenReleaseAckTimesOut()
|
||||
{
|
||||
var fileCheckService = new FakeFileCheckService
|
||||
{
|
||||
CheckOutcome = new FileCheckOutcome
|
||||
{
|
||||
Exists = true,
|
||||
ConnectionSucceeded = true,
|
||||
MatchedFilePath = "/pcb/PCB-KEEP-001.txt"
|
||||
}
|
||||
};
|
||||
var service = new WorkflowHostedService(
|
||||
new SequencedPlcService(new[]
|
||||
{
|
||||
new PlcSignalSnapshot { PcbArrived = true, PlcAckRelease = false },
|
||||
new PlcSignalSnapshot { PcbArrived = false, PlcAckRelease = false }
|
||||
}),
|
||||
new CountingScannerService
|
||||
{
|
||||
Result = new ScanOperationResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
DeviceConnected = true,
|
||||
Barcode = "PCB-KEEP-001"
|
||||
}
|
||||
},
|
||||
fileCheckService,
|
||||
new FakeAndonService(),
|
||||
new AppStateStore(),
|
||||
new AppConfig
|
||||
{
|
||||
Plc = new PlcOptions
|
||||
{
|
||||
PollIntervalMs = 20,
|
||||
ReleaseAckTimeoutMs = 50,
|
||||
ReleasePulseMs = 10
|
||||
},
|
||||
FileCheck = new FileCheckOptions
|
||||
{
|
||||
MaxRetryCount = 0,
|
||||
DeleteMatchedFileAfterPlcAck = true
|
||||
}
|
||||
},
|
||||
new FakeAppLogger<WorkflowHostedService>());
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
await Task.Delay(200);
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
Assert.Empty(fileCheckService.DeletedFilePaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除开关关闭时,即使收到 PLC 放行应答也不得删除产品 ID 文件。
|
||||
/// </summary>
|
||||
/// <returns>异步测试任务。</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldNotDeleteMatchedFile_WhenDeletionDisabled()
|
||||
{
|
||||
var fileCheckService = new FakeFileCheckService
|
||||
{
|
||||
CheckOutcome = new FileCheckOutcome
|
||||
{
|
||||
Exists = true,
|
||||
ConnectionSucceeded = true,
|
||||
MatchedFilePath = "/pcb/PCB-KEEP-002.txt"
|
||||
}
|
||||
};
|
||||
var service = new WorkflowHostedService(
|
||||
new SequencedPlcService(new[]
|
||||
{
|
||||
new PlcSignalSnapshot { PcbArrived = true, PlcAckRelease = true },
|
||||
new PlcSignalSnapshot { PcbArrived = false, PlcAckRelease = true }
|
||||
}),
|
||||
new CountingScannerService
|
||||
{
|
||||
Result = new ScanOperationResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
DeviceConnected = true,
|
||||
Barcode = "PCB-KEEP-002"
|
||||
}
|
||||
},
|
||||
fileCheckService,
|
||||
new FakeAndonService(),
|
||||
new AppStateStore(),
|
||||
new AppConfig
|
||||
{
|
||||
Plc = new PlcOptions
|
||||
{
|
||||
PollIntervalMs = 20,
|
||||
ReleaseAckTimeoutMs = 100,
|
||||
ReleasePulseMs = 10
|
||||
},
|
||||
FileCheck = new FileCheckOptions
|
||||
{
|
||||
MaxRetryCount = 0,
|
||||
DeleteMatchedFileAfterPlcAck = false
|
||||
}
|
||||
},
|
||||
new FakeAppLogger<WorkflowHostedService>());
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
await Task.Delay(200);
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
Assert.Empty(fileCheckService.DeletedFilePaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 双扫码汇总后,应使用汇总条码继续执行统一文件校验。
|
||||
/// </summary>
|
||||
@@ -577,14 +749,28 @@ public sealed class WorkflowHostedServiceTests
|
||||
|
||||
public FileCheckOutcome CheckOutcome { get; set; } = new();
|
||||
|
||||
public FileDeleteOutcome DeleteOutcome { get; set; } = new()
|
||||
{
|
||||
IsSuccess = true
|
||||
};
|
||||
|
||||
public string LastBarcode { get; private set; } = string.Empty;
|
||||
|
||||
public List<string> DeletedFilePaths { get; } = new();
|
||||
|
||||
public Task<FileCheckOutcome> CheckFileAsync(string barcode, CancellationToken cancellationToken)
|
||||
{
|
||||
LastBarcode = barcode;
|
||||
return Task.FromResult(CheckOutcome);
|
||||
}
|
||||
|
||||
public Task<FileDeleteOutcome> DeleteMatchedFileAsync(string filePath, CancellationToken cancellationToken)
|
||||
{
|
||||
DeletedFilePaths.Add(filePath);
|
||||
DeleteOutcome.FilePath = filePath;
|
||||
return Task.FromResult(DeleteOutcome);
|
||||
}
|
||||
|
||||
public Task<FileCheckTestOutcome> TestConnectionAsync(AppConfig? config, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(TestOutcome);
|
||||
|
||||
Reference in New Issue
Block a user