Der Stand lag daneben und wurde trotzdem weggeworfen

Der Fortschrittsbalken ist an **einer** Stelle gebaut und wird in jeder
Kachel verwendet — Web wie App. Fehlte er, dann nicht an der Kachel,
sondern weil der Server `State: null` schickte. Drei Listen taten das,
obwohl die Stände in derselben Abfrage danebenlagen:

  /home/suggestions  — „Weil du Ähnliches gesehen hast"
  /home/seasonal     — die saisonale Reihe
  /movies/{id}/similar — „Ähnliche Titel"

Ausgerechnet die erste entsteht aus dem Sehverhalten: ein halb gesehener
Film sah dort aus wie ein unangetasteter, während dieselbe Kachel in der
Mediathek einen Balken trug. `ZuständeZu` gab es längst, es war nur
privat.

**Und „Als Nächstes" lässt sich jetzt räumen.** Das vorhandene Weglegen
reichte nicht dorthin: die Reihe liest `Watched` und `InProgress`, nie
`Dismissed` — und bei einer nie gestarteten Folge ist `InProgress` ohnehin
`false`. Weggelegt wird deshalb die **Serie**, nicht die Folge: beim
nächsten Laden rückte sonst die übernächste nach, und man legte
zwanzigmal weg. Den Zustand dafür gibt es schon, er wurde nur nie
gelesen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-12 19:13:09 +02:00
co-authored by Claude Opus 5
parent fa8d1a97e2
commit fb8ad1c831
5 changed files with 163 additions and 8 deletions
@@ -182,4 +182,102 @@ public sealed class FortschrittTests
Assert.That(danach.RuntimeSeconds, Is.EqualTo(1000).Within(0.001));
}
// --- Wo der Stand bisher weggeworfen wurde ---
/// <summary>Auch ein Vorschlag bringt seinen Stand mit.</summary>
///
/// <remarks>
/// <b>Drei Listen gaben <c>State: null</c> zurück</b> — Vorschläge, die
/// saisonale Reihe und die ähnlichen Titel —, obwohl die Stände in
/// derselben Datenbank liegen. Ein halb gesehener Film sah dort aus wie
/// ein unangetasteter, während dieselbe Kachel in der Mediathek einen
/// Balken trug. Die Kachel wird an einer Stelle gebaut; sie kann nur
/// zeichnen, was ankommt.
///
/// Geprüft wird hier die Vorschlagsreihe: sie entsteht aus dem
/// Sehverhalten, also genügt ein angefangener Film, damit sie überhaupt
/// etwas enthält — und ausgerechnet dieser Film steht dann darin.
/// </remarks>
[Test]
public async Task Ein_Vorschlag_bringt_seinen_Stand_mit()
{
using var server = new FlaroServer();
var (client, film) = await MitEinemFilm(server);
await Melden(client, film, 300, 600);
var seite = await client.GetFromJsonAsync<Page<MovieResponse>>(
"/api/v1/home/suggestions");
var eintrag = seite!.Items.FirstOrDefault(x => x.Id == film);
Assume.That(eintrag, Is.Not.Null, "ohne Genres schlägt der Server nichts vor");
Assert.Multiple(() =>
{
Assert.That(eintrag!.State, Is.Not.Null, "ohne Stand kein Balken");
Assert.That(eintrag.State!.PositionSeconds, Is.EqualTo(300).Within(0.001));
});
}
/// <summary>Eine weggelegte Serie steht nicht mehr in „Als Nächstes".</summary>
///
/// <remarks>
/// <b>Weggelegt gilt für die Serie, nicht für die Folge.</b> Eine
/// einzelne Folge dort wegzulegen nützte nichts: beim nächsten Laden
/// rückte die übernächste nach, und man legte zwanzigmal weg. Gemeint
/// ist „diese Serie nicht mehr vorschlagen" — und genau dieser Zustand
/// wurde in der Reihe nie gelesen.
/// </remarks>
[Test]
public async Task Eine_weggelegte_Serie_verschwindet_aus_Als_Naechstes()
{
using var server = new FlaroServer();
foreach (var name in new[] { "S01E01", "S01E02" })
{
var voll = Path.Combine(_bestand, "Probe (2026)", "Season 01", $"Probe - {name}.mkv");
Directory.CreateDirectory(Path.GetDirectoryName(voll)!);
await File.WriteAllBytesAsync(voll, new byte[2048]);
}
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
var angelegt = await client.PostAsJsonAsync("/api/v1/libraries",
new CreateLibraryRequest("Prüfbestand", "Shows", _bestand));
angelegt.EnsureSuccessStatusCode();
var bibliothek = (await angelegt.Content.ReadFromJsonAsync<LibraryResponse>())!;
(await client.PostAsync($"/api/v1/libraries/{bibliothek.Id}/scan", null)).EnsureSuccessStatusCode();
for (var versuch = 0; versuch < 100; versuch++)
{
var stand = await client.GetFromJsonAsync<ScanStatus>($"/api/v1/libraries/{bibliothek.Id}/scan");
if (stand!.State is "finished") break;
if (stand.State is "failed") Assert.Fail($"Der Scan ist gescheitert: {stand.Error}");
await Task.Delay(50);
}
var serien = await client.GetFromJsonAsync<Page<SeriesResponse>>("/api/v1/series");
Assume.That(serien!.Items, Is.Not.Empty, "der Scan hat keine Serie gefunden");
var serie = serien.Items[0].Id;
var folgen = await client.GetFromJsonAsync<Page<EpisodeResponse>>(
$"/api/v1/series/{serie}/episodes");
// Erst die erste Folge abhaken — sonst hat der Benutzer die Serie nie
// angefasst, und sie stünde ohnehin nicht in der Reihe.
(await client.PutAsJsonAsync($"/api/v1/items/{folgen!.Items[0].Id}/watched",
new WatchedRequest(true))).EnsureSuccessStatusCode();
var vorher = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/next");
Assume.That(vorher!.Items.Select(x => x.SeriesId), Does.Contain(serie),
"ohne Eintrag gibt es nichts wegzulegen");
(await client.PostAsync($"/api/v1/items/{serie}/dismiss", null)).EnsureSuccessStatusCode();
var nachher = await client.GetFromJsonAsync<Page<ContinueEntry>>("/api/v1/home/next");
Assert.That(nachher!.Items.Select(x => x.SeriesId), Does.Not.Contain(serie));
}
}