Der geplante Film zeigte weiter „Abspielen" und „Herunterladen", und wer
drückte, bekam „Dieser Titel ist ab dem 6.10. um 18 Uhr verfügbar". Die
Sperre im Server war richtig; die Oberfläche wusste nur nichts davon.
Gemessen: die Liste führte `PublishAt` seit jeher mit, die Einzelabfrage
`GET /api/v1/movies/{id}` nicht — und die Detailseite entscheidet genau
daran. `publishAt` kam also immer als `null` an, die Prüfung lief ins
Leere. Dasselbe fehlte bei `GET /api/v1/series/{id}` und bei den Folgen.
Jetzt steht dort, wo sonst der Knopf stünde: „Bald ist es soweit", mit
dem Termin darunter. Kein Versprechen, das beim Klicken zurückgenommen
wird.
Der Test prüfte bisher nur die Liste — darum fiel es nicht auf. Er fragt
jetzt die Detailseite und schlägt ohne die Korrektur fehl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
238 lines
10 KiB
C#
238 lines
10 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using Flaro.Api.Endpoints;
|
|
|
|
namespace Flaro.Api.Tests;
|
|
|
|
/// <summary>Was auf Prüfung wartet, sieht niemand ausser Administratoren.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Die Route-Aufzählung unten ist der eigentliche Test.</b> Auf Filme und
|
|
/// Folgen wird an rund zwanzig Stellen zugegriffen — Mediathek, Suche,
|
|
/// Vorschläge, Weiterschauen, Ähnliches. Eine davon ohne Filter, und
|
|
/// unveröffentlichter Inhalt erscheint trotzdem; auffallen würde es nicht
|
|
/// beim Programmieren, sondern wenn jemand im Haushalt einen Titel sieht,
|
|
/// den er nicht sehen sollte.
|
|
/// </para>
|
|
/// <para>
|
|
/// Deshalb prüft dieser Test nicht eine Abfrage, sondern <b>jede Route, die
|
|
/// Inhalt ausliefert</b> — und zwar auf die Abwesenheit eines Titels. Eine
|
|
/// neue Route, die den Filter umgeht, fällt hier durch.
|
|
/// </para>
|
|
/// </remarks>
|
|
public class FreigabeTests
|
|
{
|
|
private string _bestand = "";
|
|
|
|
[SetUp]
|
|
public void Anlegen() => _bestand = Directory.CreateDirectory(
|
|
Path.Combine(Path.GetTempPath(), "flaro-freigabe-" + Guid.CreateVersion7().ToString("N"))).FullName;
|
|
|
|
[TearDown]
|
|
public void Aufraeumen()
|
|
{
|
|
try { Directory.Delete(_bestand, recursive: true); } catch (IOException) { }
|
|
}
|
|
|
|
private void Datei(string relativ)
|
|
{
|
|
var voll = Path.Combine(_bestand, relativ);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(voll)!);
|
|
File.WriteAllBytes(voll, new byte[2048]);
|
|
}
|
|
|
|
/// <summary>Jede Route, die Titel ausliefert.</summary>
|
|
private static readonly string[] Inhaltsrouten =
|
|
[
|
|
"/api/v1/movies",
|
|
"/api/v1/movies?search=Geheim",
|
|
"/api/v1/movies?sort=added",
|
|
"/api/v1/series",
|
|
"/api/v1/home/continue",
|
|
"/api/v1/home/next",
|
|
"/api/v1/home/suggestions",
|
|
];
|
|
|
|
[Test]
|
|
public async Task Ungepruefte_Titel_sieht_nur_der_Administrator()
|
|
{
|
|
Datei("Geheimsache (2024).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var admin = await server.SetUpAndSignIn();
|
|
var alsAdmin = server.AlsAngemeldeter(admin.Token);
|
|
|
|
var angelegt = await alsAdmin.PostAsJsonAsync("/api/v1/libraries",
|
|
new CreateLibraryRequest("Prüfbestand", "Movies", _bestand, ReviewNewItems: true));
|
|
angelegt.EnsureSuccessStatusCode();
|
|
var bibliothek = (await angelegt.Content.ReadFromJsonAsync<LibraryResponse>())!;
|
|
|
|
await Scannen(alsAdmin, bibliothek.Id);
|
|
|
|
// Der Administrator sieht ihn — sonst könnte er ihn nicht prüfen.
|
|
var wartend = await alsAdmin.GetFromJsonAsync<List<Pruefeintrag>>("/api/v1/review");
|
|
Assert.That(wartend, Has.Count.EqualTo(1));
|
|
Assert.That(wartend![0].Title, Is.EqualTo("Geheimsache"));
|
|
|
|
// Ein gewöhnlicher Benutzer nirgends.
|
|
var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswort");
|
|
|
|
foreach (var route in Inhaltsrouten)
|
|
{
|
|
var antwort = await gast.GetAsync(route);
|
|
Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.OK), route);
|
|
var text = await antwort.Content.ReadAsStringAsync();
|
|
Assert.That(text, Does.Not.Contain("Geheimsache"),
|
|
$"„Geheimsache“ steht in {route} — dort fehlt der Freigabefilter.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task Veroeffentlichen_macht_ihn_fuer_alle_sichtbar()
|
|
{
|
|
Datei("Geheimsache (2024).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var admin = await server.SetUpAndSignIn();
|
|
var alsAdmin = server.AlsAngemeldeter(admin.Token);
|
|
|
|
var angelegt = await alsAdmin.PostAsJsonAsync("/api/v1/libraries",
|
|
new CreateLibraryRequest("Prüfbestand", "Movies", _bestand, ReviewNewItems: true));
|
|
var bibliothek = (await angelegt.Content.ReadFromJsonAsync<LibraryResponse>())!;
|
|
await Scannen(alsAdmin, bibliothek.Id);
|
|
|
|
var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswort");
|
|
Assert.That(await Titel(gast), Is.Empty);
|
|
|
|
var frei = await alsAdmin.PostAsJsonAsync("/api/v1/review/publish",
|
|
new FreigabeRequest(null, null));
|
|
frei.EnsureSuccessStatusCode();
|
|
|
|
Assert.That(await Titel(gast), Is.EqualTo(new[] { "Geheimsache" }));
|
|
}
|
|
|
|
/// <summary>Ein geplanter Titel ist sichtbar, aber noch nicht abspielbar.</summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Zwei Zustände, nicht einer.</b> Ohne Zeitpunkt wartet ein Titel auf
|
|
/// eine Entscheidung — den soll niemand sehen. Mit einem Zeitpunkt in der
|
|
/// Zukunft ist die Entscheidung gefallen: er kommt, nur noch nicht. Ihn
|
|
/// dann zu verstecken hiesse, die Vorfreude zu unterschlagen, die der Sinn
|
|
/// des Planens ist.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Sichtbar heisst aber nicht abspielbar</b>, und das ist der Teil, der
|
|
/// wirklich zählt: ohne den Riegel bei der Auslieferung wäre „Verfügbar ab
|
|
/// …" eine Zierde, und die Datei ginge trotzdem heraus.
|
|
/// </para>
|
|
/// </remarks>
|
|
[Test]
|
|
public async Task Ein_geplanter_Titel_ist_sichtbar_aber_nicht_abspielbar()
|
|
{
|
|
Datei("Geheimsache (2024).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var admin = await server.SetUpAndSignIn();
|
|
var alsAdmin = server.AlsAngemeldeter(admin.Token);
|
|
|
|
var angelegt = await alsAdmin.PostAsJsonAsync("/api/v1/libraries",
|
|
new CreateLibraryRequest("Prüfbestand", "Movies", _bestand, ReviewNewItems: true));
|
|
var bibliothek = (await angelegt.Content.ReadFromJsonAsync<LibraryResponse>())!;
|
|
await Scannen(alsAdmin, bibliothek.Id);
|
|
|
|
var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswort");
|
|
|
|
// Auf morgen gelegt.
|
|
await alsAdmin.PostAsJsonAsync("/api/v1/review/publish",
|
|
new FreigabeRequest(null, DateTimeOffset.UtcNow.AddDays(1)));
|
|
|
|
Assert.That(await Titel(gast), Is.EqualTo(new[] { "Geheimsache" }),
|
|
"Ein geplanter Titel steht in der Mediathek — dafür plant man ihn.");
|
|
|
|
// **Aber die Datei geht nicht heraus.** Das ist der Teil, der zählt.
|
|
var filme = await gast.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
|
|
var datei = filme!.Items.Single().Files.Single();
|
|
|
|
var geladen = await gast.GetAsync($"/api/v1/files/{datei.Id}/download");
|
|
var gestroemt = await gast.PostAsJsonAsync($"/api/v1/files/{datei.Id}/stream",
|
|
new StreamRequest(["mp4"], ["h264"], ["aac"]));
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(geladen.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden));
|
|
Assert.That(gestroemt.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden));
|
|
});
|
|
|
|
// **Und die Detailseite muss es wissen.** Sie entscheidet daran, ob
|
|
// sie „Abspielen" zeigt oder den Termin. Die Liste führte `PublishAt`
|
|
// von Anfang an mit, die Einzelabfrage nicht — der Knopf stand also
|
|
// da, und wer ihn drückte, bekam eine Absage. Ein Knopf, der nichts
|
|
// tut, ist schlimmer als keiner.
|
|
var einzeln = await gast.GetFromJsonAsync<MovieResponse>(
|
|
$"/api/v1/movies/{filme.Items.Single().Id}");
|
|
Assert.That(einzeln!.PublishAt, Is.Not.Null,
|
|
"die Detailseite braucht den Termin, sonst zeigt sie Abspielen an");
|
|
|
|
// **Auch für Administratoren.** Eine Hintertür für eine Rolle wäre
|
|
// eine Regel, die manchmal gilt; wer vorziehen will, hat den Knopf.
|
|
var alsAdminGeladen = await alsAdmin.GetAsync($"/api/v1/files/{datei.Id}/download");
|
|
Assert.That(alsAdminGeladen.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden));
|
|
|
|
// Und er steht weiterhin in der Prüfliste — mit seinem Termin.
|
|
var wartend = await alsAdmin.GetFromJsonAsync<List<Pruefeintrag>>("/api/v1/review");
|
|
Assert.That(wartend, Has.Count.EqualTo(1));
|
|
Assert.That(wartend![0].PublishAt, Is.Not.Null);
|
|
|
|
// Auf gestern gelegt: sofort da, ohne dass irgendetwas umgeschaltet wurde.
|
|
await alsAdmin.PostAsJsonAsync("/api/v1/review/publish",
|
|
new FreigabeRequest(null, DateTimeOffset.UtcNow.AddDays(-1)));
|
|
Assert.That(await Titel(gast), Is.EqualTo(new[] { "Geheimsache" }));
|
|
|
|
var jetztGeladen = await gast.GetAsync($"/api/v1/files/{datei.Id}/download");
|
|
Assert.That(jetztGeladen.StatusCode, Is.Not.EqualTo(HttpStatusCode.Forbidden),
|
|
"ab dem Termin geht die Datei heraus");
|
|
}
|
|
|
|
/// <summary>Ohne Prüfung erscheint alles sofort — das ist die Vorgabe.</summary>
|
|
[Test]
|
|
public async Task Ohne_Pruefung_erscheint_alles_sofort()
|
|
{
|
|
Datei("Offen (2024).mkv");
|
|
|
|
using var server = new FlaroServer();
|
|
var admin = await server.SetUpAndSignIn();
|
|
var alsAdmin = server.AlsAngemeldeter(admin.Token);
|
|
|
|
var angelegt = await alsAdmin.PostAsJsonAsync("/api/v1/libraries",
|
|
new CreateLibraryRequest("Prüfbestand", "Movies", _bestand));
|
|
var bibliothek = (await angelegt.Content.ReadFromJsonAsync<LibraryResponse>())!;
|
|
await Scannen(alsAdmin, bibliothek.Id);
|
|
|
|
var gast = await server.NeuerBenutzer(alsAdmin, "gast", "gastpasswort");
|
|
Assert.That(await Titel(gast), Is.EqualTo(new[] { "Offen" }));
|
|
Assert.That(await alsAdmin.GetFromJsonAsync<List<Pruefeintrag>>("/api/v1/review"), Is.Empty);
|
|
}
|
|
|
|
private static async Task<string[]> Titel(HttpClient client) =>
|
|
(await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies"))!
|
|
.Items.Select(m => m.Title).ToArray();
|
|
|
|
private static async Task Scannen(HttpClient client, Guid bibliothek)
|
|
{
|
|
(await client.PostAsync($"/api/v1/libraries/{bibliothek}/scan", null))
|
|
.EnsureSuccessStatusCode();
|
|
|
|
for (var versuch = 0; versuch < 600; versuch++)
|
|
{
|
|
var stand = await client.GetFromJsonAsync<ScanStatus>($"/api/v1/libraries/{bibliothek}/scan");
|
|
if (stand!.State is "finished") return;
|
|
if (stand.State is "failed") Assert.Fail($"Der Scan ist gescheitert: {stand.Error}");
|
|
await Task.Delay(50);
|
|
}
|
|
|
|
Assert.Fail("Der Scan wurde nicht fertig.");
|
|
}
|
|
}
|