using AcDream.App.Configuration; namespace AcDream.App.Tests.Configuration; /// /// 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 — process.paths /// (HeadlessPathOverrides) and the per-session mode /// discriminator (LA2's probe flow) — so a launcher-composed document does /// not throw a raw unmapped-member /// on the App host. See docs/plans/2026-08-14-launcher-campaign.md /// LA1's pinned contract. /// 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( () => 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( () => 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); } }