using NucleicBroker; namespace NucleicBroker.Tests; /// /// In-memory IWslc for the broker contract tests (docs/WINDOWS_PORT.md §14): records calls, /// serves canned state, and exposes the event sink so tests can emit process output/exit and /// session-death exactly as the wslc facade would — from arbitrary threads, asynchronously. /// public sealed class FakeWslc : IWslc { public IBrokerEvents? Events; public List Calls = []; public Dictionary ContainerStates = []; public List Images = []; public List MissingComponents = []; public string Gateway = "172.28.0.1"; public SessionSpec? LastSession; public ContainerCreateSpec? LastCreate; public (string Name, int Signal, int GraceMs)? LastStop; public FakeProcess? LastProcess; public long LastProcId; public Exception? NextError; public string? WslcVersion => "9.9-test"; public void SetEvents(IBrokerEvents events) => Events = events; private void Record(string call) { Calls.Add(call); if (NextError is { } error) { NextError = null; throw error; } } public Task> MissingComponentsAsync(CancellationToken ct) { Record("components.missing"); return Task.FromResult>(MissingComponents); } public Task InstallComponentsAsync(CancellationToken ct) { Record("components.install"); Events?.InstallProgress("installing", 50); Events?.InstallProgress("done", 100); return Task.CompletedTask; } public Task EnsureSessionAsync(SessionSpec spec, CancellationToken ct) { Record("session.ensure"); LastSession = spec; return Task.FromResult(Gateway); } public Task TerminateSessionAsync(CancellationToken ct) { Record("session.terminate"); return Task.CompletedTask; } public Task PullImageAsync(string reference, RegistryAuth? auth, CancellationToken ct) { Record($"image.pull:{reference}"); Events?.PullProgress(reference, "downloading", 10, 100); Events?.PullProgress(reference, "done", 100, 100); Images.Add(new ImageInfo(reference, "sha256:fake", 1234)); return Task.CompletedTask; } public Task> ListImagesAsync(CancellationToken ct) { Record("image.list"); return Task.FromResult>(Images); } public Task DeleteImageAsync(string reference, CancellationToken ct) { Record($"image.delete:{reference}"); Images.RemoveAll(i => i.Ref == reference); return Task.CompletedTask; } public Task InspectImageAsync(string reference, CancellationToken ct) { Record($"image.inspect:{reference}"); return Task.FromResult(Images.FirstOrDefault(i => i.Ref == reference)); } public Task CreateContainerAsync(ContainerCreateSpec spec, CancellationToken ct) { Record($"container.create:{spec.Name}"); LastCreate = spec; ContainerStates[spec.Name] = "stopped"; return Task.CompletedTask; } public Task StartContainerAsync(string name, CancellationToken ct) { Record($"container.start:{name}"); if (!ContainerStates.ContainsKey(name)) throw new WslcError(WslcError.NotFound, $"no container named {name}"); ContainerStates[name] = "running"; return Task.CompletedTask; } public Task StopContainerAsync(string name, int signal, int graceMs, CancellationToken ct) { Record($"container.stop:{name}"); LastStop = (name, signal, graceMs); ContainerStates[name] = "stopped"; return Task.CompletedTask; } public Task DeleteContainerAsync(string name, bool force, CancellationToken ct) { Record($"container.delete:{name}"); ContainerStates.Remove(name); return Task.CompletedTask; } public Task> ListContainersAsync(CancellationToken ct) { Record("container.list"); return Task.FromResult>( ContainerStates.Select(kv => new ContainerInfo(kv.Key, "img", kv.Value)).ToList()); } public Task ContainerStateAsync(string name, CancellationToken ct) { Record($"container.state:{name}"); return Task.FromResult(ContainerStates.GetValueOrDefault(name, "absent")); } public Task ContainerStatsAsync(string name, CancellationToken ct) { Record($"container.stats:{name}"); return Task.FromResult( ContainerStates.GetValueOrDefault(name) == "running" ? new ContainerStatsInfo(111, 222, 333, 0) : null); } public Task ExecAsync(long procId, ProcSpec spec, CancellationToken ct) { Record($"proc.exec:{spec.Container}:{string.Join(' ', spec.Argv)}"); if (ContainerStates.GetValueOrDefault(spec.Container) != "running") throw new WslcError(WslcError.NotRunning, $"container {spec.Container} isn't running"); LastProcId = procId; LastProcess = new FakeProcess(); return Task.FromResult(LastProcess); } public sealed class FakeProcess : IWslcProcess { public MemoryStream Stdin = new(); public bool StdinClosed; public List Signals = []; public (int Cols, int Rows)? LastResize; public Task WriteStdinAsync(ReadOnlyMemory data, CancellationToken ct) { Stdin.Write(data.Span); return Task.CompletedTask; } public Task CloseStdinAsync(CancellationToken ct) { StdinClosed = true; return Task.CompletedTask; } public Task SignalAsync(int signal, CancellationToken ct) { Signals.Add(signal); return Task.CompletedTask; } public Task ResizeAsync(int cols, int rows, CancellationToken ct) { LastResize = (cols, rows); return Task.CompletedTask; } } }