feat(rag): implement KM-RAG retrieval read-path, endpoints, query handlers, global Q&A panel, and unit tests

This commit is contained in:
2026-05-20 20:22:29 +02:00
parent 0473103286
commit c5102c1d14
10 changed files with 839 additions and 1 deletions
@@ -148,6 +148,57 @@ public class QueryTests : IDisposable
result.Value.First().ContentHash.Should().Be("hash-123");
}
[Fact]
public async Task AskLibraryQuestionQuery_WithEmptyQuestion_ReturnsFailure()
{
// Arrange
var knowledgeServiceMock = new Mock<IKnowledgeService>();
var handler = new AskLibraryQuestionQueryHandler(knowledgeServiceMock.Object);
var query = new AskLibraryQuestionQuery("", "tenant-123");
// Act
var result = await handler.Handle(query, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeFalse();
result.Errors.First().Message.Should().Be("Question cannot be empty.");
}
[Fact]
public async Task AskLibraryQuestionQuery_WithValidQuestion_CallsKnowledgeService()
{
// Arrange
var knowledgeServiceMock = new Mock<IKnowledgeService>();
var expectedResponse = new GroundedResponseDto
{
Answer = "Based on the book, water boils at 100 degrees Celsius.",
Citations = new List<CitationDto>
{
new CitationDto
{
CitationId = "chunk-1",
Snippet = "Water boils at 100 degrees Celsius.",
SourceBook = "Physics 101"
}
}
};
knowledgeServiceMock.Setup(s => s.AskQuestionAsync("what temp does water boil?", "tenant-123", null, 5, It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Ok(expectedResponse));
var handler = new AskLibraryQuestionQueryHandler(knowledgeServiceMock.Object);
var query = new AskLibraryQuestionQuery("what temp does water boil?", "tenant-123");
// Act
var result = await handler.Handle(query, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Answer.Should().Be("Based on the book, water boils at 100 degrees Celsius.");
result.Value.Citations.Should().HaveCount(1);
result.Value.Citations.First().CitationId.Should().Be("chunk-1");
}
public void Dispose()
{
_connection.Close();