Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch
Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft. Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch. WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b umbenannten Parameter angeglichen. Dabei behoben: - 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue" gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day. - Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine CSS-Klasse gab - jetzt "success" wie laufende Instanzen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
da4ceac7d5
commit
837a634a22
@@ -10,11 +10,11 @@ using Shouldly;
|
||||
namespace ClawdDotNet.Core.Tests.Backup;
|
||||
|
||||
/// <summary>
|
||||
/// Ein ungeprüftes Wiederherstellen ist kein Backup, sondern eine Vermutung.
|
||||
/// Deshalb liegt der Schwerpunkt hier auf dem vollständigen Rundlauf.
|
||||
/// An unchecked restore is not a backup, but a guess.
|
||||
/// That's why the focus here is on the complete round trip.
|
||||
///
|
||||
/// Läuft in <see cref="SecretKeyCollection"/>, weil das Umschreiben der Geheimnisse
|
||||
/// beim Sichern denselben statischen Schlüssel benutzt wie die Geheimnistests.
|
||||
/// Runs in <see cref="SecretKeyCollection"/>, because rewriting the secrets
|
||||
/// when backing up uses the same static key as the secret tests.
|
||||
/// </summary>
|
||||
[Collection(SecretKeyCollection.Name)]
|
||||
public sealed class BackupServiceTests : IDisposable
|
||||
@@ -37,14 +37,14 @@ public sealed class BackupServiceTests : IDisposable
|
||||
{
|
||||
ClawdDotNet.Core.Storage.SqliteStorage.ReleasePooledConnections(_root);
|
||||
try { Directory.Delete(_root, recursive: true); }
|
||||
catch { /* Aufräumen ist Nebensache */ }
|
||||
catch { /* Cleaning up is secondary */ }
|
||||
}
|
||||
|
||||
// ─── Aufbau einer realistischen Instanz ───
|
||||
// ─── Building a realistic instance ───
|
||||
|
||||
private async Task BuildInstanceAsync()
|
||||
{
|
||||
// Instanzkonfiguration — Zugangsdaten liegen DPAPI-geschützt wie im Betrieb.
|
||||
// Instance configuration — credentials are DPAPI-protected as in operation.
|
||||
var instanceSettings = $$"""
|
||||
{
|
||||
"instanceId": "test-01",
|
||||
@@ -81,7 +81,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
AtomicFile.WriteAllText(Path.Combine(agentDir, "Workspace", "bericht.md"), "# Bericht\nInhalt.");
|
||||
AtomicFile.WriteAllText(Path.Combine(agentDir, "Logs", "lauf.log"), "viele Zeilen Protokoll");
|
||||
|
||||
// Datenbank mit einer Erinnerung — der wertvollste Teil.
|
||||
// Database with a memory — the most valuable part.
|
||||
var storage = new SqliteStorage(Path.Combine(_instanceDir, "state.db"));
|
||||
var memory = new SqliteMemoryRepository(storage);
|
||||
await memory.RememberAsync(new MemoryEntry
|
||||
@@ -94,7 +94,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
CreatedBy = "analyst"
|
||||
}, default);
|
||||
|
||||
// Verbindungen schließen, damit VACUUM INTO auf eine ruhige Datei trifft.
|
||||
// Close connections so VACUUM INTO meets a quiet file.
|
||||
ClawdDotNet.Core.Storage.SqliteStorage.ReleasePooledConnections(_instanceDir);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
private string RestoreDir => Path.Combine(_root, "wiederhergestellt");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Der Rundlauf
|
||||
// The round trip
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -118,17 +118,17 @@ public sealed class BackupServiceTests : IDisposable
|
||||
|
||||
result.Written.ShouldNotBeEmpty();
|
||||
|
||||
// Persönlichkeit
|
||||
// Personality
|
||||
File.ReadAllText(Path.Combine(RestoreDir, "Agents", "Agent-Analyst", "Identity.md"))
|
||||
.ShouldContain("Der Analyst");
|
||||
File.ReadAllText(Path.Combine(RestoreDir, "Agents", "Agent-Analyst", "Soul.md"))
|
||||
.ShouldContain("Gründlich");
|
||||
|
||||
// Arbeitsstand
|
||||
// Work state
|
||||
File.ReadAllText(Path.Combine(RestoreDir, "Agents", "Agent-Analyst", "Workspace", "bericht.md"))
|
||||
.ShouldContain("Inhalt");
|
||||
|
||||
// Datenbank samt Gedächtnis
|
||||
// Database including memory
|
||||
File.Exists(Path.Combine(RestoreDir, "state.db")).ShouldBeTrue();
|
||||
|
||||
var memory = new SqliteMemoryRepository(new SqliteStorage(Path.Combine(RestoreDir, "state.db")));
|
||||
@@ -142,8 +142,8 @@ public sealed class BackupServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task Zugangsdaten_sind_nach_dem_Wiederherstellen_wieder_nutzbar()
|
||||
{
|
||||
// Der eigentliche Zweck der Passphrase: Ein Backup wird gebraucht, wenn der
|
||||
// Rechner defekt ist — DPAPI-geschützte Werte wären dann unlesbar.
|
||||
// The actual purpose of the passphrase: a backup is needed when the
|
||||
// machine is broken — DPAPI-protected values would then be unreadable.
|
||||
await BuildInstanceAsync();
|
||||
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
@@ -160,17 +160,17 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Der Umzug auf einen anderen Rechner — der Migrationspfad aus #190.
|
||||
/// The move to another machine — the migration path from #190.
|
||||
///
|
||||
/// <para>Die Tests darüber sichern und stellen im selben Lauf wieder her und
|
||||
/// benutzen dabei denselben Schlüssel. Sie können damit gar nicht zeigen, worauf es
|
||||
/// beim Umzug ankommt: dass die Werte auch dann lesbar sind, wenn der Schlüssel der
|
||||
/// Quellmaschine <b>nicht</b> mitkommt. Genau das ist der Fall Windows → Linux —
|
||||
/// die bestehenden Werte sind DPAPI-verschlüsselt, und
|
||||
/// <c>SecretProtector.UnprotectV1</c> wirft dafür auf Linux bewusst.</para>
|
||||
/// <para>The tests above back up and restore in the same run and
|
||||
/// use the same key in doing so. They therefore cannot show what matters
|
||||
/// when moving: that the values are readable even if the key of the
|
||||
/// source machine does <b>not</b> come along. Exactly that is the case Windows → Linux —
|
||||
/// the existing values are DPAPI-encrypted, and
|
||||
/// <c>SecretProtector.UnprotectV1</c> deliberately throws for them on Linux.</para>
|
||||
///
|
||||
/// <para>Hier wechselt der Schlüssel deshalb mitten im Test. Ohne den Wechsel wäre
|
||||
/// der Test grün, ohne etwas zu belegen.</para>
|
||||
/// <para>Here the key therefore changes in the middle of the test. Without the change
|
||||
/// the test would be green without proving anything.</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Der_Umzug_auf_einen_anderen_Rechner_traegt()
|
||||
@@ -179,15 +179,15 @@ public sealed class BackupServiceTests : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Maschine 1: Werte liegen unter dem hiesigen Schlüssel.
|
||||
// Machine 1: values lie under the local key.
|
||||
await BuildInstanceAsync();
|
||||
var schluesselDerQuelle = File.ReadAllBytes(SecretKeyStore.KeyFilePath);
|
||||
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
new BackupOptions { Secrets = SecretMode.Passphrase, Passphrase = "umzug" });
|
||||
|
||||
// Maschine 2: anderer Rechner, anderer Schlüssel. Der Schlüssel reist nicht
|
||||
// mit — er liegt bewusst außerhalb des Instanzverzeichnisses.
|
||||
// Machine 2: different machine, different key. The key does not travel
|
||||
// along — it deliberately lies outside the instance directory.
|
||||
SecretKeyStore.UseDirectory(zielMaschine);
|
||||
|
||||
await _service.RestoreAsync(ZipPath, RestoreDir, new RestoreOptions { Passphrase = "umzug" });
|
||||
@@ -201,7 +201,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
var stored = settings.RootElement.GetProperty("openRouterApiKey").GetString();
|
||||
SecretProtector.Unprotect(stored).ShouldBe(ApiKeyPlain);
|
||||
|
||||
// Auch die verschachtelten Werte, nicht nur der offensichtliche oben.
|
||||
// The nested values too, not only the obvious one at the top.
|
||||
var agent = JsonDocument.Parse(File.ReadAllText(
|
||||
Path.Combine(RestoreDir, "Agents", "Agent-Analyst", "AgentSettings.json")));
|
||||
|
||||
@@ -212,22 +212,22 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Ein eigenes Verzeichnis für den nächsten Test der Sammlung: Das der
|
||||
// Quellmaschine steht hier nicht mehr zur Verfügung, und der echte
|
||||
// Benutzerschlüssel darf es erst recht nicht sein.
|
||||
// A directory of its own for the next test of the collection: that of the
|
||||
// source machine is no longer available here, and the real
|
||||
// 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 { /* Nebensache */ }
|
||||
try { Directory.Delete(zielMaschine, recursive: true); } catch { /* secondary */ }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Ohne_die_Passphrase_ist_der_Umzug_verloren()
|
||||
{
|
||||
// Die Kehrseite, und der Grund, warum die Passphrase beim Sichern angeboten
|
||||
// werden muss: Ohne sie ist ein Backup auf der Zielmaschine wertlos, was die
|
||||
// Zugangsdaten angeht. Das ist kein Fehler, sondern der Preis dafür, dass der
|
||||
// Schlüssel nicht mitreist — aber es muss auffallen, statt still zu geschehen.
|
||||
// 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"));
|
||||
|
||||
try
|
||||
@@ -246,7 +246,7 @@ 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 { /* Nebensache */ }
|
||||
try { Directory.Delete(zielMaschine, recursive: true); } catch { /* secondary */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +271,8 @@ public sealed class BackupServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task Unbekannte_Felder_ueberstehen_den_Rundlauf()
|
||||
{
|
||||
// Eine Sicherung darf nichts wegwerfen, nur weil eine Programmfassung ein
|
||||
// Feld nicht kennt.
|
||||
// A backup must not throw anything away just because a program version does not know
|
||||
// a field.
|
||||
await BuildInstanceAsync();
|
||||
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
@@ -284,7 +284,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Zugangsdaten weglassen
|
||||
// Leaving out credentials
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -295,7 +295,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
await _service.CreateAsync(_instanceDir, ZipPath,
|
||||
new BackupOptions { Secrets = SecretMode.Exclude });
|
||||
|
||||
// Das gesamte Archiv im Klartext durchsuchen.
|
||||
// Search the entire archive in plain text.
|
||||
var inhalt = ReadWholeArchive(ZipPath);
|
||||
|
||||
inhalt.ShouldNotContain(ApiKeyPlain);
|
||||
@@ -318,7 +318,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Manifest und Fehlerfälle
|
||||
// Manifest and error cases
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -369,7 +369,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
|
||||
// Eine Datei im Archiv nachträglich verändern.
|
||||
// Change a file in the archive afterwards.
|
||||
using (var archive = ZipFile.Open(ZipPath, ZipArchiveMode.Update))
|
||||
{
|
||||
var entry = archive.Entries.First(e => e.FullName.EndsWith("Identity.md"));
|
||||
@@ -400,7 +400,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Vorschau und Überschreiben
|
||||
// Preview and overwriting
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -449,7 +449,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Umfang
|
||||
// Scope
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -477,7 +477,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
[Fact]
|
||||
public async Task Die_WAL_Begleitdateien_landen_nicht_im_Archiv()
|
||||
{
|
||||
// VACUUM INTO arbeitet sie bereits ein — mitzusichern wäre irreführend.
|
||||
// VACUUM INTO already incorporates them — backing them up too would be misleading.
|
||||
await BuildInstanceAsync();
|
||||
await _service.CreateAsync(_instanceDir, ZipPath, new BackupOptions());
|
||||
|
||||
@@ -505,7 +505,7 @@ public sealed class BackupServiceTests : IDisposable
|
||||
_instanceDir, ZipPath, new BackupOptions { Secrets = SecretMode.Passphrase }));
|
||||
}
|
||||
|
||||
// ─── Helfer ───
|
||||
// ─── Helpers ───
|
||||
|
||||
private static string ReadWholeArchive(string zipPath)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user