171 lines
7.5 KiB
C#
171 lines
7.5 KiB
C#
using System.Text.Json;
|
|
using AcDream.Launcher.Core.Profiles;
|
|
|
|
namespace AcDream.Launcher.Core.Tests.Profiles;
|
|
|
|
public sealed class LauncherProfileHardeningTests : IDisposable
|
|
{
|
|
private readonly string _root = Path.Combine(
|
|
Path.GetTempPath(),
|
|
"acdream-launcher-profile-hardening-tests",
|
|
Guid.NewGuid().ToString("N"));
|
|
private readonly string _filePath;
|
|
|
|
public LauncherProfileHardeningTests()
|
|
{
|
|
Directory.CreateDirectory(_root);
|
|
_filePath = Path.Combine(_root, "launcher-profiles.json");
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(_root))
|
|
{
|
|
Directory.Delete(_root, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void MultiFieldEditValidatesEverythingBeforeChangingAnything()
|
|
{
|
|
LauncherProfileStore store = CreatePopulatedStore();
|
|
|
|
Assert.Throws<LauncherProfileException>(() =>
|
|
store.EditServer(
|
|
"Local ACE",
|
|
newName: "Partially renamed",
|
|
newHost: "changed.example.test",
|
|
newPort: 0));
|
|
ServerProfile server = Assert.Single(store.Document.Servers);
|
|
Assert.Equal("Local ACE", server.Name);
|
|
Assert.Equal("127.0.0.1", server.Host);
|
|
Assert.Equal(9000, server.Port);
|
|
|
|
Assert.Throws<LauncherProfileException>(() =>
|
|
store.EditCharacter(
|
|
"Local ACE",
|
|
"testaccount",
|
|
"+Acdream",
|
|
newName: "+PartiallyRenamed",
|
|
newId: "not-an-id"));
|
|
CharacterProfile character = Assert.Single(server.Accounts.Single().Characters);
|
|
Assert.Equal("+Acdream", character.Name);
|
|
Assert.Equal("0x5000000A", character.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void TransactionRestoresTheExactDocumentWhenPersistenceFails()
|
|
{
|
|
Directory.CreateDirectory(_filePath);
|
|
var store = new LauncherProfileStore(_filePath);
|
|
store.Load();
|
|
|
|
Assert.ThrowsAny<Exception>(() =>
|
|
store.ExecuteTransaction(() =>
|
|
store.AddServer("Should roll back", "host", 9000)));
|
|
|
|
Assert.Empty(store.Document.Servers);
|
|
Assert.False(File.Exists(_filePath + ".tmp"));
|
|
}
|
|
|
|
[Fact]
|
|
public void TransactionRestoresNestedCredentialAndSettingsOnMutationFailure()
|
|
{
|
|
LauncherProfileStore store = CreatePopulatedStore();
|
|
string before = JsonSerializer.Serialize(store.Document);
|
|
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
store.ExecuteTransaction(() =>
|
|
{
|
|
AccountProfile account = store.Document.Servers.Single().Accounts.Single();
|
|
account.Password = "transient-secret";
|
|
account.Characters.Single().Plugins.Add("Transient.Plugin");
|
|
throw new InvalidOperationException("simulated mutation failure");
|
|
}));
|
|
|
|
Assert.Equal(before, JsonSerializer.Serialize(store.Document));
|
|
}
|
|
|
|
public static TheoryData<string> InvalidDocuments => new()
|
|
{
|
|
{ """{"version":1,"servers":null}""" },
|
|
{ """{"version":1,"servers":[null]}""" },
|
|
{ """{"version":1,"servers":[{"name":" ","host":"h","port":9000,"accounts":[]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":" ","port":9000,"accounts":[]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":0,"accounts":[]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[]},{"name":"s","host":"h2","port":9001,"accounts":[]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[]},{"account":"a","password":"p2","characters":[]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":null,"characters":[]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":null}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c","id":"0x00000000","launchMode":"gui","plugins":[],"loginCommands":[]}]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c","id":"0x50000001","launchMode":"invalid","plugins":[],"loginCommands":[]}]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c","id":"0x50000001","launchMode":"gui","plugins":null,"loginCommands":[]}]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c","id":"0x50000001","launchMode":"gui","plugins":["P","P"],"loginCommands":[]}]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c","id":"0x50000001","launchMode":"gui","plugins":[],"loginCommands":[" "]}]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c","id":"0x50000001","launchMode":"gui","plugins":[],"loginCommands":[]},{"name":"c","id":"0x50000002","launchMode":"gui","plugins":[],"loginCommands":[]}]}]}]}""" },
|
|
{ """{"version":1,"servers":[{"name":"s","host":"h","port":9000,"accounts":[{"account":"a","password":"p","characters":[{"name":"c1","id":"0x50000001","launchMode":"gui","plugins":[],"loginCommands":[]},{"name":"c2","id":"0x50000001","launchMode":"gui","plugins":[],"loginCommands":[]}]}]}]}""" },
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(InvalidDocuments))]
|
|
public void LoadRejectsSemanticallyInvalidDocuments(string json)
|
|
{
|
|
File.WriteAllText(_filePath, json);
|
|
var store = new LauncherProfileStore(_filePath);
|
|
|
|
Assert.Throws<LauncherProfileException>(() => store.Load());
|
|
}
|
|
|
|
[Fact]
|
|
public void FailedLoadDoesNotReplaceAnAlreadyLoadedDocument()
|
|
{
|
|
LauncherProfileStore store = CreatePopulatedStore();
|
|
File.WriteAllText(_filePath, """{"version":1,"servers":null}""");
|
|
|
|
Assert.Throws<LauncherProfileException>(() => store.Load());
|
|
|
|
Assert.Equal("Local ACE", Assert.Single(store.Document.Servers).Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void LinuxLoadNormalizesAnExistingCredentialFileTo0600BeforeReading()
|
|
{
|
|
if (!OperatingSystem.IsLinux())
|
|
{
|
|
return;
|
|
}
|
|
|
|
File.WriteAllText(_filePath, """{"version":1,"servers":[]}""");
|
|
File.SetUnixFileMode(
|
|
_filePath,
|
|
UnixFileMode.UserRead
|
|
| UnixFileMode.UserWrite
|
|
| UnixFileMode.GroupRead
|
|
| UnixFileMode.OtherRead);
|
|
|
|
var store = new LauncherProfileStore(_filePath);
|
|
Assert.True(store.Load());
|
|
|
|
Assert.Equal(
|
|
UnixFileMode.UserRead | UnixFileMode.UserWrite,
|
|
File.GetUnixFileMode(_filePath));
|
|
}
|
|
|
|
private LauncherProfileStore CreatePopulatedStore()
|
|
{
|
|
var store = new LauncherProfileStore(_filePath);
|
|
store.Load();
|
|
store.AddServer("Local ACE", "127.0.0.1", 9000);
|
|
store.AddAccount("Local ACE", "testaccount", "password");
|
|
store.AddCharacter(
|
|
"Local ACE",
|
|
"testaccount",
|
|
"+Acdream",
|
|
"0x5000000A",
|
|
LaunchMode.Headless,
|
|
["ExamplePlugin"],
|
|
["/vt start"]);
|
|
store.Save();
|
|
return store;
|
|
}
|
|
}
|