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>
93 lines
3.4 KiB
C#
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");
|
|
}
|
|
}
|