#0021 增加测试工程
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
using Xunit;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Tests.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
namespace XplorePlane.Tests.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试 PipelinePersistenceService(任务 6.5)
|
||||
/// </summary>
|
||||
public class PipelinePersistenceServiceTests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
private readonly PipelinePersistenceService _svc;
|
||||
|
||||
public PipelinePersistenceServiceTests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), $"XplorePlaneTests_{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
|
||||
var mockImageSvc = TestHelpers.CreateMockImageService(new[] { "Blur", "Sharpen" });
|
||||
_svc = new PipelinePersistenceService(mockImageSvc.Object, _tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_tempDir))
|
||||
Directory.Delete(_tempDir, recursive: true);
|
||||
}
|
||||
|
||||
private static PipelineModel BuildModel(string name = "TestPipeline", params string[] keys)
|
||||
{
|
||||
var model = new PipelineModel { Name = name };
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
model.Nodes.Add(new PipelineNodeModel { OperatorKey = keys[i], Order = i });
|
||||
return model;
|
||||
}
|
||||
|
||||
// ── 往返一致性 ────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAndLoad_RoundTrip_NamePreserved()
|
||||
{
|
||||
var model = BuildModel("MyPipeline", "Blur", "Sharpen");
|
||||
var path = Path.Combine(_tempDir, "test.pipeline.json");
|
||||
|
||||
await _svc.SaveAsync(model, path);
|
||||
var loaded = await _svc.LoadAsync(path);
|
||||
|
||||
Assert.Equal("MyPipeline", loaded.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAndLoad_RoundTrip_NodeCountPreserved()
|
||||
{
|
||||
var model = BuildModel("P", "Blur", "Sharpen");
|
||||
var path = Path.Combine(_tempDir, "test.pipeline.json");
|
||||
|
||||
await _svc.SaveAsync(model, path);
|
||||
var loaded = await _svc.LoadAsync(path);
|
||||
|
||||
Assert.Equal(2, loaded.Nodes.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAndLoad_RoundTrip_OperatorKeysPreserved()
|
||||
{
|
||||
var model = BuildModel("P", "Blur", "Sharpen");
|
||||
var path = Path.Combine(_tempDir, "test.pipeline.json");
|
||||
|
||||
await _svc.SaveAsync(model, path);
|
||||
var loaded = await _svc.LoadAsync(path);
|
||||
|
||||
Assert.Equal("Blur", loaded.Nodes[0].OperatorKey);
|
||||
Assert.Equal("Sharpen", loaded.Nodes[1].OperatorKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAndLoad_RoundTrip_OrderIsSequential()
|
||||
{
|
||||
var model = BuildModel("P", "Blur", "Sharpen");
|
||||
var path = Path.Combine(_tempDir, "test.pipeline.json");
|
||||
|
||||
await _svc.SaveAsync(model, path);
|
||||
var loaded = await _svc.LoadAsync(path);
|
||||
|
||||
for (int i = 0; i < loaded.Nodes.Count; i++)
|
||||
Assert.Equal(i, loaded.Nodes[i].Order);
|
||||
}
|
||||
|
||||
// ── 非法路径被拒绝 ────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAsync_PathTraversal_ThrowsUnauthorized()
|
||||
{
|
||||
var model = BuildModel("P");
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => _svc.SaveAsync(model, "../../../evil.json"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_PathTraversal_ThrowsUnauthorized()
|
||||
{
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
() => _svc.LoadAsync("../../etc/passwd"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_EmptyPath_ThrowsArgumentException()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => _svc.LoadAsync(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_NullPath_ThrowsArgumentException()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => _svc.LoadAsync(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_FileNotFound_ThrowsFileNotFoundException()
|
||||
{
|
||||
var path = Path.Combine(_tempDir, "nonexistent.pipeline.json");
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(
|
||||
() => _svc.LoadAsync(path));
|
||||
}
|
||||
|
||||
// ── 非法 OperatorKey 被跳过 ───────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Load_UnregisteredOperatorKey_NodeSkipped()
|
||||
{
|
||||
// 手动写入包含未注册 Key 的 JSON
|
||||
var json = """
|
||||
{
|
||||
"Id": "00000000-0000-0000-0000-000000000001",
|
||||
"Name": "Test",
|
||||
"DeviceId": "",
|
||||
"Nodes": [
|
||||
{ "Id": "00000000-0000-0000-0000-000000000002", "OperatorKey": "Blur", "Order": 0, "IsEnabled": true, "Parameters": {} },
|
||||
{ "Id": "00000000-0000-0000-0000-000000000003", "OperatorKey": "UNKNOWN_KEY", "Order": 1, "IsEnabled": true, "Parameters": {} }
|
||||
]
|
||||
}
|
||||
""";
|
||||
var path = Path.Combine(_tempDir, "mixed.pipeline.json");
|
||||
await File.WriteAllTextAsync(path, json);
|
||||
|
||||
var loaded = await _svc.LoadAsync(path);
|
||||
|
||||
Assert.Single(loaded.Nodes);
|
||||
Assert.Equal("Blur", loaded.Nodes[0].OperatorKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Load_AllUnregisteredKeys_ReturnsEmptyNodes()
|
||||
{
|
||||
var json = """
|
||||
{
|
||||
"Id": "00000000-0000-0000-0000-000000000001",
|
||||
"Name": "Test",
|
||||
"DeviceId": "",
|
||||
"Nodes": [
|
||||
{ "Id": "00000000-0000-0000-0000-000000000002", "OperatorKey": "FAKE1", "Order": 0, "IsEnabled": true, "Parameters": {} },
|
||||
{ "Id": "00000000-0000-0000-0000-000000000003", "OperatorKey": "FAKE2", "Order": 1, "IsEnabled": true, "Parameters": {} }
|
||||
]
|
||||
}
|
||||
""";
|
||||
var path = Path.Combine(_tempDir, "all_fake.pipeline.json");
|
||||
await File.WriteAllTextAsync(path, json);
|
||||
|
||||
var loaded = await _svc.LoadAsync(path);
|
||||
|
||||
Assert.Empty(loaded.Nodes);
|
||||
}
|
||||
|
||||
// ── LoadAllAsync ──────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAllAsync_EmptyDirectory_ReturnsEmpty()
|
||||
{
|
||||
var result = await _svc.LoadAllAsync(_tempDir);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAllAsync_MultipleFiles_ReturnsAll()
|
||||
{
|
||||
var m1 = BuildModel("P1", "Blur");
|
||||
var m2 = BuildModel("P2", "Sharpen");
|
||||
await _svc.SaveAsync(m1, Path.Combine(_tempDir, "p1.pipeline.json"));
|
||||
await _svc.SaveAsync(m2, Path.Combine(_tempDir, "p2.pipeline.json"));
|
||||
|
||||
var result = await _svc.LoadAllAsync(_tempDir);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user