190 lines
7.0 KiB
C#
190 lines
7.0 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using FluentAssertions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
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 FilesControllerTests : IAsyncLifetime
|
|
{
|
|
private readonly TestWebAppFactory _factory;
|
|
private readonly HttpClient _client;
|
|
|
|
public FilesControllerTests(PostgresFixture postgres)
|
|
{
|
|
_factory = new TestWebAppFactory(postgres);
|
|
_client = _factory.CreateClient();
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
await db.Database.ExecuteSqlRawAsync(
|
|
"TRUNCATE book_files, book_authors, series_entries, editions, books, authors, series, import_sources RESTART IDENTITY CASCADE");
|
|
}
|
|
|
|
public Task DisposeAsync()
|
|
{
|
|
_client.Dispose();
|
|
_factory.Dispose();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
// ── GET /api/books/{id}/files ─────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task GetBookFiles_NoFiles_Returns200WithEmptyArray()
|
|
{
|
|
var bookId = await SeedBookAsync();
|
|
|
|
var response = await _client.GetAsync($"/api/books/{bookId}/files");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
var files = await response.Content.ReadFromJsonAsync<BookFileDto[]>();
|
|
files.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetBookFiles_WithFiles_Returns200WithCorrectData()
|
|
{
|
|
var bookId = await SeedBookAsync("Dune");
|
|
await SeedFileAsync(filename: "dune.epub", format: FileFormat.Epub, bookId: bookId);
|
|
await SeedFileAsync(filename: "dune.mobi", format: FileFormat.Mobi, bookId: bookId);
|
|
|
|
var response = await _client.GetAsync($"/api/books/{bookId}/files");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
var files = await response.Content.ReadFromJsonAsync<BookFileDto[]>();
|
|
files.Should().HaveCount(2);
|
|
files!.Select(f => f.Filename).Should().BeEquivalentTo(["dune.epub", "dune.mobi"]);
|
|
files.First(f => f.Filename == "dune.epub").Format.Should().Be("epub");
|
|
}
|
|
|
|
// ── GET /api/files?unmatched=true ─────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task GetUnmatchedFiles_MixedFiles_ReturnsOnlyUnmatched()
|
|
{
|
|
var bookId = await SeedBookAsync();
|
|
await SeedFileAsync(filename: "unmatched.epub", bookId: null);
|
|
await SeedFileAsync(filename: "matched.epub", bookId: bookId);
|
|
|
|
var response = await _client.GetAsync("/api/files?unmatched=true");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
var files = await response.Content.ReadFromJsonAsync<BookFileDto[]>();
|
|
files.Should().ContainSingle(f => f.Filename == "unmatched.epub");
|
|
files.Should().NotContain(f => f.Filename == "matched.epub");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetFiles_NoUnmatchedParam_Returns400()
|
|
{
|
|
var response = await _client.GetAsync("/api/files");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
|
}
|
|
|
|
// ── PATCH /api/files/{id} ─────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task AssignFile_Exists_Returns200WithUpdatedBookId()
|
|
{
|
|
var bookId = await SeedBookAsync();
|
|
var fileId = await SeedFileAsync(filename: "orphan.epub", bookId: null);
|
|
|
|
var response = await _client.PatchAsJsonAsync($"/api/files/{fileId}", new { bookId });
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
var file = await response.Content.ReadFromJsonAsync<BookFileDto>();
|
|
file!.BookId.Should().Be(bookId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AssignFile_NotFound_Returns404()
|
|
{
|
|
var response = await _client.PatchAsJsonAsync("/api/files/99999", new { bookId = 1 });
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AssignFile_ClearsBookId_WhenSetToNull()
|
|
{
|
|
var bookId = await SeedBookAsync();
|
|
var fileId = await SeedFileAsync(filename: "book.epub", bookId: bookId);
|
|
|
|
var response = await _client.PatchAsJsonAsync($"/api/files/{fileId}", new { bookId = (int?)null });
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
var file = await response.Content.ReadFromJsonAsync<BookFileDto>();
|
|
file!.BookId.Should().BeNull();
|
|
}
|
|
|
|
// ── DELETE /api/files/{id} ────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task DeleteFile_Exists_Returns204AndRemovesRecord()
|
|
{
|
|
var fileId = await SeedFileAsync();
|
|
|
|
var response = await _client.DeleteAsync($"/api/files/{fileId}");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
|
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
var exists = await db.BookFiles.AnyAsync(f => f.Id == fileId);
|
|
exists.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteFile_NotFound_Returns404()
|
|
{
|
|
var response = await _client.DeleteAsync("/api/files/99999");
|
|
|
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
private async Task<int> SeedBookAsync(string title = "Test Book")
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
var book = new Book { Title = title, Formats = [], Genres = [] };
|
|
db.Books.Add(book);
|
|
await db.SaveChangesAsync();
|
|
return book.Id;
|
|
}
|
|
|
|
private async Task<int> SeedFileAsync(
|
|
string filename = "test.epub",
|
|
FileFormat format = FileFormat.Epub,
|
|
int? bookId = null,
|
|
long sizeBytes = 1024)
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
var file = new BookFile
|
|
{
|
|
Filename = filename,
|
|
Path = filename,
|
|
Format = format,
|
|
SizeBytes = sizeBytes,
|
|
BookId = bookId,
|
|
AddedAt = DateTime.UtcNow,
|
|
};
|
|
db.BookFiles.Add(file);
|
|
await db.SaveChangesAsync();
|
|
return file.Id;
|
|
}
|
|
}
|