using ClawdDotNet.App.Services; using ClawdDotNet.App.Settings; using ClawdDotNet.Core.Config; using ClawdDotNet.Core.Tools; using Microsoft.Extensions.Logging.Abstractions; using ClawdDotNet.Core.Instances; namespace ClawdDotNet.App.Tests.Services; /// /// An without engine, scanner and network — enough for the /// administration services. /// /// This is possible because AppHost.StartAsync is only one way to /// build one: the properties are required init, nothing more is needed. What /// is missing here — Engine, Scanner, Staging — is regularly null in operation /// too, and exactly that behavior is to be tested. /// internal sealed class TestHost : IDisposable { private readonly string _root; public AppHost Host { get; } /// Every change that went through SaveInstance. public List Changes { get; } = []; public TestHost(params AgentConfig[] agents) { _root = Path.Combine( Path.GetTempPath(), "clawd-tests", "host-" + Guid.NewGuid().ToString("N")[..8]); var instancesDir = Path.Combine(_root, "Instances"); var instancePath = Path.Combine(instancesDir, "Test"); Directory.CreateDirectory(instancePath); var instance = new InstanceConfig { InstanceName = "Test" }; instance.Agents.AddRange(agents); Host = new AppHost { Settings = new SettingsManager(_root), Directories = new InstanceDirectoryManager(instancesDir), Instance = instance, InstancePath = instancePath, LogDirectory = Path.Combine(_root, "Logs"), LoggerFactory = NullLoggerFactory.Instance, Tools = new ToolRegistry() }; Host.InstanceChanged += Changes.Add; } public void Dispose() { try { Directory.Delete(_root, recursive: true); } catch { /* A leftover test folder is no reason to turn a run red. */ } } public static AgentConfig Agent(string agentId, string displayName = "") => new() { AgentId = agentId, DisplayName = displayName.Length == 0 ? agentId : displayName, Model = "anthropic/claude-sonnet-4-5" }; }