namespace Flaro.Api.Tests;
/// Was beim allerersten Start passiert, bevor irgendetwas anderes greift.
public class FlaroPathsTests
{
private static readonly string[] Variablen = ["FLARO_DATA", "FLARO_CACHE", "FLARO_TRANSCODE"];
private string _wurzel = "";
private Dictionary _vorher = [];
/// Merkt sich die Umgebung, bevor diese Klasse daran rührt.
///
///
/// Ohne das kostete der ganze Lauf eine Viertelstunde. Zwei Tests
/// hier löschen die Pfadvariablen, um den Rückfall auf die Voreinstellung
/// zu prüfen — und liessen sie gelöscht zurück. Jeder danach gestartete
/// Testserver fiel dann auf <Projektverzeichnis>/data zurück,
/// und Dutzende Server teilten sich **eine** SQLite-Datei. Die warteten
/// dann aufeinander, statt zu prüfen.
///
/// Prozessweiter Zustand gehört zurückgesetzt, wo er verändert wurde. Das
/// ist keine Formsache: der Schaden zeigt sich erst in einer anderen Klasse.
///
[SetUp]
public void Anlegen()
{
_vorher = Variablen.ToDictionary(v => v, Environment.GetEnvironmentVariable);
_wurzel = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "flaro-pfade-" + Guid.CreateVersion7().ToString("N"))).FullName;
}
[TearDown]
public void Aufraeumen()
{
// Zurücksetzen, sonst lässt sich das gesperrte Verzeichnis nicht löschen.
if (!OperatingSystem.IsWindows())
{
foreach (var d in Directory.EnumerateDirectories(_wurzel, "*", SearchOption.AllDirectories))
File.SetUnixFileMode(d, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
}
try { Directory.Delete(_wurzel, recursive: true); } catch (IOException) { }
foreach (var (name, wert) in _vorher) Environment.SetEnvironmentVariable(name, wert);
}
private FlaroPaths Pfade(string? daten = null) => new()
{
Data = daten ?? Path.Combine(_wurzel, "config"),
Cache = Path.Combine(_wurzel, "cache"),
Transcode = Path.Combine(_wurzel, "transcode"),
};
[Test]
public void Fehlende_Verzeichnisse_werden_angelegt()
{
var pfade = Pfade();
pfade.EnsureExists();
Assert.That(Directory.Exists(pfade.Data), Is.True);
Assert.That(Directory.Exists(pfade.Cache), Is.True);
Assert.That(Directory.Exists(pfade.Transcode), Is.True);
}
[Test]
public void Ein_zweiter_Start_stoert_sich_nicht_am_ersten()
{
var pfade = Pfade();
pfade.EnsureExists();
Assert.DoesNotThrow(() => pfade.EnsureExists());
}
/// Der häufigste Stolperstein beim ersten Containerstart.
///
///
/// Hier wird die Diagnose geprüft, nicht die Funktion. Ein
/// vorhandenes, aber gesperrtes Verzeichnis lässt
/// Directory.CreateDirectory durchgehen; ohne eigene Schreibprobe
/// stirbt der Server erst später an „unable to open database file" — einer
/// Meldung, die weder das Verzeichnis nennt noch verrät, dass es um
/// Dateirechte geht.
///
[Test]
public void Ein_gesperrtes_Verzeichnis_faellt_beim_Start_auf_und_nennt_den_Grund()
{
if (OperatingSystem.IsWindows()) Assert.Ignore("Dateirechte verhalten sich dort anders.");
var gesperrt = Directory.CreateDirectory(Path.Combine(_wurzel, "gesperrt")).FullName;
if (OperatingSystem.IsWindows()) return;
File.SetUnixFileMode(gesperrt, UnixFileMode.UserRead | UnixFileMode.UserExecute);
var fehler = Assert.Throws(() => Pfade(daten: gesperrt).EnsureExists());
Assert.That(fehler!.Message, Does.Contain("FLARO_DATA"), "Welche Einstellung gemeint ist");
Assert.That(fehler.Message, Does.Contain(gesperrt), "Welches Verzeichnis");
Assert.That(fehler.Message, Does.Contain("uid="), "Als wer der Server läuft");
Assert.That(fehler.Message, Does.Contain("chown"), "Was zu tun ist");
}
[Test]
public void Ohne_Umgebungsvariablen_liegt_alles_neben_dem_Programm()
{
foreach (var name in new[] { "FLARO_DATA", "FLARO_CACHE", "FLARO_TRANSCODE" })
Environment.SetEnvironmentVariable(name, null);
var pfade = FlaroPaths.FromEnvironment(_wurzel);
Assert.That(pfade.Data, Is.EqualTo(Path.Combine(_wurzel, "data")));
Assert.That(pfade.DatabaseFile, Does.EndWith("flaro.db"));
}
[Test]
public void Die_Umgebung_sticht_die_Voreinstellung()
{
Environment.SetEnvironmentVariable("FLARO_DATA", "/woanders");
Assert.That(FlaroPaths.FromEnvironment(_wurzel).Data, Is.EqualTo("/woanders"));
// Zurückgesetzt wird im TearDown, für alle drei Variablen gemeinsam.
}
}