using Flaro.Core.Metadata;
using Microsoft.Extensions.Logging.Abstractions;
namespace Flaro.Metadata.Tests;
/// Die beiden Werte von Rotten Tomatoes, von der Seite selbst gelesen.
///
///
/// Gegen einen festgehaltenen Seitenausschnitt, nicht gegen das Netz.
/// Eine Prüfung, die wirklich hinausgeht, misst das Netz und nicht den Code;
/// sie wäre launisch und fiele aus, sobald jemand ohne Verbindung arbeitet.
/// Der Ausschnitt unten ist gekürzt, aber in der Form, die
/// /m/the_matrix am 9. Oktober 2026 geliefert hat.
///
public class RottenTomatoesTests
{
private static string Seite(
string art = "Movie", string name = "The Matrix", string datum = "1999-03-31",
string kritik = "83", string publikum = "85") =>
$$$"""
""";
private static RottenTomatoesClient Client(Attrappe attrappe, bool an = true) =>
new(new HttpClient(attrappe) { BaseAddress = new Uri(RottenTomatoesClient.BaseAddress) },
new FesterTomatenschalter(an),
// Ohne Pause: die Bremse ist eine Höflichkeit gegenüber der
// echten Seite, und eine Prüfung geht nirgends hin.
new Anklopfbremse(TimeSpan.Zero),
NullLogger.Instance);
private static Wertungsfrage Matrix => new("tt0133093", "The Matrix", 1999);
/// Ausgeschaltet heisst: keine einzige Anfrage.
///
///
/// Das ist der wichtigste Satz dieser Datei. Hier wird eine fremde
/// Webseite ausgelesen; das darf nicht beiläufig geschehen, weil jemand
/// einen Scan startet.
///
[Test]
public async Task Ausgeschaltet_geht_keine_Anfrage_hinaus()
{
var attrappe = Attrappe.MitJson(Seite());
var wertungen = await Client(attrappe, an: false).HolenAsync(Matrix);
Assert.Multiple(() =>
{
Assert.That(wertungen, Is.Empty);
Assert.That(attrappe.Aufrufe, Is.Empty);
});
}
/// Kritik und Publikum kommen getrennt zurück.
[Test]
public async Task Beide_Werte_kommen_getrennt()
{
var wertungen = await Client(Attrappe.MitJson(Seite())).HolenAsync(Matrix);
Assert.Multiple(() =>
{
var kritik = wertungen.Single(w => w.Quelle == "rottenTomatoes");
Assert.That(kritik.Anzeige, Is.EqualTo("83%"));
Assert.That(kritik.Wert, Is.EqualTo(8.3).Within(0.01));
var publikum = wertungen.Single(w => w.Quelle == "rottenTomatoesAudience");
Assert.That(publikum.Anzeige, Is.EqualTo("85%"));
Assert.That(publikum.Wert, Is.EqualTo(8.5).Within(0.01));
});
}
/// Der gewöhnliche Titel kostet genau eine Anfrage.
///
///
/// Gemessen an der echten Seite: /m/the_matrix antwortet,
/// /m/the_matrix_1999 mit 404. Mit Jahr zuerst zu fragen kostete
/// bei jedem gewöhnlichen Titel eine Anfrage zu viel — auf einer Seite,
/// die man ohnehin nur mitbenutzt.
///
[Test]
public async Task Ein_gewoehnlicher_Titel_kostet_eine_Anfrage()
{
var attrappe = Attrappe.MitJson(Seite());
await Client(attrappe).HolenAsync(Matrix);
Assert.Multiple(() =>
{
Assert.That(attrappe.Aufrufe, Has.Count.EqualTo(1));
Assert.That(attrappe.Aufrufe[0], Does.EndWith("m/the_matrix"));
});
}
/// Ein Film mit anderem Jahr ist ein anderer Film.
///
///
/// Auf der Seite steht keine IMDb-Kennung. Der Weg wird aus dem
/// Titel geraten, und ein geratener Weg kann überall hinführen. Lieber
/// keine Zahl als die eines fremden Films.
///
[Test]
public async Task Ein_fremdes_Jahr_wird_verworfen()
{
var attrappe = Attrappe.MitJson(Seite(name: "The Lion King", datum: "1994-06-24"));
var wertungen = await Client(attrappe)
.HolenAsync(new Wertungsfrage(null, "The Lion King", 2019));
Assert.That(wertungen, Is.Empty);
}
/// Beim falschen Jahr wird der Weg mit Jahr nachgereicht.
///
///
/// Die Neuverfilmung ist der einzige Fall, der zwei Anfragen
/// rechtfertigt. /m/the_lion_king ist die von 1994,
/// /m/the_lion_king_2019 die neue. Erst wird die alte am Jahr
/// erkannt und verworfen, dann greift die zweite.
///
[Test]
public async Task Eine_Neuverfilmung_wird_ueber_den_Weg_mit_Jahr_gefunden()
{
var attrappe = Attrappe.NachPfad(
("the_lion_king_2019", Seite(name: "The Lion King (2019)", datum: "2019-07-19",
kritik: "52", publikum: "88")),
("the_lion_king", Seite(name: "The Lion King", datum: "1994-06-24",
kritik: "93", publikum: "93")));
var wertungen = await Client(attrappe)
.HolenAsync(new Wertungsfrage(null, "The Lion King", 2019));
Assert.Multiple(() =>
{
Assert.That(attrappe.Aufrufe, Has.Count.EqualTo(2));
Assert.That(wertungen.Single(w => w.Quelle == "rottenTomatoes").Anzeige,
Is.EqualTo("52%"), "die Fassung von 2019, nicht die von 1994");
Assert.That(wertungen.Single(w => w.Quelle == "rottenTomatoesAudience").Anzeige,
Is.EqualTo("88%"));
});
}
/// Ein Jahr daneben ist kein anderer Film.
///
///
/// Rotten Tomatoes führt das Datum der amerikanischen Erstaufführung,
/// TMDB oft das der Heimatpremiere — ein Film vom Dezember steht dort
/// schon mal im Folgejahr.
///
[Test]
public async Task Ein_Jahr_Unterschied_zaehlt_noch_als_derselbe()
{
var wertungen = await Client(Attrappe.MitJson(Seite(datum: "2000-01-20")))
.HolenAsync(Matrix);
Assert.That(wertungen, Has.Count.EqualTo(2));
}
/// Eine Serie darf nicht auf einer Filmseite landen.
[Test]
public async Task Eine_Filmseite_beantwortet_keine_Seriensuche()
{
var wertungen = await Client(Attrappe.MitJson(Seite(art: "Movie", name: "Dark")))
.HolenAsync(new Wertungsfrage(null, "Dark", 1999, Serie: true));
Assert.That(wertungen, Is.Empty);
}
[Test]
public async Task Eine_Serie_wird_unter_tv_gesucht()
{
var attrappe = Attrappe.MitJson(
Seite(art: "TVSeries", name: "Dark", datum: "2017-12-01"));
var wertungen = await Client(attrappe)
.HolenAsync(new Wertungsfrage(null, "Dark", 2017, Serie: true));
Assert.Multiple(() =>
{
Assert.That(attrappe.Aufrufe[0], Does.EndWith("tv/dark"),
"Serien stehen dort ohne Jahr: /tv/dark_2017 antwortet mit 404.");
Assert.That(wertungen, Has.Count.EqualTo(2));
});
}
/// Ein unbekannter Weg endet mit 404 — und das ist eine Antwort.
[Test]
public async Task Ein_unbekannter_Weg_ergibt_nichts()
{
var wertungen = await Client(Attrappe.MitStatus(System.Net.HttpStatusCode.NotFound))
.HolenAsync(Matrix);
Assert.That(wertungen, Is.Empty);
}
/// Eine Sperrseite ohne die bekannten Felder ergibt nichts, keinen Absturz.
[Test]
public async Task Eine_umgebaute_Seite_ergibt_nichts()
{
var wertungen = await Client(Attrappe.MitJson("Nichts hier."))
.HolenAsync(Matrix);
Assert.That(wertungen, Is.Empty);
}
[Test]
public async Task Kein_Netz_ergibt_nichts_statt_eines_Absturzes()
{
var wertungen = await Client(Attrappe.DieWirft(new HttpRequestException("weg")))
.HolenAsync(Matrix);
Assert.That(wertungen, Is.Empty);
}
/// Der Originaltitel wird zuerst versucht.
///
///
/// Sonst fände ein deutscher Bestand dort so gut wie nichts. Steht
/// die Mediathek auf Deutsch, heisst der Film bei uns „Ich einfach
/// unverbesserlich"; Rotten Tomatoes kennt nur despicable_me.
///
[Test]
public async Task Der_Originaltitel_wird_zuerst_versucht()
{
var attrappe = Attrappe.NachPfad(
("despicable_me", Seite(name: "Despicable Me", datum: "2010-07-09",
kritik: "81", publikum: "84")));
var wertungen = await Client(attrappe).HolenAsync(new Wertungsfrage(
null, "Ich einfach unverbesserlich", 2010, Originaltitel: "Despicable Me"));
Assert.Multiple(() =>
{
Assert.That(attrappe.Aufrufe[0], Does.EndWith("m/despicable_me"));
Assert.That(wertungen.Single(w => w.Quelle == "rottenTomatoesAudience").Anzeige,
Is.EqualTo("84%"));
});
}
/// Gibt es den Originaltitel dort nicht, kommt der deutsche dran.
[Test]
public async Task Ohne_Treffer_wird_auch_der_andere_Titel_versucht()
{
var attrappe = Attrappe.NachPfad(
("ich_einfach_unverbesserlich",
Seite(name: "Ich einfach unverbesserlich", datum: "2010-07-09")));
var wertungen = await Client(attrappe).HolenAsync(new Wertungsfrage(
null, "Ich einfach unverbesserlich", 2010, Originaltitel: "Despicable Me"));
Assert.Multiple(() =>
{
Assert.That(attrappe.Aufrufe[0], Does.Contain("despicable_me"), "der originale zuerst");
Assert.That(wertungen, Has.Count.EqualTo(2));
});
}
/// Derselbe Titel zweimal ergibt nicht zwei Anfragen.
[Test]
public async Task Ein_gleicher_Originaltitel_kostet_nichts_extra()
{
var attrappe = Attrappe.MitJson(Seite());
await Client(attrappe).HolenAsync(
new Wertungsfrage(null, "The Matrix", 1999, Originaltitel: "The Matrix"));
Assert.That(attrappe.Aufrufe, Has.Count.EqualTo(1));
}
/// Einmal wirklich hinaus — nur von Hand.
///
///
///
/// [Explicit], und das ist der Punkt. Diese Prüfung misst
/// das Netz und eine fremde Seite, nicht den Code; im Durchlauf wäre sie
/// launisch und fiele ohne Verbindung aus. Von Hand ist sie dagegen das
/// einzige, was die Frage beantwortet, ob das Auslesen heute noch geht:
///
///
/// dotnet test --filter "FullyQualifiedName~Wirklich_von_der_echten_Seite"
///
///
[Test, Explicit("Geht wirklich ins Netz.")]
public async Task Wirklich_von_der_echten_Seite_gelesen()
{
using var echt = new HttpClient { BaseAddress = new Uri(RottenTomatoesClient.BaseAddress) };
echt.DefaultRequestHeaders.UserAgent.ParseAdd(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/131.0.0.0 Safari/537.36");
var client = new RottenTomatoesClient(
echt, new FesterTomatenschalter(true), new Anklopfbremse(TimeSpan.Zero),
NullLogger.Instance);
var wertungen = await client.HolenAsync(Matrix);
Assert.Multiple(() =>
{
Assert.That(wertungen.Select(w => w.Quelle),
Is.EquivalentTo(new[] { "rottenTomatoes", "rottenTomatoesAudience" }));
TestContext.Out.WriteLine(string.Join(", ",
wertungen.Select(w => $"{w.Quelle} {w.Anzeige}")));
});
}
/// So baut Rotten Tomatoes seine Wege.
[TestCase("The Matrix", ExpectedResult = "the_matrix")]
[TestCase("Spider-Man: No Way Home", ExpectedResult = "spider_man_no_way_home")]
[TestCase("WALL·E", ExpectedResult = "wall_e")]
[TestCase("Ich einfach unverbesserlich", ExpectedResult = "ich_einfach_unverbesserlich")]
[TestCase("Österreich – Grüße", ExpectedResult = "osterreich_grusse")]
[TestCase(" ", ExpectedResult = "")]
public string Aus_dem_Titel_wird_ein_Weg(string titel) =>
RottenTomatoesClient.Kennstueck(titel);
}
public sealed record FesterTomatenschalter(bool An) : IRottenTomatoesSettingsSource
{
public Task EingeschaltetAsync(CancellationToken ct = default) => Task.FromResult(An);
}