Files
ShrinkDataSaver/Tests/LocalStorageProviderStrategyTests.cs
T
cneicy 67d6814c4b
Publish UPM package / publish (push) Successful in 2s
Publish NuGet packages / publish (push) Successful in 4m2s
feat: update Godot integration and coordinated package dependencies
2026-11-06 19:13:16 +08:00

240 lines
9.2 KiB
C#

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using NUnit.Framework;
using UnityEngine.TestTools;
namespace ShrinkDataSaver.Tests
{
[TestFixture]
public class LocalStorageProviderStrategyTests
{
private string _rootPath;
[SetUp]
public void SetUp()
{
_rootPath = Path.Combine(Path.GetTempPath(), $"ShrinkDataSaverStrategyTests_{Guid.NewGuid():N}");
Directory.CreateDirectory(_rootPath);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(_rootPath))
{
Directory.Delete(_rootPath, true);
}
}
[Test]
public void DefaultConstructor_UsesFileReplace()
{
Assert.AreEqual(LocalStorageCommitStrategy.FileReplace,
new LocalStorageProvider(_rootPath).CommitStrategy);
}
[UnityTest]
public IEnumerator RenameOnly_RotatesTwoBackups() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
await WriteAsync(provider, "three");
Assert.AreEqual("three", await ReadAsync(provider, "save.dat"));
Assert.AreEqual("two", await ReadAsync(provider, "save.dat.bak1"));
Assert.AreEqual("one", await ReadAsync(provider, "save.dat.bak2"));
});
[UnityTest]
public IEnumerator RenameOnly_RecoversPrimaryFromBak1_WhenPrimaryMissing() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
var primary = Path.Combine(_rootPath, "save.dat");
var primaryBackup = primary + ".bak1";
File.Delete(primaryBackup);
File.Move(primary, primaryBackup);
Assert.IsTrue(await provider.ExistsAsync("save.dat"));
Assert.AreEqual("two", await ReadAsync(provider, "save.dat"));
Assert.IsTrue(File.Exists(primary));
Assert.IsFalse(File.Exists(primaryBackup));
});
[UnityTest]
public IEnumerator RenameOnly_RecoversPrimaryFromBak2_WhenBak1Missing() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
await WriteAsync(provider, "three");
var primary = Path.Combine(_rootPath, "save.dat");
File.Delete(primary);
File.Delete(primary + ".bak1");
Assert.AreEqual("one", await ReadAsync(provider, "save.dat"));
});
[UnityTest]
public IEnumerator RenameOnly_DeletesStaleTempBeforeWrite() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
var primary = Path.Combine(_rootPath, "save.dat");
File.WriteAllText(primary + ".tmp", "stale", Encoding.UTF8);
await WriteAsync(provider, "two");
Assert.IsFalse(File.Exists(primary + ".tmp"));
Assert.AreEqual("two", await ReadAsync(provider, "save.dat"));
});
[UnityTest]
public IEnumerator RenameOnly_PreservesSecondGeneration_WhenBak1Missing() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
await WriteAsync(provider, "three");
var primary = Path.Combine(_rootPath, "save.dat");
File.Delete(primary + ".bak1");
await WriteAsync(provider, "four");
Assert.AreEqual("four", await ReadAsync(provider, "save.dat"));
Assert.AreEqual("three", await ReadAsync(provider, "save.dat.bak1"));
Assert.AreEqual("one", await ReadAsync(provider, "save.dat.bak2"));
});
[UnityTest]
public IEnumerator RenameOnly_DoesNotPromoteOrphanTemp() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
var primary = Path.Combine(_rootPath, "save.dat");
File.WriteAllText(primary + ".tmp", "partial", new UTF8Encoding(false));
Assert.IsFalse(await provider.ExistsAsync("save.dat"));
Assert.IsFalse(File.Exists(primary));
Assert.IsFalse(File.Exists(primary + ".tmp"));
var readFailed = false;
try
{
await ReadAsync(provider, "save.dat");
}
catch (FileNotFoundException)
{
readFailed = true;
}
Assert.IsTrue(readFailed);
});
[UnityTest]
public IEnumerator RenameOnly_PrefersBackupOverPartialTemp() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
var primary = Path.Combine(_rootPath, "save.dat");
File.Delete(primary);
File.WriteAllText(primary + ".tmp", "partial", new UTF8Encoding(false));
Assert.IsTrue(await provider.ExistsAsync("save.dat"));
Assert.AreEqual("one", await ReadAsync(provider, "save.dat"));
Assert.IsFalse(File.Exists(primary + ".tmp"));
});
[UnityTest]
public IEnumerator FileReplace_DoesNotApplyRenameOnlyRecovery() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
var primary = Path.Combine(_rootPath, "save.dat");
File.Delete(primary);
File.WriteAllText(primary + ".tmp", "partial", new UTF8Encoding(false));
Assert.IsFalse(await provider.ExistsAsync("save.dat"));
Assert.IsTrue(File.Exists(primary + ".bak1"));
Assert.IsTrue(File.Exists(primary + ".tmp"));
var readFailed = false;
try
{
await ReadAsync(provider, "save.dat");
}
catch (FileNotFoundException)
{
readFailed = true;
}
Assert.IsTrue(readFailed);
});
[UnityTest]
public IEnumerator DeleteAsync_RemovesBackupsAndTemp() => UniTask.ToCoroutine(async () =>
{
var provider = new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly, TestMove);
await WriteAsync(provider, "one");
await WriteAsync(provider, "two");
var primary = Path.Combine(_rootPath, "save.dat");
File.WriteAllText(primary + ".tmp", "stale", Encoding.UTF8);
await provider.DeleteAsync("save.dat");
Assert.IsFalse(File.Exists(primary));
Assert.IsFalse(File.Exists(primary + ".bak1"));
Assert.IsFalse(File.Exists(primary + ".bak2"));
Assert.IsFalse(File.Exists(primary + ".tmp"));
});
[UnityTest]
public IEnumerator PlatformInitialize_UsesInjectedStorage() => UniTask.ToCoroutine(async () =>
{
var storage = new MockStorageProvider();
ShrinkSettings.ResetForTesting();
ShrinkDataSaverPlatform.Initialize(storage, "runtime-root");
ShrinkSettings.Set("injected", 7);
await ShrinkSettings.SaveAsync();
Assert.IsTrue(await storage.ExistsAsync("runtime-root/settings.json"));
var bytes = await storage.ReadAsync("runtime-root/settings.json");
StringAssert.Contains("\"injected\"", Encoding.UTF8.GetString(bytes));
});
// Tests the shared rotation contract; Godot integration tests exercise actual overwrite rename.
private static void TestMove(string source, string destination)
{
if (File.Exists(destination)) File.Delete(destination);
File.Move(source, destination);
}
[Test]
public void RenameOnly_RequiresOverwriteCapability() => Assert.Throws<ArgumentException>(() =>
new LocalStorageProvider(_rootPath, LocalStorageCommitStrategy.RenameOnly));
private static async UniTask WriteAsync(IStorageProvider provider, string value)
{
await provider.WriteAsync("save.dat", Encoding.UTF8.GetBytes(value));
}
private static async UniTask<string> ReadAsync(IStorageProvider provider, string path)
{
var bytes = await provider.ReadAsync(path);
return Encoding.UTF8.GetString(bytes);
}
}
}