Files
Flaro/server/tests/Flaro.Core.Tests/AuthCodeTests.cs
T
ScarriffleandClaude Opus 5 ed13d5fa43 Kratzplatz, Markenerkennung, Tonabdruck — erfundene Wörter für Fachbegriffe
„Scratch disk" wörtlich zu übersetzen ist derselbe Fehler wie „Wachhund"
für Watchdog und „Keks" für Cookie. Und „Marke" ist kein Fachbegriff für
das, was gemeint ist: Jellyfin nennt diese Abschnitte Media Segments, und
das ist auch das Wort, in dem sich darüber reden lässt.

    Kratzplatz*       → Transcode*          (was es ist, steht in FLARO_TRANSCODE)
    Marke, Marken     → Segment, Segmente
    Markenerkennung   → Segmenterkennung
    Markenart/-suche  → Segmentart/-suche
    Tonabdruck        → Fingerprint         (Chromaprint nennt es so)
    Tonabgleich       → Fingerprintabgleich
    Schlüsselbild     → Keyframe
    Bildabtaster      → Frameabtaster

Gewöhnliches Deutsch bleibt gewöhnliches Deutsch: Wartung, Freigabe,
Meldung, Abspann, Vorspann. Das war nie das Problem.

**Was sich nicht ändert, und zwar mit Absicht.** Die drei
Einstellungsschlüssel `wartung.kratzplatz.paket.*` bleiben, wie sie sind —
sie stehen in der Datenbank, und ein neuer Name hiesse, dass die Werte
des Nutzers stillschweigend auf die Vorgabe zurückfallen. Dasselbe für
die Endpunktpfade `/marken`: sie sind nicht sichtbar, und ein Umbenennen
wäre Diff ohne Gewinn.

**Die Migration ist von Hand geschrieben.** `dotnet ef` hat ein Gerüst
erzeugt, das beide Tabellen löscht und neu anlegt — es sieht eine
verschwundene und eine neue Tabelle, nicht eine umbenannte. Damit wäre
jedes erkannte Segment und jeder Vermerk „hier wurde schon gesucht" weg
gewesen. `RenameTable` und `RenameIndex` tun dasselbe, ohne eine Zeile
anzufassen; nachgemessen an einer echten SQLite-Datei: Zeilen vorher 1
und 1, nachher 1 und 1, Inhalt unverändert.

Nachgezogen: in `bilder.ts` und `main.ts` heisst „marke" ein Häkchen und
das Logo — die blieben unberührt. Und „Segment" ist sächlich, was eine
Blindersetzung nicht weiss: „eine Segment" stand danach an vierzehn
Stellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-16 16:26:57 +02:00

119 lines
3.6 KiB
C#

using Flaro.Core.Auth;
namespace Flaro.Core.Tests;
public class SecretsTests
{
[Test]
public void Zwei_Geheimnisse_sind_nie_gleich()
{
var gesehen = new HashSet<string>();
for (var i = 0; i < 200; i++) Assert.That(gesehen.Add(Secrets.Create()), Is.True);
}
[Test]
public void Das_Geheimnis_ist_URL_sicher()
{
for (var i = 0; i < 50; i++)
{
var s = Secrets.Create();
Assert.That(s, Does.Not.Contain("+").And.Not.Contain("/").And.Not.Contain("="));
}
}
[Test]
public void Der_Fingerfingerprint_ist_gleichbleibend_und_unterscheidet()
{
var a = Secrets.Create();
var b = Secrets.Create();
// Zweimal gerechnet, nicht zweimal derselbe Ausdruck: geprüft wird,
// dass die Funktion bei gleicher Eingabe gleich antwortet.
var ersterLauf = Secrets.Fingerprint(a);
var zweiterLauf = Secrets.Fingerprint(a);
Assert.That(zweiterLauf, Is.EqualTo(ersterLauf));
Assert.That(Secrets.Fingerprint(b), Is.Not.EqualTo(ersterLauf));
Assert.That(ersterLauf, Has.Length.EqualTo(64));
}
/// <summary>Der Fingerfingerprint darf das Geheimnis nicht enthalten.</summary>
[Test]
public void Der_Fingerfingerprint_verraet_das_Geheimnis_nicht()
{
var s = Secrets.Create();
Assert.That(Secrets.Fingerprint(s), Does.Not.Contain(s));
}
}
public class AuthCodeTests
{
private static readonly DateTimeOffset Jetzt = new(2026, 10, 3, 12, 0, 0, TimeSpan.Zero);
private static AuthCode Angelegt(DateTimeOffset? läuftAb = null) => new()
{
Code = AuthCode.NewCode(),
HandleFingerprint = Secrets.Fingerprint(Secrets.Create()),
DeviceName = "Wohnzimmer",
DeviceId = "tv-1",
CreatedAt = Jetzt,
ExpiresAt = läuftAb ?? Jetzt + AuthCode.Lifetime,
};
[Test]
public void Der_Code_hat_immer_sechs_Ziffern()
{
for (var i = 0; i < 500; i++)
{
var code = AuthCode.NewCode();
Assert.That(code, Has.Length.EqualTo(6));
Assert.That(code, Does.Match("^[0-9]{6}$"));
}
}
/// <summary>Führende Nullen dürfen nicht wegfallen — „000123" ist ein gültiger Code.</summary>
[Test]
public void Fuehrende_Nullen_bleiben_erhalten()
{
var mitNull = Enumerable.Range(0, 20_000).Select(_ => AuthCode.NewCode()).Any(c => c[0] == '0');
Assert.That(mitNull, Is.True, "In 20 000 Ziehungen kam kein Code mit führender Null vor");
}
[Test]
public void Ohne_Bestaetigung_gibt_es_kein_Token()
{
Assert.That(Angelegt().CanBeRedeemed(Jetzt), Is.False);
}
[Test]
public void Nach_Bestaetigung_gibt_es_ein_Token()
{
var code = Angelegt();
code.ApprovedByUserId = Guid.CreateVersion7();
Assert.That(code.CanBeRedeemed(Jetzt), Is.True);
}
/// <summary>Einmal abgeholt ist abgeholt.</summary>
[Test]
public void Ein_verbrauchter_Code_gibt_kein_zweites_Token()
{
var code = Angelegt();
code.ApprovedByUserId = Guid.CreateVersion7();
code.ConsumedAt = Jetzt;
Assert.That(code.CanBeRedeemed(Jetzt), Is.False);
}
[Test]
public void Ein_abgelaufener_Code_gibt_kein_Token_auch_wenn_bestaetigt()
{
var code = Angelegt(läuftAb: Jetzt);
code.ApprovedByUserId = Guid.CreateVersion7();
Assert.That(code.IsExpired(Jetzt), Is.True);
Assert.That(code.CanBeRedeemed(Jetzt), Is.False);
}
[Test]
public void Die_Gueltigkeit_ist_kurz()
{
Assert.That(AuthCode.Lifetime, Is.LessThanOrEqualTo(TimeSpan.FromMinutes(10)));
}
}