using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using Moq; using FluentResults; using NexusReader.Application.Abstractions.Services; using NexusReader.Application.DTOs.AI; using NexusReader.Application.DTOs.User; using NexusReader.Application.Queries.Library; using NexusReader.Data.Persistence; using NexusReader.Domain.Entities; using Xunit; namespace NexusReader.Application.Tests.Queries; public class QueryTests : IDisposable { private readonly SqliteConnection _connection; private readonly DbContextOptions _contextOptions; private readonly Mock> _dbContextFactoryMock; private readonly Mock>> _embeddingGeneratorMock; public QueryTests() { _connection = new SqliteConnection("DataSource=:memory:"); _connection.Open(); _contextOptions = new DbContextOptionsBuilder() .UseSqlite(_connection) .Options; // Seed initial database schema using var context = new AppDbContext(_contextOptions); context.Database.EnsureCreated(); _dbContextFactoryMock = new Mock>(); _dbContextFactoryMock.Setup(f => f.CreateDbContextAsync(It.IsAny())) .ReturnsAsync(() => new AppDbContext(_contextOptions)); _dbContextFactoryMock.Setup(f => f.CreateDbContext()) .Returns(() => new AppDbContext(_contextOptions)); _embeddingGeneratorMock = new Mock>>(); } [Fact] public async Task GetMyEbooksQuery_WithPopulatedDescription_ReturnsCorrectDescription() { // Arrange using (var context = new AppDbContext(_contextOptions)) { var user = new NexusUser { Id = "user-123", UserName = "testuser", Email = "test@example.com", TenantId = "tenant-123", SubscriptionPlanId = 1 }; context.Users.Add(user); var author = new Author { Id = 1, Name = "Adam Mickiewicz" }; context.Authors.Add(author); var ebook = new Ebook { Id = Guid.NewGuid(), UserId = "user-123", Title = "Pan Tadeusz", AuthorId = author.Id, Description = "A Polish epic poem written by Adam Mickiewicz.", CoverUrl = "cover.png", Progress = 42.5, LastChapter = "Księga I", LastChapterIndex = 1, AddedDate = DateTime.UtcNow, LastReadDate = DateTime.UtcNow, FilePath = "dummy.epub" }; context.Ebooks.Add(ebook); await context.SaveChangesAsync(); } var handler = new GetMyEbooksQueryHandler(_dbContextFactoryMock.Object); var query = new GetMyEbooksQuery("user-123"); // Act var result = await handler.Handle(query, CancellationToken.None); // Assert result.IsSuccess.Should().BeTrue(); result.Value.Should().HaveCount(1); result.Value.First().Title.Should().Be("Pan Tadeusz"); result.Value.First().Description.Should().Be("A Polish epic poem written by Adam Mickiewicz."); result.Value.First().Progress.Should().Be(42.5); } [Fact] public async Task SearchLibrarySemanticallyQuery_WithEmptyQueryText_ReturnsFailure() { // Arrange var knowledgeServiceMock = new Mock(); var handler = new SearchLibrarySemanticallyQueryHandler(knowledgeServiceMock.Object); var query = new SearchLibrarySemanticallyQuery("", "tenant-123"); // Act var result = await handler.Handle(query, CancellationToken.None); // Assert result.IsSuccess.Should().BeFalse(); result.Errors.First().Message.Should().Be("Query text cannot be empty."); } [Fact] public async Task SearchLibrarySemanticallyQuery_WithValidQuery_CallsKnowledgeService() { // Arrange var knowledgeServiceMock = new Mock(); var expectedResults = new List { new SemanticSearchResultDto { ContentHash = "hash-123", Snippet = "Semantic search result content snippet", UnitType = "Concept", RelevanceScore = 0.95f } }; knowledgeServiceMock.Setup(s => s.SearchLibrarySemanticallyAsync("test", "tenant-123", 5, It.IsAny())) .ReturnsAsync(Result.Ok(expectedResults)); var handler = new SearchLibrarySemanticallyQueryHandler(knowledgeServiceMock.Object); var query = new SearchLibrarySemanticallyQuery("test", "tenant-123"); // Act var result = await handler.Handle(query, CancellationToken.None); // Assert result.IsSuccess.Should().BeTrue(); result.Value.Should().HaveCount(1); result.Value.First().Snippet.Should().Be("Semantic search result content snippet"); result.Value.First().ContentHash.Should().Be("hash-123"); } public void Dispose() { _connection.Close(); _connection.Dispose(); } }