Files
RichardandClaude Opus 5.5 77062facc9 Stufe 4c (#764): Bezeichner auf Englisch - Typen, Methoden, Felder, Parameter, Locals, Testnamen
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>
2026-10-04 13:18:43 +02:00

93 lines
3.4 KiB
C#

using ClawdDotNet.App.Services;
using ClawdDotNet.Core.Config;
using Shouldly;
namespace ClawdDotNet.App.Tests.Services;
/// <summary>
/// The instance's credentials must survive the way to disk and back.
///
/// <para><c>SaveInstanceConfig</c> encrypted when writing, but <c>LoadInstanceConfig</c>
/// did not decrypt when reading. Whatever had once been saved via the user interface
/// came back as <c>enc:v2:…</c> and was used as the value — the OpenRouter key
/// thus went to the API encrypted.</para>
///
/// <para>It went undiscovered because <c>ConfigLoader.LoadAsync</c> does it right
/// and is not called at all in the running program: only <c>Migrate</c>
/// of that class is needed.</para>
/// </summary>
public sealed class InstanceSecretRoundtripTests
{
[Fact]
public void Credentials_come_back_in_plaintext()
{
using var host = new TestHost();
host.Host.Instance.OpenRouterApiKey = "sk-or-v1-beispielschluessel";
host.Host.Instance.WebUiToken = "ein-hinreichend-langes-token";
host.Host.Instance.Watchdog.AgentToken = "dc-token-beispiel";
host.Host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
var loaded = host.Host.Directories.LoadInstanceConfig(host.Host.InstancePath);
loaded.OpenRouterApiKey.ShouldBe("sk-or-v1-beispielschluessel");
loaded.WebUiToken.ShouldBe("ein-hinreichend-langes-token");
loaded.Watchdog.AgentToken.ShouldBe("dc-token-beispiel");
}
[Fact]
public void In_the_file_they_are_still_protected()
{
// The counter-check: decrypting when loading must not mean that nothing is
// encrypted any more.
using var host = new TestHost();
host.Host.Instance.OpenRouterApiKey = "sk-or-v1-beispielschluessel";
host.Host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
var rawText = File.ReadAllText(
Path.Combine(host.Host.InstancePath, "InstanceSettings.json"));
rawText.ShouldNotContain("sk-or-v1-beispielschluessel");
}
[Fact]
public void Plaintext_from_older_files_stays_readable()
{
// Decrypting must be safely applicable to something that was never encrypted
// — otherwise the first start after the update would break every existing instance.
using var host = new TestHost();
var path = Path.Combine(host.Host.InstancePath, "InstanceSettings.json");
File.WriteAllText(path, """
{
"instanceId": "alt",
"instanceName": "Alt",
"openRouterApiKey": "klartext-von-hand-eingetragen",
"webUiToken": "auch-klartext"
}
""");
var loaded = host.Host.Directories.LoadInstanceConfig(host.Host.InstancePath);
loaded.OpenRouterApiKey.ShouldBe("klartext-von-hand-eingetragen");
loaded.WebUiToken.ShouldBe("auch-klartext");
}
[Fact]
public void Saving_twice_does_not_encrypt_twice()
{
using var host = new TestHost();
host.Host.Instance.WebUiToken = "ein-hinreichend-langes-token";
host.Host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
host.Host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
host.Host.Directories.LoadInstanceConfig(host.Host.InstancePath)
.WebUiToken.ShouldBe("ein-hinreichend-langes-token");
}
}