应文浩wenhao.ying@xiaobao100.com bce2612b64 feat: 完善具体实现
2026-04-06 15:25:34 +08:00

301 lines
12 KiB
C#

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<HttpResponseMessage> 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<Type, object?> _services = new();
private readonly Dictionary<Type, int> _callCounts = new();
public SimpleServiceProvider AddService(Type type, object? instance)
{
_services[type] = instance;
return this;
}
public SimpleServiceProvider AddService<T>(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<string, object, ToolExecutionContext, Task<PermissionResult>> Handler { get; set; }
= static (_, _, _) => Task.FromResult(PermissionResult.Allowed());
public Task<PermissionResult> 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<object?> EnsureServerStartedAsync(string filePath) => Task.FromResult<object?>(null);
public Task<T?> SendRequestAsync<T>(string filePath, string method, object? parameters) => Task.FromResult<T?>(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<TaskStateChangedEventArgs>? TaskStateChanged;
public Task<LocalShellTask> CreateShellTaskAsync(string command, System.Diagnostics.ProcessStartInfo psi)
=> Task.FromResult(new LocalShellTask { TaskId = Guid.NewGuid().ToString("N"), Command = command, ProcessStartInfo = psi });
public Task<LocalAgentTask> CreateAgentTaskAsync(string prompt, string? agentType, string? model)
=> Task.FromResult(new LocalAgentTask { TaskId = Guid.NewGuid().ToString("N"), Prompt = prompt, AgentType = agentType, Model = model });
public Task<RemoteAgentTask> CreateRemoteAgentTaskAsync(string sessionUrl)
=> Task.FromResult(new RemoteAgentTask { TaskId = Guid.NewGuid().ToString("N"), SessionUrl = sessionUrl });
public Task<DreamTask> CreateDreamTaskAsync(string triggerReason)
=> Task.FromResult(new DreamTask { TaskId = Guid.NewGuid().ToString("N"), TriggerReason = triggerReason });
public Task StopTaskAsync(string taskId) => Task.CompletedTask;
public Task<string?> GetTaskOutputAsync(string taskId) => Task.FromResult<string?>(null);
public IReadOnlyList<BackgroundTask> 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<string?> GetOAuthTokenAsync() => Task.FromResult<string?>(null);
}
public sealed class StubToolRegistry : IToolRegistry
{
public IReadOnlyList<ITool> Tools { get; set; } = [];
public Task<IReadOnlyList<ITool>> GetToolsAsync(ToolPermissionContext? permissionContext = null) => Task.FromResult(Tools);
}
public sealed class StubCommandRegistry : ICommandRegistry
{
public IReadOnlyList<ICommand> Commands { get; set; } = [];
public IReadOnlyList<ICommand>? EnabledCommandsOverride { get; set; }
public Task<IReadOnlyList<ICommand>> GetCommandsAsync() => Task.FromResult(Commands);
public Task<IReadOnlyList<ICommand>> GetEnabledCommandsAsync() => Task.FromResult(EnabledCommandsOverride ?? Commands);
}
public sealed class StubSessionMemoryService : ISessionMemoryService
{
public string? CurrentMemory { get; set; }
public List<IReadOnlyList<Message>> ExtractedMessages { get; } = [];
public Task<string?> GetCurrentMemoryAsync() => Task.FromResult(CurrentMemory);
public Task TryExtractAsync(IReadOnlyList<Message> messages)
{
ExtractedMessages.Add(messages);
return Task.CompletedTask;
}
}
public sealed class StubFeatureFlagService : IFeatureFlagService
{
public HashSet<string> EnabledFlags { get; } = new(StringComparer.OrdinalIgnoreCase);
public bool IsEnabled(string featureFlag) => EnabledFlags.Contains(featureFlag);
public IReadOnlySet<string> GetEnabledFlags() => EnabledFlags;
}
public sealed class StubCompanionService : ICompanionService
{
public Func<string, Companion> Factory { get; set; } = static seed => new Companion(Species.Cat, Eye.Blue, Hat.None, Rarity.Common, seed);
public List<string> 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<object?, Task<string>> DescriptionFactory { get; init; } = static _ => Task.FromResult(string.Empty);
public Func<object, bool> ConcurrencySafeFactory { get; init; } = static _ => true;
public Func<object, bool> ReadOnlyFactory { get; init; } = static _ => true;
public JsonElement InputSchema { get; init; } = JsonDocument.Parse("{}").RootElement.Clone();
public bool IsEnabled() => Enabled;
public JsonElement GetInputSchema() => InputSchema;
public Task<string> 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<string, SubmitMessageOptions?, CancellationToken, IAsyncEnumerable<SDKMessage>> SubmitHandler { get; set; }
= static (_, _, _) => Empty();
public IReadOnlyList<Message> Messages { get; set; } = [];
public TokenUsage Usage { get; set; } = new(0, 0, 0, 0);
public IAsyncEnumerable<SDKMessage> SubmitMessageAsync(string content, SubmitMessageOptions? options = null, CancellationToken ct = default)
=> SubmitHandler(content, options, ct);
public Task CancelAsync() => Task.CompletedTask;
public IReadOnlyList<Message> GetMessages() => Messages;
public TokenUsage GetCurrentUsage() => Usage;
private static async IAsyncEnumerable<SDKMessage> Empty()
{
yield break;
}
}
public sealed class TestLogger<T> : ILogger<T>
{
public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullDisposable.Instance;
public bool IsEnabled(LogLevel logLevel) => false;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
}
private sealed class NullDisposable : IDisposable
{
public static NullDisposable Instance { get; } = new();
public void Dispose()
{
}
}
}