acdream/tests/AcDream.App.Tests/Configuration/SessionConfigurationLoaderTests.cs
Erik 75a6724d5b wip: Campaign LA LA1 fix round — INCOMPLETE, stopped mid-task
Agent was stopped for token budget partway through the LA1 review fix
round. Landed here: F1 best-effort SessionStatusWriter, F2 App reader
tolerance (paths/mode), F5 argument-parsing hardening, plus new tests.
NOT DONE: F4 shared-fixture production shape (was the next step), F3
reconnect disconnected edge + recorded limitation, F6 exited
idempotency/reasons, F7 structural redaction test, F8 platform-guard
test + comment fix, optional RuntimeOptions PrintMembers redaction.

Build/test state UNVERIFIED at this commit. Next session: finish the
remaining findings, run the suites, then narrow re-review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:32:52 +02:00

140 lines
4.6 KiB
C#

using AcDream.App.Configuration;
namespace AcDream.App.Tests.Configuration;
/// <summary>
/// Campaign LA slice LA1 review fix (F2): the App session-config reader
/// must TOLERATE the two document shapes only the Headless side of the
/// pinned contract currently defines meaning for — <c>process.paths</c>
/// (<c>HeadlessPathOverrides</c>) and the per-session <c>mode</c>
/// discriminator (LA2's probe flow) — so a launcher-composed document does
/// not throw a raw unmapped-member <see cref="System.Text.Json.JsonException"/>
/// on the App host. See <c>docs/plans/2026-08-14-launcher-campaign.md</c>
/// LA1's pinned contract.
/// </summary>
public sealed class SessionConfigurationLoaderTests
{
[Fact]
public void ProcessPathsAreAcceptedButIgnored()
{
using TemporaryFile file = TemporaryFile.Create(
"""
{
"version": 1,
"process": {
"paths": {
"configDirectory": "/config",
"dataDirectory": "/data",
"cacheDirectory": "/cache"
}
},
"sessions": [
{
"id": "paths-tolerant",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "account",
"credential": { "provider": "environment", "reference": "X" }
}
]
}
""");
(SessionConfiguration configuration, SessionDescriptor session) =
SessionConfigurationLoader.Load(file.Path);
Assert.Equal("paths-tolerant", session.Id);
Assert.Equal("/config", configuration.Process?.Paths?.ConfigDirectory);
Assert.Equal("/data", configuration.Process?.Paths?.DataDirectory);
Assert.Equal("/cache", configuration.Process?.Paths?.CacheDirectory);
}
[Fact]
public void AbsentModeIsTreatedAsPlay()
{
using TemporaryFile file = TemporaryFile.Create(
"""
{
"version": 1,
"sessions": [
{
"id": "no-mode",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "account",
"credential": { "provider": "environment", "reference": "X" }
}
]
}
""");
(_, SessionDescriptor session) = SessionConfigurationLoader.Load(file.Path);
Assert.Null(session.Mode);
}
[Fact]
public void ProbeModeFailsLoadWithAnExplicitHeadlessOnlyMessage()
{
using TemporaryFile file = TemporaryFile.Create(
"""
{
"version": 1,
"sessions": [
{
"id": "probe-session",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "account",
"credential": { "provider": "environment", "reference": "X" },
"mode": "probe"
}
]
}
""");
SessionConfigurationException error = Assert.Throws<SessionConfigurationException>(
() => SessionConfigurationLoader.Load(file.Path));
Assert.Contains("mode", error.Message, StringComparison.OrdinalIgnoreCase);
Assert.Contains("probe", error.Message, StringComparison.Ordinal);
Assert.Contains("headless-only", error.Message, StringComparison.Ordinal);
}
[Fact]
public void UnrecognizedModeFailsLoad()
{
using TemporaryFile file = TemporaryFile.Create(
"""
{
"version": 1,
"sessions": [
{
"id": "bad-mode",
"endpoint": { "host": "127.0.0.1", "port": 9000 },
"account": "account",
"credential": { "provider": "environment", "reference": "X" },
"mode": "bogus"
}
]
}
""");
Assert.Throws<SessionConfigurationException>(
() => SessionConfigurationLoader.Load(file.Path));
}
private sealed class TemporaryFile : IDisposable
{
private TemporaryFile(string path) => Path = path;
internal string Path { get; }
internal static TemporaryFile Create(string json)
{
string path = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
$"acdream-app-la1-loader-{Guid.NewGuid():N}.json");
File.WriteAllText(path, json);
return new TemporaryFile(path);
}
public void Dispose() => File.Delete(Path);
}
}