Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt). Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e): Properties, Record-Parameter, Enum-Member sowie die Parameter der Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
150 lines
5.8 KiB
C#
150 lines
5.8 KiB
C#
using ClawdDotNet.Core.Config;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.App.Tests;
|
|
|
|
/// <summary>
|
|
/// Where an instance logs to and backs up to — and that two instances do not end up
|
|
/// in the same folder (#528, stage 1).
|
|
///
|
|
/// <para><b>The occasion.</b> Both defaults were application-wide. <c>Startup.log</c>
|
|
/// therefore contained alternating lines of two instances, without marking which one
|
|
/// was speaking; and because retention cleans up by prefix in exactly one folder,
|
|
/// the running instance threw away the other's archives along with its own. As long as that is
|
|
/// the case, every troubleshooting session in multi-instance operation is guesswork.</para>
|
|
///
|
|
/// <para><b>What remains from #375.</b> A relative value is resolved against the
|
|
/// instance directory, not against the working directory of the process. A
|
|
/// systemd service without <c>WorkingDirectory=</c> starts in <c>/</c>, and <c>"./Logs"</c>
|
|
/// then pointed to <c>/Logs</c> — not creatable for a service account.</para>
|
|
/// </summary>
|
|
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
|
|
};
|
|
|
|
// ─── Defaults ───
|
|
|
|
[Fact]
|
|
public void The_defaults_are_empty()
|
|
{
|
|
// The core: empty means "take the folder of this instance". If a value stands here,
|
|
// every new instance carries it around.
|
|
new InstanceConfig().LogDirectory.ShouldBe("");
|
|
new InstanceConfig().BackupDirectory.ShouldBe("");
|
|
}
|
|
|
|
[Fact]
|
|
public void Without_value_the_instance_logs_into_its_own_directory()
|
|
{
|
|
AppHost.ResolveLogDirectory(Instance(), InstancePath)
|
|
.ShouldBe(Path.Combine(InstancePath, "Logs"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Without_value_the_instance_backs_up_into_its_own_directory()
|
|
{
|
|
AppHost.ResolveBackupDirectory(Instance(), InstancePath)
|
|
.ShouldBe(Path.Combine(InstancePath, "Backups"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The actual promise of stage 1. Everything else in this file checks the
|
|
/// mechanics; this test checks the promise.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Two_instances_without_value_share_nothing()
|
|
{
|
|
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);
|
|
}
|
|
|
|
// ─── Entered values ───
|
|
|
|
[Fact]
|
|
public void An_absolute_value_takes_precedence()
|
|
{
|
|
var ownLog = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-eigene-logs"));
|
|
var ownBackup = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "clawd-eigene-sicherungen"));
|
|
|
|
AppHost.ResolveLogDirectory(Instance(logDirectory: ownLog), InstancePath)
|
|
.ShouldBe(ownLog);
|
|
|
|
AppHost.ResolveBackupDirectory(Instance(backupDirectory: ownBackup), InstancePath)
|
|
.ShouldBe(ownBackup);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_relative_value_counts_against_the_instance_directory()
|
|
{
|
|
// Not against the working directory of the process — that was the error from #375.
|
|
AppHost.ResolveLogDirectory(Instance(logDirectory: "./Protokolle"), InstancePath)
|
|
.ShouldBe(Path.Combine(InstancePath, "Protokolle"));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_relative_value_does_not_depend_on_the_process_working_directory()
|
|
{
|
|
AppHost.ResolveLogDirectory(Instance(logDirectory: "./Protokolle"), InstancePath)
|
|
.ShouldNotBe(Path.GetFullPath("./Protokolle"));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("./Logs")]
|
|
[InlineData("Logs")]
|
|
public void The_old_default_points_to_the_same_place_as_no_value(string old)
|
|
{
|
|
// That's why LoadInstanceConfig may turn them into "empty" without anything
|
|
// shifting.
|
|
AppHost.ResolveLogDirectory(Instance(logDirectory: old), InstancePath)
|
|
.ShouldBe(AppHost.ResolveLogDirectory(Instance(), InstancePath));
|
|
}
|
|
|
|
[Fact]
|
|
public void Only_spaces_count_as_no_value()
|
|
{
|
|
AppHost.ResolveLogDirectory(Instance(logDirectory: " "), InstancePath)
|
|
.ShouldBe(Path.Combine(InstancePath, "Logs"));
|
|
}
|
|
|
|
// ─── Reference point ───
|
|
|
|
[Fact]
|
|
public void An_empty_WorkingDirectory_falls_back_to_the_instance_path()
|
|
{
|
|
// Since CreateInstance the field is in the file as an absolute path, but may have been
|
|
// emptied by hand. Then the place it was loaded from carries the day.
|
|
AppHost.ResolveLogDirectory(
|
|
Instance(logDirectory: "Logs", workingDirectory: ""), InstancePath)
|
|
.ShouldBe(Path.Combine(InstancePath, "Logs"));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_relative_WorkingDirectory_also_falls_back_to_the_instance_path()
|
|
{
|
|
// "./data/" is the old default of the field and would again have been resolved against the
|
|
// working directory of the process.
|
|
AppHost.ResolveLogDirectory(
|
|
Instance(logDirectory: "Logs", workingDirectory: "./data/"), InstancePath)
|
|
.ShouldBe(Path.Combine(InstancePath, "Logs"));
|
|
}
|
|
}
|