using System.Net; using System.Text; using BuildFlow.Application.AiModel.Handlers; using BuildFlow.Application.AiModel.Queries; using BuildFlow.Domain.Enums; using BuildFlow.Shared.Domain.Errors; using BuildFlow.Shared.Domain.Exceptions; using FluentAssertions; using Xunit; namespace BuildFlow.Rag.Tests; /// /// 发现 LM Studio 本地模型处理器测试。 /// 成功路径用本地 HttpListener 伪服务(绕过真实 LM Studio/网络);异常分支覆盖非法 BaseUrl(依赖调用前校验)。 /// public class DiscoverLmStudioModelsQueryHandlerTests { [Fact] public async Task Handle_LocalServer_ReturnsDiscoveredModels() { var port = 18731; using var listener = new HttpListener(); listener.Prefixes.Add($"http://127.0.0.1:{port}/"); listener.Start(); var serveTask = Task.Run(async () => { var context = await listener.GetContextAsync(); var body = Encoding.UTF8.GetBytes("{\"data\":[{\"id\":\"local-llm\"}]}"); context.Response.ContentType = "application/json"; await context.Response.OutputStream.WriteAsync(body); context.Response.Close(); }); var handler = new DiscoverLmStudioModelsQueryHandler(); var result = await handler.Handle( new DiscoverLmStudioModelsQuery($"http://127.0.0.1:{port}/v1"), CancellationToken.None); await serveTask; result.Should().ContainSingle(); result[0].ModelId.Should().Be("local-llm"); result[0].Provider.Should().Be((int)ModelProviderEnum.LMStudio); } [Fact] public async Task Handle_InvalidBaseUrl_ThrowsInvalidParam() { var handler = new DiscoverLmStudioModelsQueryHandler(); var act = async () => await handler.Handle( new DiscoverLmStudioModelsQuery("ftp://not-http"), CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.InvalidParam); } [Fact] public async Task Handle_RelativeBaseUrl_ThrowsInvalidParam() { var handler = new DiscoverLmStudioModelsQueryHandler(); var act = async () => await handler.Handle( new DiscoverLmStudioModelsQuery("localhost:1234"), CancellationToken.None); var ex = await act.Should().ThrowAsync(); ex.Which.Code.Should().Be(ErrorCode.InvalidParam); } }