test(save): 添加开发存档清单校验测试

This commit is contained in:
2026-07-16 18:40:14 +08:00
parent 8deff54e48
commit 7dfd87e868
@@ -7,6 +7,7 @@ using System.Text;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using Newtonsoft.Json;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
@@ -374,5 +375,139 @@ namespace AibisDream.EditorTools
}
}
}
[TestFixture]
public sealed class DevSaveCatalogTests
{
private string root;
[SetUp]
public void SetUp()
{
root = Path.Combine(Path.GetTempPath(), $"aibis-dev-save-{Guid.NewGuid():N}");
Directory.CreateDirectory(root);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(root)) Directory.Delete(root, true);
}
[Test]
public void CatalogUsesExplicitOrderAndKeepsInvalidEntriesVisible()
{
WriteCatalog(new DevSaveCatalogEntryDto { order = 20, label = "late", path = "S/late", anchorNode = "Late" },
new DevSaveCatalogEntryDto { order = 10, label = "early", path = "S/early", anchorNode = "Early" });
var section = DevSaveCatalog.Load(root).Single();
Assert.That(section.Entries.Select(entry => entry.Label), Is.EqualTo(new[] { "early", "late" }));
Assert.That(section.Entries.All(entry => !entry.IsValid), Is.True);
Assert.That(section.Entries.All(entry => entry.Error.Contains("缺失")), Is.True);
}
[Test]
public void CatalogRejectsPathTraversal()
{
WriteCatalog(new DevSaveCatalogEntryDto { order = 1, label = "unsafe", path = "../outside", anchorNode = "Start" });
var entry = DevSaveCatalog.Load(root).Single().Entries.Single();
Assert.That(entry.IsValid, Is.False);
Assert.That(entry.Error, Does.Contain("越过"));
}
[Test]
public void CatalogReportsCorruptSchemaAndAnchorMismatch()
{
WriteSnapshot("S/corrupt", "{");
WriteSnapshot("S/schema", JsonConvert.SerializeObject(BuildSnapshot("Node", schema: 99)));
WriteSnapshot("S/node", JsonConvert.SerializeObject(BuildSnapshot("Actual")));
WriteCatalog(
new DevSaveCatalogEntryDto { order = 1, label = "corrupt", path = "S/corrupt", anchorNode = "Node" },
new DevSaveCatalogEntryDto { order = 2, label = "schema", path = "S/schema", anchorNode = "Node" },
new DevSaveCatalogEntryDto { order = 3, label = "node", path = "S/node", anchorNode = "Expected" });
var entries = DevSaveCatalog.Load(root).Single().Entries;
Assert.That(entries[0].Error, Does.Contain("JSON"));
Assert.That(entries[1].Error, Does.Contain("版本"));
Assert.That(entries[2].Error, Does.Contain("节点不匹配"));
}
[Test]
public void CommittedCatalogContainsOnlyValidUniqueEntries()
{
var sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
Assert.That(DevSaveCatalog.LastError, Is.Null);
Assert.That(sections.Count, Is.GreaterThan(0));
foreach (var section in sections)
{
Assert.That(section.Entries, Is.Not.Empty, section.Id);
Assert.That(section.Entries.All(entry => entry.IsValid), Is.True,
string.Join("\n", section.Entries.Where(entry => !entry.IsValid).Select(entry => $"{entry.Label}: {entry.Error}")));
Assert.That(section.Entries.Select(entry => entry.Order).Distinct().Count(), Is.EqualTo(section.Entries.Count));
Assert.That(section.Entries.Select(entry => entry.AnchorNode).Distinct().Count(), Is.EqualTo(section.Entries.Count));
}
}
[Test]
public void StrictRestoreContextTracksPhaseWarningsAndErrors()
{
var context = new SnapshotRestoreContext(new SaveSnapshot(), strictMode: true);
context.SetPhase("Provider restore");
context.Warn("optional state missing");
context.Error("required state missing");
Assert.That(context.StrictMode, Is.True);
Assert.That(context.CurrentPhase, Is.EqualTo("Provider restore"));
Assert.That(context.Warnings, Is.EqualTo(new[] { "optional state missing" }));
Assert.That(context.Errors, Is.EqualTo(new[] { "required state missing" }));
Assert.That(context.HasErrors, Is.True);
}
private void WriteCatalog(params DevSaveCatalogEntryDto[] entries)
{
var catalog = new DevSaveCatalogDto
{
sections = new List<DevSaveCatalogSectionDto>
{
new()
{
id = "S",
title = "Section",
expectedSceneName = "Scene/Test",
expectedSceneSoName = "TestSO",
expectedYarnProjectId = "TestYarn",
entries = entries.ToList()
}
}
};
File.WriteAllText(Path.Combine(root, "catalog.json"), JsonConvert.SerializeObject(catalog));
}
private void WriteSnapshot(string relativeDirectory, string json)
{
var directory = Path.Combine(root, relativeDirectory.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(directory);
File.WriteAllText(Path.Combine(directory, "snapshot.json"), json);
}
private static SaveSnapshot BuildSnapshot(string node, int schema = SaveSnapshotSchema.CurrentVersion)
{
return new SaveSnapshot
{
schemaVersion = schema,
scene = new SceneSnapshotDto { sceneName = "Scene/Test" },
anchor = new AnchorSnapshot
{
sceneSoName = "TestSO",
yarnProjectId = "TestYarn",
nodeName = node
}
};
}
}
}
#endif