using Flaro.Core.Delivery;
namespace Flaro.Core.Tests;
/// Welche umgepackten Dateien weggeräumt werden.
///
///
/// Der Anlass: 39 Gigabyte Transcode-Cache. Für jede Mitnahme baut der
/// Server eine MP4 in Quellgrösse und löschte sie nie. Hier steht die Regel,
/// die das beendet — ohne Platte, ohne Server, damit die Entscheidung selbst
/// nachlesbar ist und nicht nur ihr Ergebnis.
///
public class TranscoderegelTests
{
private static readonly DateTimeOffset Jetzt = new(2026, 10, 8, 12, 0, 0, TimeSpan.Zero);
private const int Bauart = 2;
private static readonly Guid EinsId = Guid.Parse("00000000-0000-0000-0000-000000000001");
private static readonly Guid ZweiId = Guid.Parse("00000000-0000-0000-0000-000000000002");
private const long Gigabyte = 1024L * 1024 * 1024;
/// Eine Datei, wie sie im Transcode-Cache läge.
private static Paketakte Akte(
Guid id, long gigabyte, TimeSpan vorWieLange, long? geaendertMs = null, int bauart = Bauart)
{
var ms = geaendertMs ?? 1_000;
var name = Paketname.Bauen(id, DateTimeOffset.FromUnixTimeMilliseconds(ms), bauart);
return new Paketakte($"/kratz/packages/{name}", gigabyte * Gigabyte,
Jetzt - vorWieLange, Paketname.Lesen(name));
}
/// Die Mediathek kennt beide Dateien in genau diesem Stand.
private static readonly Dictionary Bekannt =
new() { [EinsId] = 1_000, [ZweiId] = 1_000 };
private static IReadOnlyList Weg(
IReadOnlyList akten,
Transcoderegeln? regeln = null,
IReadOnlyDictionary? bekannt = null,
Func? inBenutzung = null) =>
Transcodeauswahl.Wegzuwerfen(
akten, bekannt ?? Bekannt, Bauart,
regeln ?? Transcoderegeln.Vorgabe, Jetzt,
inBenutzung ?? (_ => false))
.Select(a => a.Pfad).ToList();
/// Was nie wieder ausgeliefert wird, geht — auch wenn Platz wäre.
[Test]
public void Eine_Datei_ohne_Eintrag_in_der_Mediathek_geht_weg()
{
var akten = new[] { Akte(EinsId, 1, TimeSpan.FromHours(2)) };
Assert.That(Weg(akten, bekannt: new Dictionary()), Has.Count.EqualTo(1),
"der Titel ist aus der Mediathek verschwunden, das Paket ist wertlos");
}
[Test]
public void Ein_veralteter_Zeitstempel_im_Namen_gilt_als_verwaist()
{
var akten = new[] { Akte(EinsId, 1, TimeSpan.FromHours(2), geaendertMs: 999) };
Assert.That(Weg(akten), Has.Count.EqualTo(1),
"die Quelle wurde ersetzt; dieses Paket gehört zum alten Stand");
}
[Test]
public void Eine_andere_Bauart_gilt_als_verwaist()
{
var akten = new[] { Akte(EinsId, 1, TimeSpan.FromHours(2), bauart: 1) };
Assert.That(Weg(akten), Has.Count.EqualTo(1),
"so gebaut würde der Server es heute nicht mehr ausliefern");
}
[Test]
public void Ein_unleserlicher_Name_gilt_als_verwaist()
{
var akten = new[]
{
new Paketakte("/kratz/packages/irgendwas.mp4", Gigabyte, Jetzt, Paketname.Lesen("irgendwas.mp4")),
};
Assert.That(Weg(akten), Has.Count.EqualTo(1));
}
/// Die Schonfrist schützt auch Verwaiste nicht.
///
///
/// Sie schützt das, was gleich gebraucht wird. Eine Datei, die niemand
/// mehr ausliefern kann, wird nicht gleich gebraucht — sie frisch
/// liegenzulassen hiesse, auf etwas zu warten, das nicht kommt.
///
[Test]
public void Eine_frische_Verwaiste_geht_trotzdem()
{
var akten = new[] { Akte(EinsId, 1, TimeSpan.Zero, bauart: 1) };
Assert.That(Weg(akten), Has.Count.EqualTo(1));
}
[Test]
public void Unter_der_Obergrenze_bleibt_alles_liegen()
{
var akten = new[]
{
Akte(EinsId, 5, TimeSpan.FromHours(2)),
Akte(ZweiId, 5, TimeSpan.FromHours(3)),
};
Assert.That(Weg(akten), Is.Empty);
}
/// Über der Obergrenze geht das am längsten Ungenutzte zuerst.
[Test]
public void Ueber_der_Obergrenze_geht_das_aelteste_zuerst()
{
var jung = Akte(EinsId, 15, TimeSpan.FromHours(2));
var alt = Akte(ZweiId, 15, TimeSpan.FromHours(20));
var weg = Weg([jung, alt]);
Assert.Multiple(() =>
{
Assert.That(weg, Has.Count.EqualTo(1), "eines von beiden reicht, um unter 20 GB zu kommen");
Assert.That(weg[0], Is.EqualTo(alt.Pfad));
});
}
[Test]
public void Was_in_der_Schonfrist_liegt_bleibt_auch_ueber_der_Obergrenze()
{
var eben = Akte(EinsId, 15, TimeSpan.FromMinutes(1));
var auchEben = Akte(ZweiId, 15, TimeSpan.FromMinutes(2));
Assert.That(Weg([eben, auchEben]), Is.Empty,
"beide wurden gerade gebraucht — wegräumen hiesse, jemandem den Download abzuschneiden");
}
[Test]
public void Was_in_Benutzung_ist_bleibt_auch_ueber_der_Obergrenze()
{
var laeuft = Akte(EinsId, 15, TimeSpan.FromDays(2));
var daneben = Akte(ZweiId, 15, TimeSpan.FromHours(2));
var weg = Weg([laeuft, daneben], inBenutzung: p => p == laeuft.Pfad);
Assert.That(weg, Does.Not.Contain(laeuft.Pfad),
"ein Download über Mobilfunk kann Stunden dauern, ohne dass eine Anfrage kommt");
}
/// Unbenutztes geht auch, wenn noch Platz wäre.
[Test]
public void Lange_Unbenutzte_gehen_auch_unter_der_Obergrenze()
{
var akten = new[] { Akte(EinsId, 1, TimeSpan.FromDays(30)) };
Assert.That(Weg(akten), Has.Count.EqualTo(1));
}
[Test]
public void Eine_Obergrenze_von_null_heisst_unbegrenzt()
{
var regeln = Transcoderegeln.Vorgabe with { ObergrenzeBytes = 0 };
var akten = new[]
{
Akte(EinsId, 500, TimeSpan.FromHours(2)),
Akte(ZweiId, 500, TimeSpan.FromHours(3)),
};
Assert.That(Weg(akten, regeln), Is.Empty);
}
[Test]
public void Ohne_Altersabbau_bleibt_auch_sehr_Altes_liegen()
{
var regeln = Transcoderegeln.Vorgabe with { Unbenutzt = TimeSpan.Zero };
Assert.That(Weg([Akte(EinsId, 1, TimeSpan.FromDays(400))], regeln), Is.Empty);
}
/// Auch mit abgeschalteten Regeln fliegt Verwaistes.
///
///
/// „Unbegrenzt" heisst „so viel du willst", nicht „sammle auch Müll".
///
[Test]
public void Verwaistes_geht_auch_bei_abgeschalteten_Grenzen()
{
var regeln = new Transcoderegeln(0, TimeSpan.Zero, TimeSpan.FromMinutes(30));
Assert.That(Weg([Akte(EinsId, 1, TimeSpan.FromHours(2), bauart: 1)], regeln), Has.Count.EqualTo(1));
}
[Test]
public void Eine_zu_kurze_Schonfrist_wird_hochgesetzt()
{
var regeln = Transcoderegeln.Geprueft(20, 7, 1);
Assert.That(regeln.Schonfrist, Is.EqualTo(TimeSpan.FromMinutes(5)),
"eine Minute löschte die Datei mitten im Herunterladen weg");
}
[Test]
public void Null_und_negative_Werte_heissen_abgeschaltet()
{
var regeln = Transcoderegeln.Geprueft(0, -3, 30);
Assert.Multiple(() =>
{
Assert.That(regeln.ObergrenzeBytes, Is.Zero);
Assert.That(regeln.Unbenutzt, Is.EqualTo(TimeSpan.Zero));
});
}
}
/// Der Name einer umgepackten Datei, in beide Richtungen.
public class PaketnameTests
{
[Test]
public void Ein_gebauter_Name_laesst_sich_wieder_lesen()
{
var id = Guid.CreateVersion7();
var geaendert = DateTimeOffset.FromUnixTimeMilliseconds(1_700_000_000_123);
var gelesen = Paketname.Lesen(Paketname.Bauen(id, geaendert, 2));
Assert.That(gelesen, Is.EqualTo(new Paketname.Teile(id, 1_700_000_000_123, 2)));
}
[Test]
public void Der_Pfad_davor_stoert_nicht()
{
var id = Guid.CreateVersion7();
var name = Paketname.Bauen(id, DateTimeOffset.FromUnixTimeMilliseconds(5), 2);
Assert.That(Paketname.Lesen($"/kratz/packages/{name}")?.DateiId, Is.EqualTo(id));
}
[TestCase("irgendwas.mp4")]
[TestCase("abc-123-2.mp4")]
[TestCase("00000000000000000000000000000001-123.mp4")]
[TestCase("00000000000000000000000000000001-123-2.teil")]
[TestCase("00000000000000000000000000000001-nichtzahl-2.mp4")]
public void Ein_fremder_Name_ergibt_nichts(string name) =>
Assert.That(Paketname.Lesen(name), Is.Null);
}