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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
837a634a22
commit
77062facc9
@@ -106,7 +106,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Der_vollstaendige_Rundlauf_stellt_alles_wieder_her()
|
||||
public async Task The_full_round_trip_restores_everything()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
|
||||
@@ -140,7 +140,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Zugangsdaten_sind_nach_dem_Wiederherstellen_wieder_nutzbar()
|
||||
public async Task Credentials_are_usable_again_after_restoring()
|
||||
{
|
||||
// The actual purpose of the passphrase: a backup is needed when the
|
||||
// machine is broken — DPAPI-protected values would then be unreadable.
|
||||
@@ -173,27 +173,27 @@ public sealed class BackupServiceTests : IDisposable
|
||||
/// the test would be green without proving anything.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Der_Umzug_auf_einen_anderen_Rechner_traegt()
|
||||
public async Task The_move_to_another_machine_holds()
|
||||
{
|
||||
var zielMaschine = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
||||
var targetMachine = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
||||
|
||||
try
|
||||
{
|
||||
// Machine 1: values lie under the local key.
|
||||
await BuildInstanceAsync();
|
||||
var schluesselDerQuelle = File.ReadAllBytes(SecretKeyStore.KeyFilePath);
|
||||
var sourceKey = File.ReadAllBytes(SecretKeyStore.KeyFilePath);
|
||||
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
new BackupOptions { Secrets = SecretMode.Passphrase, Passphrase = "umzug" });
|
||||
|
||||
// Machine 2: different machine, different key. The key does not travel
|
||||
// along — it deliberately lies outside the instance directory.
|
||||
SecretKeyStore.UseDirectory(zielMaschine);
|
||||
SecretKeyStore.UseDirectory(targetMachine);
|
||||
|
||||
await _service.RestoreAsync(ZipPath, RestoreDir, new RestoreOptions { Passphrase = "umzug" });
|
||||
|
||||
File.ReadAllBytes(SecretKeyStore.KeyFilePath)
|
||||
.ShouldNotBe(schluesselDerQuelle, "sonst wäre es derselbe Rechner");
|
||||
.ShouldNotBe(sourceKey, "sonst wäre es derselbe Rechner");
|
||||
|
||||
var settings = JsonDocument.Parse(
|
||||
File.ReadAllText(Path.Combine(RestoreDir, "InstanceSettings.json")));
|
||||
@@ -217,18 +217,18 @@ public sealed class BackupServiceTests : IDisposable
|
||||
// user key must certainly not be it.
|
||||
SecretKeyStore.UseDirectory(
|
||||
Path.Combine(Path.GetTempPath(), "clawd-tests", "keys", Guid.NewGuid().ToString("N")));
|
||||
try { Directory.Delete(zielMaschine, recursive: true); } catch { /* secondary */ }
|
||||
try { Directory.Delete(targetMachine, recursive: true); } catch { /* secondary */ }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ohne_die_Passphrase_ist_der_Umzug_verloren()
|
||||
public async Task Without_the_passphrase_the_move_is_lost()
|
||||
{
|
||||
// The flip side, and the reason why the passphrase has to be offered when backing
|
||||
// up: without it a backup on the target machine is worthless as far as the
|
||||
// credentials are concerned. That is not an error, but the price for the
|
||||
// key not travelling along — but it has to be noticed instead of happening silently.
|
||||
var zielMaschine = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
||||
var targetMachine = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -237,7 +237,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
new BackupOptions { Secrets = SecretMode.Passphrase, Passphrase = "umzug" });
|
||||
|
||||
SecretKeyStore.UseDirectory(zielMaschine);
|
||||
SecretKeyStore.UseDirectory(targetMachine);
|
||||
|
||||
await Should.ThrowAsync<Exception>(
|
||||
() => _service.RestoreAsync(ZipPath, RestoreDir, new RestoreOptions { Passphrase = "falsch" }));
|
||||
@@ -246,12 +246,12 @@ public sealed class BackupServiceTests : IDisposable
|
||||
{
|
||||
SecretKeyStore.UseDirectory(
|
||||
Path.Combine(Path.GetTempPath(), "clawd-tests", "keys", Guid.NewGuid().ToString("N")));
|
||||
try { Directory.Delete(zielMaschine, recursive: true); } catch { /* secondary */ }
|
||||
try { Directory.Delete(targetMachine, recursive: true); } catch { /* secondary */ }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Auch_verschachtelte_Zugangsdaten_in_Tool_Konfigurationen_kommen_zurueck()
|
||||
public async Task Nested_credentials_in_tool_configurations_come_back_too()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
|
||||
@@ -269,7 +269,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Unbekannte_Felder_ueberstehen_den_Rundlauf()
|
||||
public async Task Unknown_fields_survive_the_round_trip()
|
||||
{
|
||||
// A backup must not throw anything away just because a program version does not know
|
||||
// a field.
|
||||
@@ -288,7 +288,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Ohne_Zugangsdaten_enthaelt_das_Archiv_keine_Geheimnisse()
|
||||
public async Task Without_credentials_the_archive_contains_no_secrets()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
|
||||
@@ -296,25 +296,25 @@ public sealed class BackupServiceTests : IDisposable
|
||||
new BackupOptions { Secrets = SecretMode.Exclude });
|
||||
|
||||
// Search the entire archive in plain text.
|
||||
var inhalt = ReadWholeArchive(ZipPath);
|
||||
var content = ReadWholeArchive(ZipPath);
|
||||
|
||||
inhalt.ShouldNotContain(ApiKeyPlain);
|
||||
inhalt.ShouldNotContain(MailPasswordPlain);
|
||||
content.ShouldNotContain(ApiKeyPlain);
|
||||
content.ShouldNotContain(MailPasswordPlain);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Eine_Sicherung_mit_Passphrase_zeigt_die_Geheimnisse_nicht_im_Klartext()
|
||||
public async Task A_backup_with_passphrase_does_not_show_the_secrets_in_plaintext()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
new BackupOptions { Secrets = SecretMode.Passphrase, Passphrase = "geheim" });
|
||||
|
||||
var inhalt = ReadWholeArchive(ZipPath);
|
||||
var content = ReadWholeArchive(ZipPath);
|
||||
|
||||
inhalt.ShouldNotContain(ApiKeyPlain);
|
||||
inhalt.ShouldNotContain(MailPasswordPlain);
|
||||
inhalt.ShouldContain("pbe:v1:");
|
||||
content.ShouldNotContain(ApiKeyPlain);
|
||||
content.ShouldNotContain(MailPasswordPlain);
|
||||
content.ShouldContain("pbe:v1:");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -322,7 +322,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Das_Manifest_beschreibt_die_Sicherung()
|
||||
public async Task The_manifest_describes_the_backup()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
|
||||
@@ -340,7 +340,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ohne_Passphrase_wird_nicht_wiederhergestellt()
|
||||
public async Task Without_passphrase_nothing_is_restored()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
@@ -353,7 +353,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Eine_falsche_Passphrase_wird_erkannt()
|
||||
public async Task A_wrong_passphrase_is_recognised()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
@@ -364,7 +364,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ein_veraendertes_Archiv_faellt_auf()
|
||||
public async Task A_modified_archive_is_noticed()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
@@ -386,15 +386,15 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ein_Archiv_ohne_Manifest_wird_abgelehnt()
|
||||
public async Task An_archive_without_manifest_is_rejected()
|
||||
{
|
||||
var fremd = Path.Combine(_root, "fremd.zip");
|
||||
var quelle = Path.Combine(_root, "quelle");
|
||||
Directory.CreateDirectory(quelle);
|
||||
File.WriteAllText(Path.Combine(quelle, "irgendwas.txt"), "Inhalt");
|
||||
ZipFile.CreateFromDirectory(quelle, fremd);
|
||||
var foreign = Path.Combine(_root, "fremd.zip");
|
||||
var source = Path.Combine(_root, "quelle");
|
||||
Directory.CreateDirectory(source);
|
||||
File.WriteAllText(Path.Combine(source, "irgendwas.txt"), "Inhalt");
|
||||
ZipFile.CreateFromDirectory(source, foreign);
|
||||
|
||||
var ex = await Should.ThrowAsync<BackupException>(() => _service.InspectAsync(fremd));
|
||||
var ex = await Should.ThrowAsync<BackupException>(() => _service.InspectAsync(foreign));
|
||||
|
||||
ex.Message.ShouldContain("Manifest");
|
||||
}
|
||||
@@ -404,7 +404,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Die_Vorschau_schreibt_nichts()
|
||||
public async Task The_preview_writes_nothing()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
@@ -417,35 +417,35 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Vorhandene_Dateien_werden_ohne_Zustimmung_nicht_ueberschrieben()
|
||||
public async Task Existing_files_are_not_overwritten_without_consent()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
|
||||
Directory.CreateDirectory(RestoreDir);
|
||||
var vorhanden = Path.Combine(RestoreDir, "InstanceSettings.json");
|
||||
AtomicFile.WriteAllText(vorhanden, """{"wichtig":"nicht verlieren"}""");
|
||||
var present = Path.Combine(RestoreDir, "InstanceSettings.json");
|
||||
AtomicFile.WriteAllText(present, """{"wichtig":"nicht verlieren"}""");
|
||||
|
||||
var result = await _service.RestoreAsync(ZipPath, RestoreDir, new RestoreOptions());
|
||||
|
||||
result.HasConflicts.ShouldBeTrue();
|
||||
result.Skipped.ShouldContain("InstanceSettings.json");
|
||||
File.ReadAllText(vorhanden).ShouldContain("nicht verlieren");
|
||||
File.ReadAllText(present).ShouldContain("nicht verlieren");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Mit_Zustimmung_wird_ueberschrieben()
|
||||
public async Task With_consent_it_is_overwritten()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
|
||||
Directory.CreateDirectory(RestoreDir);
|
||||
var vorhanden = Path.Combine(RestoreDir, "InstanceSettings.json");
|
||||
AtomicFile.WriteAllText(vorhanden, """{"alt":true}""");
|
||||
var present = Path.Combine(RestoreDir, "InstanceSettings.json");
|
||||
AtomicFile.WriteAllText(present, """{"alt":true}""");
|
||||
|
||||
await _service.RestoreAsync(ZipPath, RestoreDir, new RestoreOptions { Overwrite = true });
|
||||
|
||||
File.ReadAllText(vorhanden).ShouldContain("Testinstanz");
|
||||
File.ReadAllText(present).ShouldContain("Testinstanz");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -453,7 +453,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public async Task Protokolle_bleiben_standardmaessig_aussen_vor()
|
||||
public async Task Logs_are_left_out_by_default()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
@@ -464,7 +464,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Protokolle_lassen_sich_einschliessen()
|
||||
public async Task Logs_can_be_included()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions { IncludeLogs = true });
|
||||
@@ -475,7 +475,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Die_WAL_Begleitdateien_landen_nicht_im_Archiv()
|
||||
public async Task The_WAL_side_files_do_not_end_up_in_the_archive()
|
||||
{
|
||||
// VACUUM INTO already incorporates them — backing them up too would be misleading.
|
||||
await BuildInstanceAsync();
|
||||
@@ -488,7 +488,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ein_fehlendes_Instanzverzeichnis_wird_gemeldet()
|
||||
public async Task A_missing_instance_directory_is_reported()
|
||||
{
|
||||
var ex = await Should.ThrowAsync<BackupException>(
|
||||
() => _service.CreateAsync(Path.Combine(_root, "gibtsnicht"), ZipPath, new BackupOptions()));
|
||||
@@ -497,7 +497,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Passphrase_Modus_ohne_Passphrase_wird_abgelehnt()
|
||||
public async Task Passphrase_mode_without_passphrase_is_rejected()
|
||||
{
|
||||
await BuildInstanceAsync();
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
catch { /* Cleaning up is secondary */ }
|
||||
}
|
||||
|
||||
private string Quelle(byte[] inhalt)
|
||||
private string Source(byte[] content)
|
||||
{
|
||||
var pfad = Datei("quelle.bin");
|
||||
File.WriteAllBytes(pfad, inhalt);
|
||||
return pfad;
|
||||
var path = Datei("quelle.bin");
|
||||
File.WriteAllBytes(path, content);
|
||||
return path;
|
||||
}
|
||||
|
||||
// ─── The container ───
|
||||
@@ -59,20 +59,20 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
/// without it being noticed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_Archiv_ueber_mehrere_Stuecke_kommt_unveraendert_zurueck()
|
||||
public void An_archive_spanning_several_chunks_comes_back_unchanged()
|
||||
{
|
||||
var inhalt = RandomNumberGenerator.GetBytes(2_600_000);
|
||||
var content = RandomNumberGenerator.GetBytes(2_600_000);
|
||||
|
||||
LicenseBackupCipher.Encrypt(Quelle(inhalt), "{}", Datei("a.clawdbak"), Lizenz);
|
||||
LicenseBackupCipher.Encrypt(Source(content), "{}", Datei("a.clawdbak"), Lizenz);
|
||||
LicenseBackupCipher.DecryptTo(Datei("a.clawdbak"), Datei("zurueck.bin"), Lizenz);
|
||||
|
||||
File.ReadAllBytes(Datei("zurueck.bin")).ShouldBe(inhalt);
|
||||
File.ReadAllBytes(Datei("zurueck.bin")).ShouldBe(content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Auch_ein_leeres_Archiv_kommt_zurueck()
|
||||
public void An_empty_archive_comes_back_too()
|
||||
{
|
||||
LicenseBackupCipher.Encrypt(Quelle([]), "{}", Datei("a.clawdbak"), Lizenz);
|
||||
LicenseBackupCipher.Encrypt(Source([]), "{}", Datei("a.clawdbak"), Lizenz);
|
||||
LicenseBackupCipher.DecryptTo(Datei("a.clawdbak"), Datei("zurueck.bin"), Lizenz);
|
||||
|
||||
File.ReadAllBytes(Datei("zurueck.bin")).ShouldBeEmpty();
|
||||
@@ -83,9 +83,9 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
/// above all before an instance is stopped for it.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_fremder_Schluessel_wird_erkannt_bevor_etwas_geschrieben_wird()
|
||||
public void A_foreign_key_is_recognised_before_anything_is_written()
|
||||
{
|
||||
LicenseBackupCipher.Encrypt(Quelle([1, 2, 3]), "{}", Datei("a.clawdbak"), Lizenz);
|
||||
LicenseBackupCipher.Encrypt(Source([1, 2, 3]), "{}", Datei("a.clawdbak"), Lizenz);
|
||||
|
||||
LicenseBackupCipher.MatchesKey(Datei("a.clawdbak"), "ANDERE-LIZENZ").ShouldBeFalse();
|
||||
LicenseBackupCipher.MatchesKey(Datei("a.clawdbak"), Lizenz).ShouldBeTrue();
|
||||
@@ -101,18 +101,18 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
/// space or a lowercase letter must not decide about "lost" there.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Schreibweise_und_Leerzeichen_des_Schluessels_spielen_keine_Rolle()
|
||||
public void Spelling_and_spaces_of_the_key_do_not_matter()
|
||||
{
|
||||
LicenseBackupCipher.Encrypt(Quelle([4, 5, 6]), "{}", Datei("a.clawdbak"), " clawd-abcd-1234 -efgh-5678 ");
|
||||
LicenseBackupCipher.Encrypt(Source([4, 5, 6]), "{}", Datei("a.clawdbak"), " clawd-abcd-1234 -efgh-5678 ");
|
||||
|
||||
LicenseBackupCipher.DecryptTo(Datei("a.clawdbak"), Datei("zurueck.bin"), Lizenz);
|
||||
File.ReadAllBytes(Datei("zurueck.bin")).ShouldBe([4, 5, 6]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Das_Manifest_ist_lesbar_ohne_das_Archiv_zu_entschluesseln()
|
||||
public void The_manifest_is_readable_without_decrypting_the_archive()
|
||||
{
|
||||
LicenseBackupCipher.Encrypt(Quelle(RandomNumberGenerator.GetBytes(3_000_000)),
|
||||
LicenseBackupCipher.Encrypt(Source(RandomNumberGenerator.GetBytes(3_000_000)),
|
||||
"""{"instanceName":"Alpha"}""", Datei("a.clawdbak"), Lizenz);
|
||||
|
||||
LicenseBackupCipher.ReadManifest(Datei("a.clawdbak"), Lizenz).ShouldContain("Alpha");
|
||||
@@ -123,26 +123,26 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
/// complete and get a ZIP that aborts somewhere when unpacking.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_fehlendes_letztes_Stueck_faellt_auf()
|
||||
public void A_missing_last_chunk_is_noticed()
|
||||
{
|
||||
var inhalt = RandomNumberGenerator.GetBytes(1024 * 1024 + 500);
|
||||
var content = RandomNumberGenerator.GetBytes(1024 * 1024 + 500);
|
||||
const string manifest = "{}";
|
||||
|
||||
LicenseBackupCipher.Encrypt(Quelle(inhalt), manifest, Datei("a.clawdbak"), Lizenz);
|
||||
LicenseBackupCipher.Encrypt(Source(content), manifest, Datei("a.clawdbak"), Lizenz);
|
||||
|
||||
// Header 8+1+16, manifest 12+4+16+len, first chunk 12+4+16+1 MiB - cut after that.
|
||||
var bisErstesStueck = 25 + (32 + Encoding.UTF8.GetByteCount(manifest)) + (32 + 1024 * 1024);
|
||||
var voll = File.ReadAllBytes(Datei("a.clawdbak"));
|
||||
File.WriteAllBytes(Datei("a.clawdbak"), voll[..bisErstesStueck]);
|
||||
var upToFirstChunk = 25 + (32 + Encoding.UTF8.GetByteCount(manifest)) + (32 + 1024 * 1024);
|
||||
var full = File.ReadAllBytes(Datei("a.clawdbak"));
|
||||
File.WriteAllBytes(Datei("a.clawdbak"), full[..upToFirstChunk]);
|
||||
|
||||
Should.Throw<BackupException>(() =>
|
||||
LicenseBackupCipher.DecryptTo(Datei("a.clawdbak"), Datei("zurueck.bin"), Lizenz));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Eine_Veraenderung_mitten_im_Archiv_faellt_auf()
|
||||
public void A_change_in_the_middle_of_the_archive_is_noticed()
|
||||
{
|
||||
LicenseBackupCipher.Encrypt(Quelle(RandomNumberGenerator.GetBytes(200_000)), "{}",
|
||||
LicenseBackupCipher.Encrypt(Source(RandomNumberGenerator.GetBytes(200_000)), "{}",
|
||||
Datei("a.clawdbak"), Lizenz);
|
||||
|
||||
var bytes = File.ReadAllBytes(Datei("a.clawdbak"));
|
||||
@@ -155,12 +155,12 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
|
||||
// ─── The backup as a whole ───
|
||||
|
||||
private string Instanz()
|
||||
private string Instance()
|
||||
{
|
||||
var pfad = Datei("Instance-Alpha");
|
||||
Directory.CreateDirectory(pfad);
|
||||
var path = Datei("Instance-Alpha");
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
AtomicFile.WriteAllText(Path.Combine(pfad, "InstanceSettings.json"), $$"""
|
||||
AtomicFile.WriteAllText(Path.Combine(path, "InstanceSettings.json"), $$"""
|
||||
{
|
||||
"instanceId": "alpha-1",
|
||||
"instanceName": "Alpha",
|
||||
@@ -168,14 +168,14 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
}
|
||||
""");
|
||||
|
||||
return pfad;
|
||||
return path;
|
||||
}
|
||||
|
||||
private async Task<string> SicherungAsync()
|
||||
private async Task<string> BackupAsync()
|
||||
{
|
||||
var ziel = Datei("backup_Alpha.clawdbak");
|
||||
|
||||
await new BackupService().CreateAsync(Instanz(), ziel, new BackupOptions
|
||||
await new BackupService().CreateAsync(Instance(), ziel, new BackupOptions
|
||||
{
|
||||
Secrets = SecretMode.License,
|
||||
LicenseKey = Lizenz
|
||||
@@ -190,20 +190,20 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
/// license key.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Eine_Sicherung_ueberlebt_den_Maschinenwechsel()
|
||||
public async Task A_backup_survives_the_machine_change()
|
||||
{
|
||||
var sicherung = await SicherungAsync();
|
||||
var backup = await BackupAsync();
|
||||
|
||||
SecretKeyStore.UseDirectory(Datei("maschine-b"));
|
||||
|
||||
var ziel = Datei("Wiederhergestellt");
|
||||
await new BackupService().RestoreAsync(sicherung, ziel,
|
||||
await new BackupService().RestoreAsync(backup, ziel,
|
||||
new RestoreOptions { LicenseKey = Lizenz, Overwrite = true });
|
||||
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(Path.Combine(ziel, "InstanceSettings.json")));
|
||||
var wert = doc.RootElement.GetProperty("openRouterApiKey").GetString();
|
||||
var value = doc.RootElement.GetProperty("openRouterApiKey").GetString();
|
||||
|
||||
SecretProtector.Unprotect(wert).ShouldBe(ApiKey);
|
||||
SecretProtector.Unprotect(value).ShouldBe(ApiKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,44 +211,44 @@ public sealed class LicenseBackupTests : IDisposable
|
||||
/// one at all.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Die_Sicherung_verraet_keinen_Klartext()
|
||||
public async Task The_backup_reveals_no_plaintext()
|
||||
{
|
||||
var sicherung = await SicherungAsync();
|
||||
var backup = await BackupAsync();
|
||||
|
||||
LicenseBackupCipher.IsEncrypted(sicherung).ShouldBeTrue();
|
||||
LicenseBackupCipher.IsEncrypted(backup).ShouldBeTrue();
|
||||
|
||||
var roh = Encoding.Latin1.GetString(File.ReadAllBytes(sicherung));
|
||||
roh.ShouldNotContain(ApiKey);
|
||||
roh.ShouldNotContain("openRouterApiKey");
|
||||
roh.ShouldNotContain("InstanceSettings");
|
||||
var raw = Encoding.Latin1.GetString(File.ReadAllBytes(backup));
|
||||
raw.ShouldNotContain(ApiKey);
|
||||
raw.ShouldNotContain("openRouterApiKey");
|
||||
raw.ShouldNotContain("InstanceSettings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// With a foreign key nothing is written — not half, not partially.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Mit_fremdem_Schluessel_bleibt_das_Ziel_unberuehrt()
|
||||
public async Task With_a_foreign_key_the_target_stays_untouched()
|
||||
{
|
||||
var sicherung = await SicherungAsync();
|
||||
var backup = await BackupAsync();
|
||||
var ziel = Datei("Ziel");
|
||||
Directory.CreateDirectory(ziel);
|
||||
|
||||
await Should.ThrowAsync<BackupKeyMismatchException>(() =>
|
||||
new BackupService().RestoreAsync(sicherung, ziel,
|
||||
new BackupService().RestoreAsync(backup, ziel,
|
||||
new RestoreOptions { LicenseKey = "ANDERE-LIZENZ", Overwrite = true }));
|
||||
|
||||
Directory.EnumerateFileSystemEntries(ziel).ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Die_Uebersicht_braucht_den_passenden_Schluessel()
|
||||
public async Task The_overview_needs_the_matching_key()
|
||||
{
|
||||
var sicherung = await SicherungAsync();
|
||||
var backup = await BackupAsync();
|
||||
|
||||
(await new BackupService().InspectAsync(sicherung, licenseKey: Lizenz))
|
||||
(await new BackupService().InspectAsync(backup, licenseKey: Lizenz))
|
||||
.InstanceName.ShouldBe("Alpha");
|
||||
|
||||
await Should.ThrowAsync<BackupKeyMismatchException>(() =>
|
||||
new BackupService().InspectAsync(sicherung, licenseKey: "ANDERE-LIZENZ"));
|
||||
new BackupService().InspectAsync(backup, licenseKey: "ANDERE-LIZENZ"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user