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);
}
}