using System.Net; using System.Net.Http.Json; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using PageManager.Api.Api.Dtos; using PageManager.Api.Data; using PageManager.Api.Data.Models; using PageManager.Api.Tests.Integration.Fixtures; namespace PageManager.Api.Tests.Integration; [Collection("Postgres")] public class ImportControllerTests : IAsyncLifetime { private readonly TestWebAppFactory _factory; private readonly HttpClient _client; public ImportControllerTests(PostgresFixture postgres) { _factory = new TestWebAppFactory(postgres); _client = _factory.CreateClient(); } public async Task InitializeAsync() { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions .ExecuteSqlRawAsync(db.Database, "TRUNCATE import_queue_items, import_sources RESTART IDENTITY CASCADE"); } public Task DisposeAsync() { _client.Dispose(); _factory.Dispose(); return Task.CompletedTask; } // ── GET /api/sources ────────────────────────────────────────────────────── [Fact] public async Task GetSources_EmptyDb_Returns200WithEmptyArray() { var response = await _client.GetAsync("/api/sources"); response.StatusCode.Should().Be(HttpStatusCode.OK); var sources = await response.Content.ReadFromJsonAsync(); sources.Should().BeEmpty(); } [Fact] public async Task GetSources_SeededSource_ReturnsMappedDto() { await SeedSourceAsync(name: "My Library", type: ImportSourceType.Folder, path: "/books"); var response = await _client.GetAsync("/api/sources"); var sources = await response.Content.ReadFromJsonAsync(); sources.Should().HaveCount(1); sources![0].Name.Should().Be("My Library"); sources[0].Type.Should().Be("folder"); sources[0].Enabled.Should().BeTrue(); } // ── PATCH /api/sources/{id} ─────────────────────────────────────────────── [Fact] public async Task UpdateSource_Found_Returns200WithUpdatedEnabled() { var id = await SeedSourceAsync(); var response = await _client.PatchAsJsonAsync($"/api/sources/{id}", new { enabled = false }); response.StatusCode.Should().Be(HttpStatusCode.OK); var dto = await response.Content.ReadFromJsonAsync(); dto!.Enabled.Should().BeFalse(); } [Fact] public async Task UpdateSource_NotFound_Returns404() { var response = await _client.PatchAsJsonAsync("/api/sources/missing-id", new { enabled = false }); response.StatusCode.Should().Be(HttpStatusCode.NotFound); } // ── GET /api/queue ──────────────────────────────────────────────────────── [Fact] public async Task GetQueue_EmptyDb_Returns200WithEmptyArray() { var response = await _client.GetAsync("/api/queue"); response.StatusCode.Should().Be(HttpStatusCode.OK); var items = await response.Content.ReadFromJsonAsync(); items.Should().BeEmpty(); } // ── DELETE /api/queue/{id} ──────────────────────────────────────────────── [Fact] public async Task RemoveQueueItem_Found_Returns204() { var id = await SeedQueueItemAsync(); var response = await _client.DeleteAsync($"/api/queue/{id}"); response.StatusCode.Should().Be(HttpStatusCode.NoContent); } [Fact] public async Task RemoveQueueItem_NotFound_Returns404() { var response = await _client.DeleteAsync("/api/queue/missing-id"); response.StatusCode.Should().Be(HttpStatusCode.NotFound); } // ── POST /api/queue/{id}/retry ──────────────────────────────────────────── [Fact] public async Task RetryQueueItem_FailedItem_Returns204() { var id = await SeedQueueItemAsync(status: QueueItemStatus.Failed); var response = await _client.PostAsync($"/api/queue/{id}/retry", null); response.StatusCode.Should().Be(HttpStatusCode.NoContent); } [Fact] public async Task RetryQueueItem_NonFailedItem_Returns404() { var id = await SeedQueueItemAsync(status: QueueItemStatus.Completed); var response = await _client.PostAsync($"/api/queue/{id}/retry", null); response.StatusCode.Should().Be(HttpStatusCode.NotFound); } // ── Helpers ─────────────────────────────────────────────────────────────── private async Task SeedSourceAsync( string name = "Test Source", ImportSourceType type = ImportSourceType.Folder, string path = "/test") { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var source = new ImportSource { Name = name, Type = type, Path = path }; db.ImportSources.Add(source); await db.SaveChangesAsync(); return source.Id; } private async Task SeedQueueItemAsync( string filename = "book.epub", QueueItemStatus status = QueueItemStatus.Queued) { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var item = new ImportQueueItem { Filename = filename, Status = status, Source = "https://example.com" }; db.ImportQueueItems.Add(item); await db.SaveChangesAsync(); return item.Id; } }