Die alte Grenze war für zwei, drei Dateien bemessen. Der neue Test über 120 Dateien schaffte sie allein, fiel aber durch, sobald die ganze Suite nebenher lief — kein Fehler im Scan, sondern zu knappe Geduld. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
351 lines
14 KiB
C#
351 lines
14 KiB
C#
using System.Net;
|
||
using System.Net.Http.Json;
|
||
using Flaro.Api.Endpoints;
|
||
|
||
namespace Flaro.Api.Tests;
|
||
|
||
/// <summary>Scan gegen einen echten Ordner mit echten Dateien.</summary>
|
||
public class ScanTests
|
||
{
|
||
private string _bestand = "";
|
||
|
||
[SetUp]
|
||
public void Anlegen() => _bestand = Directory.CreateDirectory(
|
||
Path.Combine(Path.GetTempPath(), "flaro-medien-" + Guid.CreateVersion7().ToString("N"))).FullName;
|
||
|
||
[TearDown]
|
||
public void Aufraeumen()
|
||
{
|
||
try { Directory.Delete(_bestand, recursive: true); } catch (IOException) { }
|
||
}
|
||
|
||
private void Datei(string relativ, int bytes = 2048)
|
||
{
|
||
var voll = Path.Combine(_bestand, relativ);
|
||
Directory.CreateDirectory(Path.GetDirectoryName(voll)!);
|
||
File.WriteAllBytes(voll, new byte[bytes]);
|
||
}
|
||
|
||
private async Task<(HttpClient Admin, Guid LibraryId)> MitBibliothek(FlaroServer server, string art)
|
||
{
|
||
var admin = await server.SetUpAndSignIn();
|
||
var client = server.AlsAngemeldeter(admin.Token);
|
||
var angelegt = await client.PostAsJsonAsync("/api/v1/libraries",
|
||
new CreateLibraryRequest("Prüfbestand", art, _bestand));
|
||
angelegt.EnsureSuccessStatusCode();
|
||
var bibliothek = (await angelegt.Content.ReadFromJsonAsync<LibraryResponse>())!;
|
||
return (client, bibliothek.Id);
|
||
}
|
||
|
||
/// <summary>Stösst an und wartet, bis der Hintergrundlauf fertig ist.</summary>
|
||
private static async Task<ScanStatus> Scannen(HttpClient client, Guid bibliothek)
|
||
{
|
||
var angestossen = await client.PostAsync($"/api/v1/libraries/{bibliothek}/scan", null);
|
||
Assert.That(angestossen.StatusCode, Is.EqualTo(HttpStatusCode.Accepted));
|
||
|
||
// **Dreissig Sekunden, nicht fünf.** Die alte Grenze war für zwei,
|
||
// drei Dateien bemessen. Der Test über 120 Dateien schaffte sie
|
||
// allein, fiel aber durch, sobald die ganze Suite nebenher lief —
|
||
// und das war kein Fehler im Scan, sondern eine zu knappe Geduld.
|
||
// Teurer wird es nur, wenn wirklich etwas hängt.
|
||
const int schritte = 600, abstand = 50;
|
||
|
||
for (var versuch = 0; versuch < schritte; versuch++)
|
||
{
|
||
var stand = await client.GetFromJsonAsync<ScanStatus>($"/api/v1/libraries/{bibliothek}/scan");
|
||
if (stand!.State is "finished") return stand;
|
||
if (stand.State is "failed") Assert.Fail($"Der Scan ist gescheitert: {stand.Error}");
|
||
await Task.Delay(abstand);
|
||
}
|
||
Assert.Fail($"Der Scan wurde in {schritte * abstand / 1000} Sekunden nicht fertig.");
|
||
return null!;
|
||
}
|
||
|
||
[Test]
|
||
public async Task Filme_werden_gefunden_und_benannt()
|
||
{
|
||
Datei("The.Matrix.1999.1080p.BluRay.x264-GRP.mkv");
|
||
Datei("Der Untergang (2004).mkv");
|
||
Datei("The Matrix (1999).srt"); // zählt nicht
|
||
Datei("@eaDir/The.Matrix.1999.mkv"); // zählt auch nicht
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
|
||
var stand = await Scannen(client, bibliothek);
|
||
Assert.That(stand.FilesFound, Is.EqualTo(2));
|
||
Assert.That(stand.Added, Is.EqualTo(2));
|
||
|
||
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
||
Assert.That(filme!.Total, Is.EqualTo(2));
|
||
// Sortiert ohne Artikel: „Matrix" vor „Untergang".
|
||
Assert.That(filme.Items.Select(m => m.Title),
|
||
Is.EqualTo(new[] { "The Matrix", "Der Untergang" }));
|
||
Assert.That(filme.Items[0].Year, Is.EqualTo(1999));
|
||
}
|
||
|
||
/// <summary>Zweimal scannen muss dasselbe ergeben wie einmal.</summary>
|
||
///
|
||
/// <remarks>
|
||
/// <b>Sonst wächst die Mediathek bei jedem Lauf.</b> Das ist der Fehler,
|
||
/// den man erst nach Wochen bemerkt — wenn jeder Film fünfmal dasteht.
|
||
/// </remarks>
|
||
[Test]
|
||
public async Task Ein_zweiter_Lauf_aendert_nichts()
|
||
{
|
||
Datei("The Matrix (1999).mkv");
|
||
Datei("Inception (2010).mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
|
||
await Scannen(client, bibliothek);
|
||
var zweiter = await Scannen(client, bibliothek);
|
||
|
||
Assert.That(zweiter.Added, Is.EqualTo(0));
|
||
Assert.That(zweiter.Removed, Is.EqualTo(0));
|
||
Assert.That((await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies"))!.Total, Is.EqualTo(2));
|
||
}
|
||
|
||
[Test]
|
||
public async Task Eine_geloeschte_Datei_verschwindet_aus_der_Mediathek()
|
||
{
|
||
Datei("The Matrix (1999).mkv");
|
||
Datei("Inception (2010).mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
File.Delete(Path.Combine(_bestand, "Inception (2010).mkv"));
|
||
var danach = await Scannen(client, bibliothek);
|
||
|
||
Assert.That(danach.Removed, Is.EqualTo(1));
|
||
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
||
Assert.That(filme!.Items.Select(m => m.Title), Is.EqualTo(new[] { "The Matrix" }));
|
||
}
|
||
|
||
/// <summary>Das Aufräumen darf nicht wegräumen, was derselbe Lauf schrieb.</summary>
|
||
///
|
||
/// <remarks>
|
||
/// <b>Mehr als fünfzig Dateien, und das ist der Punkt.</b> Seit der Scan
|
||
/// alle fünfzig Dateien zwischenspeichert, liegt ein Teil des Bestands
|
||
/// schon in der Datenbank, während der Lauf noch läuft. Direkt danach
|
||
/// löscht er alles, was eine ältere <c>SeenAt</c>-Zeit trägt als dieser
|
||
/// Lauf — würde die Zeit irgendwo neu genommen statt vor der Schleife
|
||
/// festgehalten, risse dieser Schritt den halben Bestand mit.
|
||
///
|
||
/// Die Dateien hier sind Nullbytes, an denen ffprobe scheitert. Damit
|
||
/// greift das Zwischenspeichern aus der Schleife und nicht das aus der
|
||
/// Untersuchung — also genau der Weg, der sonst ungeprüft bliebe.
|
||
/// </remarks>
|
||
[Test]
|
||
public async Task Ein_grosser_Bestand_ueberlebt_das_Aufraeumen()
|
||
{
|
||
for (var i = 1; i <= 120; i++) Datei($"Prüffilm {i:000} (2024).mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
|
||
var erster = await Scannen(client, bibliothek);
|
||
|
||
Assert.That(erster.Added, Is.EqualTo(120));
|
||
Assert.That(erster.Removed, Is.Zero, "Ein erster Lauf darf nichts entfernen.");
|
||
Assert.That((await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies"))!.Total,
|
||
Is.EqualTo(120));
|
||
|
||
// Und ein zweiter Lauf bleibt ebenso vollständig.
|
||
var zweiter = await Scannen(client, bibliothek);
|
||
Assert.That(zweiter.Removed, Is.Zero);
|
||
Assert.That((await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies"))!.Total,
|
||
Is.EqualTo(120));
|
||
}
|
||
|
||
/// <summary>Schreibvarianten desselben Titels ergeben einen Eintrag.</summary>
|
||
///
|
||
/// <remarks>
|
||
/// <b>Der Fall, der die Mediathek doppelt aussehen liess.</b> Der Scan
|
||
/// verglich den Titel aus dem Dateinamen zeichengenau — ein Punkt
|
||
/// Unterschied, und es wurden zwei Filme, die anschliessend beide
|
||
/// denselben TMDB-Eintrag bekamen und ununterscheidbar nebeneinander
|
||
/// standen.
|
||
/// </remarks>
|
||
[Test]
|
||
public async Task Schreibvarianten_ergeben_einen_Film()
|
||
{
|
||
Datei("Dr. Dolittle 2 (2001).mkv");
|
||
Datei("Dr Dolittle 2.mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
||
Assert.That(filme!.Total, Is.EqualTo(1));
|
||
Assert.That(filme.Items[0].Files, Has.Count.EqualTo(2));
|
||
Assert.That(filme.Items[0].Year, Is.EqualTo(2001),
|
||
"Das Jahr der einen Datei gilt auch, wenn die andere keines trägt.");
|
||
}
|
||
|
||
/// <summary>Eine mobile Fassung ist kein eigener Film.</summary>
|
||
[Test]
|
||
public async Task Eine_optimierte_Fassung_bleibt_beim_Film()
|
||
{
|
||
Datei("The Matrix (1999).mkv");
|
||
Datei("The Matrix (1999) - Mobil.mp4");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
||
Assert.That(filme!.Total, Is.EqualTo(1), "Sonst stünde ein zweiter Film ‚The Matrix - Mobil‘ daneben.");
|
||
Assert.That(filme.Items[0].Title, Is.EqualTo("The Matrix"));
|
||
Assert.That(filme.Items[0].Files, Has.Count.EqualTo(2));
|
||
}
|
||
|
||
/// <summary>Gleicher Name, anderes Jahr — zwei Filme, und das muss so bleiben.</summary>
|
||
[Test]
|
||
public async Task Zwei_Verfilmungen_bleiben_zwei_Filme()
|
||
{
|
||
Datei("Dune (1984).mkv");
|
||
Datei("Dune (2021).mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
||
Assert.That(filme!.Total, Is.EqualTo(2));
|
||
}
|
||
|
||
/// <summary>4K und 1080p sind ein Film mit zwei Dateien, nicht zwei Filme.</summary>
|
||
[Test]
|
||
public async Task Zwei_Fassungen_desselben_Films_bleiben_ein_Eintrag()
|
||
{
|
||
Datei("The Matrix (1999) - 1080p.mkv");
|
||
Datei("The Matrix (1999) - 2160p.mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
||
Assert.That(filme!.Total, Is.EqualTo(1));
|
||
Assert.That(filme.Items[0].Files, Has.Count.EqualTo(2));
|
||
}
|
||
|
||
[Test]
|
||
public async Task Serien_werden_in_Staffeln_und_Folgen_zerlegt()
|
||
{
|
||
Datei("Dark/Staffel 1/Dark - S01E01 - Geheimnisse.mkv");
|
||
Datei("Dark/Staffel 1/Dark - S01E02 - Lügen.mkv");
|
||
Datei("Dark/Staffel 2/Dark - S02E01 - Anfänge.mkv");
|
||
Datei("Notruf.Hafenkante.S01E01.German.1080p.mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Shows");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
|
||
Assert.That(serien!.Total, Is.EqualTo(2));
|
||
|
||
var dark = serien.Items.Single(s => s.Title == "Dark");
|
||
Assert.That(dark.EpisodeCount, Is.EqualTo(3));
|
||
Assert.That(dark.Seasons, Is.EqualTo(new[] { 1, 2 }));
|
||
|
||
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>($"/api/v1/series/{dark.Id}/episodes");
|
||
Assert.That(folgen!.Items.Select(f => f.Title),
|
||
Is.EqualTo(new[] { "Geheimnisse", "Lügen", "Anfänge" }));
|
||
|
||
var nurStaffel2 = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
|
||
$"/api/v1/series/{dark.Id}/episodes?season=2");
|
||
Assert.That(nurStaffel2!.Items, Has.Count.EqualTo(1));
|
||
}
|
||
|
||
/// <summary>Was keine Folgennummer trägt, wird nicht geraten.</summary>
|
||
[Test]
|
||
public async Task Dateien_ohne_Folgennummer_werden_gezaehlt_aber_nicht_zugeordnet()
|
||
{
|
||
Datei("Dark/Dark - S01E01.mkv");
|
||
Datei("Dark/Irgendein Mitschnitt.mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Shows");
|
||
var stand = await Scannen(client, bibliothek);
|
||
|
||
Assert.That(stand.FilesFound, Is.EqualTo(2));
|
||
Assert.That(stand.Unrecognised, Is.EqualTo(1));
|
||
Assert.That(stand.Added, Is.EqualTo(1));
|
||
}
|
||
|
||
[Test]
|
||
public async Task Die_Suche_grenzt_ein()
|
||
{
|
||
Datei("The Matrix (1999).mkv");
|
||
Datei("The Matrix Reloaded (2003).mkv");
|
||
Datei("Inception (2010).mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var treffer = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies?search=matrix");
|
||
Assert.That(treffer!.Total, Is.EqualTo(2));
|
||
}
|
||
|
||
/// <summary>Eine Anfrage darf sich die Seitengrösse nicht selbst aussuchen.</summary>
|
||
[Test]
|
||
public async Task Die_Seitengroesse_ist_gedeckelt()
|
||
{
|
||
for (var i = 1; i <= 5; i++) Datei($"Film {i} (200{i}).mkv");
|
||
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await Scannen(client, bibliothek);
|
||
|
||
var seite = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies?take=100000");
|
||
Assert.That(seite!.Total, Is.EqualTo(5), "Die Gesamtzahl bleibt richtig");
|
||
Assert.That(seite.Items, Has.Count.LessThanOrEqualTo(200));
|
||
|
||
var zweiteSeite = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies?skip=2&take=2");
|
||
Assert.That(zweiteSeite!.Items, Has.Count.EqualTo(2));
|
||
Assert.That(zweiteSeite.Total, Is.EqualTo(5));
|
||
}
|
||
|
||
[Test]
|
||
public async Task Vor_dem_ersten_Lauf_steht_der_Scan_auf_untaetig()
|
||
{
|
||
using var server = new FlaroServer();
|
||
var (client, bibliothek) = await MitBibliothek(server, "Movies");
|
||
|
||
var stand = await client.GetFromJsonAsync<ScanStatus>($"/api/v1/libraries/{bibliothek}/scan");
|
||
Assert.That(stand!.State, Is.EqualTo("idle"));
|
||
}
|
||
|
||
[Test]
|
||
public async Task Ein_gewoehnlicher_Benutzer_stoesst_keinen_Scan_an()
|
||
{
|
||
using var server = new FlaroServer();
|
||
var (admin, bibliothek) = await MitBibliothek(server, "Movies");
|
||
await admin.PostAsJsonAsync("/api/v1/users",
|
||
new CreateUserRequest("gast", "auch-ein-langes-passwort", false));
|
||
var gast = await server.SignIn("gast", "auch-ein-langes-passwort", "iPad", "g-9");
|
||
|
||
var versuch = await server.AlsAngemeldeter(gast.Token)
|
||
.PostAsync($"/api/v1/libraries/{bibliothek}/scan", null);
|
||
|
||
Assert.That(versuch.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden));
|
||
}
|
||
|
||
[Test]
|
||
public async Task Eine_Bibliothek_die_es_nicht_gibt_laesst_sich_nicht_scannen()
|
||
{
|
||
using var server = new FlaroServer();
|
||
var (client, _) = await MitBibliothek(server, "Movies");
|
||
|
||
var versuch = await client.PostAsync($"/api/v1/libraries/{Guid.CreateVersion7()}/scan", null);
|
||
Assert.That(versuch.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
|
||
}
|
||
}
|