using ClawdDotNet.Core.Config; using Shouldly; namespace ClawdDotNet.App.Tests; /// /// Wohin eine Instanz protokolliert und sichert — und dass zwei Instanzen dabei nicht /// im selben Ordner landen (#528, Stufe 1). /// /// Der Anlass. Beide Vorgaben waren anwendungsweit. In Startup.log /// standen deshalb abwechselnd Zeilen zweier Instanzen, ohne Kennzeichnung, welche /// gerade spricht; und weil die Aufbewahrung nach Präfix in genau einem Ordner /// aufräumt, warf die laufende Instanz die Archive der anderen mit weg. Solange das so /// ist, ist jede Fehlersuche im Mehrinstanzbetrieb Ratearbeit. /// /// Was aus #375 bleibt. Ein relativer Wert zählt gegen das /// Instanzverzeichnis, nicht gegen das Arbeitsverzeichnis des Prozesses. Ein /// systemd-Dienst ohne WorkingDirectory= startet in /, und "./Logs" /// zeigte dann auf /Logs — für ein Dienstkonto nicht anlegbar. /// public sealed class LogDirectoryResolutionTests { private static readonly string InstancePath = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-tests", "Instance-Test")); private static InstanceConfig Instance( string logDirectory = "", string backupDirectory = "", string? workingDirectory = null) => new() { LogDirectory = logDirectory, BackupDirectory = backupDirectory, WorkingDirectory = workingDirectory ?? InstancePath }; // ─── Vorgaben ─── [Fact] public void Die_Vorbelegungen_sind_leer() { // Der Kern: Leer heisst „nimm den Ordner dieser Instanz". Steht hier ein Wert, // traegt ihn jede neue Instanz mit sich herum. new InstanceConfig().LogDirectory.ShouldBe(""); new InstanceConfig().BackupDirectory.ShouldBe(""); } [Fact] public void Ohne_Angabe_protokolliert_die_Instanz_in_ihr_eigenes_Verzeichnis() { AppHost.ResolveLogDirectory(Instance(), InstancePath) .ShouldBe(Path.Combine(InstancePath, "Logs")); } [Fact] public void Ohne_Angabe_sichert_die_Instanz_in_ihr_eigenes_Verzeichnis() { AppHost.ResolveBackupDirectory(Instance(), InstancePath) .ShouldBe(Path.Combine(InstancePath, "Backups")); } /// /// Das eigentliche Versprechen von Stufe 1. Alles andere in dieser Datei prüft die /// Mechanik; dieser Test prüft die Zusage. /// [Fact] public void Zwei_Instanzen_ohne_Angabe_teilen_sich_nichts() { var a = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-tests", "Instance-A")); var b = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-tests", "Instance-B")); var logA = AppHost.ResolveLogDirectory(Instance(workingDirectory: a), a); var logB = AppHost.ResolveLogDirectory(Instance(workingDirectory: b), b); var backupA = AppHost.ResolveBackupDirectory(Instance(workingDirectory: a), a); var backupB = AppHost.ResolveBackupDirectory(Instance(workingDirectory: b), b); logA.ShouldNotBe(logB); backupA.ShouldNotBe(backupB); } // ─── Eingetragene Werte ─── [Fact] public void Ein_absoluter_Wert_geht_vor() { var eigenesLog = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-eigene-logs")); var eigenesBackup = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-eigene-sicherungen")); AppHost.ResolveLogDirectory(Instance(logDirectory: eigenesLog), InstancePath) .ShouldBe(eigenesLog); AppHost.ResolveBackupDirectory(Instance(backupDirectory: eigenesBackup), InstancePath) .ShouldBe(eigenesBackup); } [Fact] public void Ein_relativer_Wert_zaehlt_gegen_das_Instanzverzeichnis() { // Nicht gegen das Arbeitsverzeichnis des Prozesses — das war der Fehler aus #375. AppHost.ResolveLogDirectory(Instance(logDirectory: "./Protokolle"), InstancePath) .ShouldBe(Path.Combine(InstancePath, "Protokolle")); } [Fact] public void Ein_relativer_Wert_haengt_nicht_am_Arbeitsverzeichnis_des_Prozesses() { AppHost.ResolveLogDirectory(Instance(logDirectory: "./Protokolle"), InstancePath) .ShouldNotBe(Path.GetFullPath("./Protokolle")); } [Theory] [InlineData("./Logs")] [InlineData("Logs")] public void Die_alte_Vorbelegung_zeigt_auf_dieselbe_Stelle_wie_keine_Angabe(string alt) { // Deshalb darf LoadInstanceConfig sie zu „leer" machen, ohne dass sich etwas // verschiebt. AppHost.ResolveLogDirectory(Instance(logDirectory: alt), InstancePath) .ShouldBe(AppHost.ResolveLogDirectory(Instance(), InstancePath)); } [Fact] public void Nur_Leerzeichen_gelten_als_keine_Angabe() { AppHost.ResolveLogDirectory(Instance(logDirectory: " "), InstancePath) .ShouldBe(Path.Combine(InstancePath, "Logs")); } // ─── Bezugspunkt ─── [Fact] public void Ein_leeres_WorkingDirectory_faellt_auf_den_Instanzpfad_zurueck() { // Das Feld steht seit CreateInstance absolut in der Datei, kann aber von Hand // geleert worden sein. Dann trägt der Ort, aus dem geladen wurde. AppHost.ResolveLogDirectory( Instance(logDirectory: "Logs", workingDirectory: ""), InstancePath) .ShouldBe(Path.Combine(InstancePath, "Logs")); } [Fact] public void Ein_relatives_WorkingDirectory_faellt_ebenfalls_auf_den_Instanzpfad_zurueck() { // "./data/" ist die alte Vorbelegung des Feldes und wäre wieder gegen das // Arbeitsverzeichnis des Prozesses aufgelöst worden. AppHost.ResolveLogDirectory( Instance(logDirectory: "Logs", workingDirectory: "./data/"), InstancePath) .ShouldBe(Path.Combine(InstancePath, "Logs")); } }