using System.Net; using System.Text; using System.Text.Json; using FreeCode.Core.Enums; using FreeCode.Core.Interfaces; using FreeCode.Core.Models; using Microsoft.Extensions.Logging; namespace FreeCode.Tests.Unit.Helpers; public sealed class MockHttpHandler : DelegatingHandler { private readonly string _response; private readonly HttpStatusCode _statusCode; private int _callCount; public MockHttpHandler(string response, HttpStatusCode statusCode = HttpStatusCode.OK) { _response = response ?? string.Empty; _statusCode = statusCode; } public HttpRequestMessage? LastRequest { get; private set; } public string? LastRequestBody { get; private set; } public int RequestCount => _callCount; protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Interlocked.Increment(ref _callCount); LastRequest = request; LastRequestBody = request.Content is null ? null : request.Content.ReadAsStringAsync(cancellationToken).GetAwaiter().GetResult(); var contentType = _response.StartsWith("data:", StringComparison.Ordinal) ? "text/event-stream" : "application/json"; var response = new HttpResponseMessage(_statusCode) { Content = new StringContent(_response, Encoding.UTF8, contentType) }; return Task.FromResult(response); } } public static class TestHelper { public static ToolExecutionContext CreateContext( string? workingDirectory = null, PermissionMode permissionMode = PermissionMode.Default, IPermissionEngine? permissionEngine = null, ILspClientManager? lspManager = null, IBackgroundTaskManager? taskManager = null, IServiceProvider? services = null) { return new ToolExecutionContext( WorkingDirectory: workingDirectory ?? Directory.GetCurrentDirectory(), PermissionMode: permissionMode, AdditionalWorkingDirectories: [], PermissionEngine: permissionEngine ?? new StubPermissionEngine(), LspManager: lspManager ?? new StubLspClientManager(), TaskManager: taskManager ?? new StubBackgroundTaskManager(), Services: services ?? new SimpleServiceProvider()); } public static ApiRequest CreateApiRequest( string systemPrompt, JsonElement? messages = null, JsonElement? tools = null, string? model = null) { var resolvedMessages = messages ?? JsonDocument.Parse(""" [ { "role": "user", "content": "Hello" } ] """).RootElement.Clone(); var resolvedTools = tools ?? JsonDocument.Parse(""" [ { "name": "test_tool", "description": "A test tool", "input_schema": { "type": "object", "properties": {} } } ] """).RootElement.Clone(); return new ApiRequest(systemPrompt, resolvedMessages, resolvedTools, model); } public static ApiRequest CreateMinimalApiRequest() { return CreateApiRequest("You are a system prompt."); } } public static class TestContextFactory { public static ToolExecutionContext CreateContext( string? workingDirectory = null, PermissionMode permissionMode = PermissionMode.Default, IPermissionEngine? permissionEngine = null, ILspClientManager? lspManager = null, IBackgroundTaskManager? taskManager = null, IServiceProvider? services = null) => TestHelper.CreateContext(workingDirectory, permissionMode, permissionEngine, lspManager, taskManager, services); } public sealed class SimpleServiceProvider : IServiceProvider { private readonly Dictionary _services = new(); private readonly Dictionary _callCounts = new(); public SimpleServiceProvider AddService(Type type, object? instance) { _services[type] = instance; return this; } public SimpleServiceProvider AddService(T instance) where T : class => AddService(typeof(T), instance); public object? GetService(Type serviceType) { _callCounts[serviceType] = GetCallCount(serviceType) + 1; return _services.GetValueOrDefault(serviceType); } public int GetCallCount(Type serviceType) => _callCounts.GetValueOrDefault(serviceType); } public sealed class StubPermissionEngine : IPermissionEngine { public Func> Handler { get; set; } = static (_, _, _) => Task.FromResult(PermissionResult.Allowed()); public Task CheckAsync(string toolName, object input, ToolExecutionContext context) => Handler(toolName, input, context); } public sealed class StubLspClientManager : ILspClientManager { public bool IsConnected => false; public Task InitializeAsync(CancellationToken ct = default) => Task.CompletedTask; public Task ShutdownAsync() => Task.CompletedTask; public object? GetServerForFile(string filePath) => null; public Task EnsureServerStartedAsync(string filePath) => Task.FromResult(null); public Task SendRequestAsync(string filePath, string method, object? parameters) => Task.FromResult(default); public Task OpenFileAsync(string filePath, string content) => Task.CompletedTask; public Task ChangeFileAsync(string filePath, string content) => Task.CompletedTask; public Task SaveFileAsync(string filePath) => Task.CompletedTask; public Task CloseFileAsync(string filePath) => Task.CompletedTask; } public sealed class StubBackgroundTaskManager : IBackgroundTaskManager { public event EventHandler? TaskStateChanged; public Task CreateShellTaskAsync(string command, System.Diagnostics.ProcessStartInfo psi) => Task.FromResult(new LocalShellTask { TaskId = Guid.NewGuid().ToString("N"), Command = command, ProcessStartInfo = psi }); public Task CreateAgentTaskAsync(string prompt, string? agentType, string? model) => Task.FromResult(new LocalAgentTask { TaskId = Guid.NewGuid().ToString("N"), Prompt = prompt, AgentType = agentType, Model = model }); public Task CreateRemoteAgentTaskAsync(string sessionUrl) => Task.FromResult(new RemoteAgentTask { TaskId = Guid.NewGuid().ToString("N"), SessionUrl = sessionUrl }); public Task CreateDreamTaskAsync(string triggerReason) => Task.FromResult(new DreamTask { TaskId = Guid.NewGuid().ToString("N"), TriggerReason = triggerReason }); public Task StopTaskAsync(string taskId) => Task.CompletedTask; public Task GetTaskOutputAsync(string taskId) => Task.FromResult(null); public IReadOnlyList ListTasks() => []; public BackgroundTask? GetTask(string taskId) => null; } public sealed class StubAuthService : IAuthService { public bool IsAuthenticated { get; set; } public bool IsClaudeAiUser { get; set; } public bool IsInternalUser { get; set; } public event EventHandler? AuthStateChanged; public Task LoginAsync(string provider = "anthropic") => Task.CompletedTask; public Task LogoutAsync() => Task.CompletedTask; public Task GetOAuthTokenAsync() => Task.FromResult(null); } public sealed class StubToolRegistry : IToolRegistry { public IReadOnlyList Tools { get; set; } = []; public Task> GetToolsAsync(ToolPermissionContext? permissionContext = null) => Task.FromResult(Tools); } public sealed class StubCommandRegistry : ICommandRegistry { public IReadOnlyList Commands { get; set; } = []; public IReadOnlyList? EnabledCommandsOverride { get; set; } public Task> GetCommandsAsync() => Task.FromResult(Commands); public Task> GetEnabledCommandsAsync() => Task.FromResult(EnabledCommandsOverride ?? Commands); } public sealed class StubSessionMemoryService : ISessionMemoryService { public string? CurrentMemory { get; set; } public List> ExtractedMessages { get; } = []; public Task GetCurrentMemoryAsync() => Task.FromResult(CurrentMemory); public Task TryExtractAsync(IReadOnlyList messages) { ExtractedMessages.Add(messages); return Task.CompletedTask; } } public sealed class StubFeatureFlagService : IFeatureFlagService { public HashSet EnabledFlags { get; } = new(StringComparer.OrdinalIgnoreCase); public bool IsEnabled(string featureFlag) => EnabledFlags.Contains(featureFlag); public IReadOnlySet GetEnabledFlags() => EnabledFlags; } public sealed class StubCompanionService : ICompanionService { public Func Factory { get; set; } = static seed => new Companion(Species.Cat, Eye.Blue, Hat.None, Rarity.Common, seed); public List Seeds { get; } = []; public Companion Create(string seed) { Seeds.Add(seed); return Factory(seed); } } public sealed class StubTool : ITool { public string Name { get; init; } = string.Empty; public string[]? Aliases { get; init; } public string? SearchHint { get; init; } public ToolCategory Category { get; init; } = ToolCategory.FileSystem; public bool Enabled { get; init; } = true; public Func> DescriptionFactory { get; init; } = static _ => Task.FromResult(string.Empty); public Func ConcurrencySafeFactory { get; init; } = static _ => true; public Func ReadOnlyFactory { get; init; } = static _ => true; public JsonElement InputSchema { get; init; } = JsonDocument.Parse("{}").RootElement.Clone(); public bool IsEnabled() => Enabled; public JsonElement GetInputSchema() => InputSchema; public Task GetDescriptionAsync(object? input = null) => DescriptionFactory(input); public bool IsConcurrencySafe(object input) => ConcurrencySafeFactory(input); public bool IsReadOnly(object input) => ReadOnlyFactory(input); } public sealed class StubQueryEngine : IQueryEngine { public Func> SubmitHandler { get; set; } = static (_, _, _) => Empty(); public IReadOnlyList Messages { get; set; } = []; public TokenUsage Usage { get; set; } = new(0, 0, 0, 0); public IAsyncEnumerable SubmitMessageAsync(string content, SubmitMessageOptions? options = null, CancellationToken ct = default) => SubmitHandler(content, options, ct); public Task CancelAsync() => Task.CompletedTask; public IReadOnlyList GetMessages() => Messages; public TokenUsage GetCurrentUsage() => Usage; private static async IAsyncEnumerable Empty() { yield break; } } public sealed class TestLogger : ILogger { public IDisposable BeginScope(TState state) where TState : notnull => NullDisposable.Instance; public bool IsEnabled(LogLevel logLevel) => false; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { } private sealed class NullDisposable : IDisposable { public static NullDisposable Instance { get; } = new(); public void Dispose() { } } }