240 lines
9.2 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|