build-flow.api/tests/BuildFlow.Monitoring.Tests/ExternalSystemsHandlerTests.cs
xiangning 7479521546 feat(rag): 实现RAG模块的Hybrid检索和向量索引优化
- 添加HNSW向量索引提升检索性能
- 实现Hybrid检索模式支持向量和关键词双路召回
- 采用RRF算法融合多路召回结果
- 支持ParentChild分块模式的上下文回填
- 优化向量写入采用批量SQL操作提升性能
- 添加RAG PostgreSQL连接字符串配置
- 更新部署脚本增加PGVector集成测试
- 修正表名引用从document_chunks到b_document_chunks
- 重构RagRetrievalService实现完整的检索流程
- 增加重排策略支持关键词和LLM重排
- 修改服务器部署地址配置
2026-08-11 10:59:09 +08:00

179 lines
7.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using BuildFlow.Shared.Domain.Errors;
using BuildFlow.Shared.Domain.Exceptions;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Monitoring.Application.ExternalSystems.Commands;
using Monitoring.Application.ExternalSystems.DTOs;
using Monitoring.Application.ExternalSystems.Handlers;
using Monitoring.Application.ExternalSystems.Queries;
using Monitoring.Infrastructure.Persistence;
namespace BuildFlow.Monitoring.Tests;
/// <summary>
/// [SOP-MONITORING] ExternalSystems 子域 Handler 全覆盖测试。
/// 覆盖: Create / List / GetDetail / Update(含无效类型、缺失、非法 JSON、凭证覆盖分支)。
/// 约定: 直接用 new Handler(db) 调 Handle;db 由 MonitoringTestDb.Create 提供(自动补审计字段)。
/// 用 Create handler 串联构造前置数据,避免直接依赖实体内部构造。
/// </summary>
public class ExternalSystemsHandlerTests
{
private static MonitoringDbContext Db([System.Runtime.CompilerServices.CallerMemberName] string name = "")
=> MonitoringTestDb.Create(name);
#region Create
[Fact]
public async Task Create_Valid_ReturnsDtoAndPersists()
{
await using var db = Db();
var result = await new CreateExternalSystemCommandHandler(db).Handle(
new CreateExternalSystemCommand("u号租", 1, "https://api.example.com", 300, null),
CancellationToken.None);
result.Should().NotBeNull();
result.Name.Should().Be("u号租");
result.SystemType.Should().Be(1);
result.BaseUrl.Should().Be("https://api.example.com");
result.IsEnabled.Should().BeTrue();
result.PollIntervalSeconds.Should().Be(300);
(await db.ExternalSystems.CountAsync()).Should().Be(1);
}
[Fact]
public async Task Create_InvalidSystemType_ThrowsBusiness()
{
await using var db = Db();
var act = async () => await new CreateExternalSystemCommandHandler(db).Handle(
new CreateExternalSystemCommand("x", 999, "https://x", 0, null),
CancellationToken.None);
var ex = await act.Should().ThrowAsync<BusinessException>();
ex.Which.Code.Should().Be(ErrorCode.InvalidParam);
}
#endregion
#region List
[Fact]
public async Task List_Empty_ReturnsEmpty()
{
await using var db = Db();
var result = await new ListExternalSystemsQueryHandler(db)
.Handle(new ListExternalSystemsQuery(), CancellationToken.None);
result.Should().BeEmpty();
}
[Fact]
public async Task List_ReturnsAllDescendingByCreatedAt()
{
await using var db = Db();
var create = new CreateExternalSystemCommandHandler(db);
var a = await create.Handle(new CreateExternalSystemCommand("A", 1, "https://a", 1, null), CancellationToken.None);
var b = await create.Handle(new CreateExternalSystemCommand("B", 1, "https://b", 1, null), CancellationToken.None);
// 强制 A 更早创建,使倒序断言稳定
var entA = await db.ExternalSystems.FirstAsync(x => x.Id == a.Id);
entA.CreatedAt = DateTime.UtcNow.AddMinutes(-10);
await db.SaveChangesAsync();
var list = await new ListExternalSystemsQueryHandler(db)
.Handle(new ListExternalSystemsQuery(), CancellationToken.None);
list.Should().HaveCount(2);
list[0].Name.Should().Be("B");
list[1].Name.Should().Be("A");
}
#endregion
#region GetDetail
[Fact]
public async Task GetDetail_Existing_ReturnsDetail()
{
await using var db = Db();
var created = await new CreateExternalSystemCommandHandler(db).Handle(
new CreateExternalSystemCommand("u号租", 1, "https://x", 60, null),
CancellationToken.None);
var detail = await new GetExternalSystemDetailQueryHandler(db)
.Handle(new GetExternalSystemDetailQuery(created.Id), CancellationToken.None);
detail.Should().NotBeNull();
detail.Id.Should().Be(created.Id);
detail.Name.Should().Be("u号租");
}
[Fact]
public async Task GetDetail_NotFound_Throws()
{
await using var db = Db();
var act = async () => await new GetExternalSystemDetailQueryHandler(db)
.Handle(new GetExternalSystemDetailQuery(Guid.NewGuid()), CancellationToken.None);
var ex = await act.Should().ThrowAsync<NotFoundException>();
ex.Which.Code.Should().Be(ErrorCode.NotFound);
}
#endregion
#region Update
[Fact]
public async Task Update_NameAndBaseUrl_Overwrites()
{
await using var db = Db();
var created = await new CreateExternalSystemCommandHandler(db).Handle(
new CreateExternalSystemCommand("old", 1, "https://old", 60, null),
CancellationToken.None);
var result = await new UpdateExternalSystemCommandHandler(db).Handle(
new UpdateExternalSystemCommand(created.Id, "new", "https://new", null, null, null, null),
CancellationToken.None);
result.Name.Should().Be("new");
result.BaseUrl.Should().Be("https://new");
}
[Fact]
public async Task Update_NonExistent_ThrowsNotFound()
{
await using var db = Db();
var act = async () => await new UpdateExternalSystemCommandHandler(db).Handle(
new UpdateExternalSystemCommand(Guid.NewGuid(), "x", null, null, null, null, null),
CancellationToken.None);
var ex = await act.Should().ThrowAsync<NotFoundException>();
ex.Which.Code.Should().Be(ErrorCode.NotFound);
}
[Fact]
public async Task Update_InvalidParametersJson_ThrowsBusiness()
{
await using var db = Db();
var created = await new CreateExternalSystemCommandHandler(db).Handle(
new CreateExternalSystemCommand("x", 1, "https://x", 60, null),
CancellationToken.None);
var act = async () => await new UpdateExternalSystemCommandHandler(db).Handle(
new UpdateExternalSystemCommand(created.Id, null, null, null, null, "{bad json", null),
CancellationToken.None);
var ex = await act.Should().ThrowAsync<BusinessException>();
ex.Which.Code.Should().Be(ErrorCode.InvalidParam);
}
[Fact]
public async Task Update_Credentials_Overwrites()
{
await using var db = Db();
var created = await new CreateExternalSystemCommandHandler(db).Handle(
new CreateExternalSystemCommand("x", 1, "https://x", 60, null),
CancellationToken.None);
var creds = new List<CredentialInput> { new("token", "ak", "av", true, null) };
await new UpdateExternalSystemCommandHandler(db).Handle(
new UpdateExternalSystemCommand(created.Id, null, null, null, null, null, creds),
CancellationToken.None);
var detail = await new GetExternalSystemDetailQueryHandler(db)
.Handle(new GetExternalSystemDetailQuery(created.Id), CancellationToken.None);
detail.Credentials.Should().HaveCount(1);
detail.Credentials[0].Key.Should().Be("ak");
}
#endregion
}