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. } }