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";
/// The fake is deliberately fully-capable: it stands in for a facade with BOTH wslc surfaces
/// bound, so the broker's happy path is what these tests exercise. Degradation is the
/// facade's business (docs/WINDOWS_PORT.md D13), and tests that care set this directly.
public List CapabilityList = ["enumerate", "reattach", "tty", "stats"];
public IReadOnlyList Capabilities => CapabilityList;
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;
}
}
}