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:
Richard
2026-10-04 12:45:03 +02:00
co-authored by Claude Opus 5.5
parent da4ceac7d5
commit 837a634a22
311 changed files with 7238 additions and 7252 deletions
@@ -4,14 +4,14 @@ using ClawdDotNet.Core.Api.Models;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Prüft, ob eine Nachrichtenfolge von der Chat-Completions-API akzeptiert würde.
/// Checks whether a message sequence would be accepted by the chat completions API.
///
/// Die Regeln entsprechen dem, was OpenRouter/Anthropic/OpenAI verlangen:
/// Eine tool-Antwort ist nur gültig, wenn ihr eine assistant-Nachricht mit einem
/// passenden tool_call vorausgeht — und jeder tool_call braucht seine Antwort.
/// The rules correspond to what OpenRouter/Anthropic/OpenAI demand:
/// a tool answer is only valid if it is preceded by an assistant message with a
/// matching tool_call — and every tool_call needs its answer.
///
/// Wird eine Regel verletzt, antwortet die API mit HTTP 400 und der laufende
/// Agent bricht ab. Genau das ist Bug B1 aus der Bestandsaufnahme.
/// If a rule is violated, the API answers with HTTP 400 and the running
/// agent aborts. That is exactly bug B1 from the inventory.
/// </summary>
internal static class ContextInvariants
{
@@ -37,22 +37,22 @@ internal static class ContextInvariants
private static IEnumerable<string> Validate(IReadOnlyList<ChatMessage> messages)
{
// ── Regel 1: höchstens eine system-Nachricht, und zwar ganz vorne ──
// ── Rule 1: at most one system message, and at the very front ──
for (var i = 1; i < messages.Count; i++)
{
if (messages[i].Role == "system")
yield return $"[{i}] system-Nachricht steht nicht an Position 0";
}
// ── Regel 2: jede tool-Nachricht braucht eine ToolCallId ──
// ── Rule 2: every tool message needs a ToolCallId ──
for (var i = 0; i < messages.Count; i++)
{
if (messages[i].Role == "tool" && string.IsNullOrEmpty(messages[i].ToolCallId))
yield return $"[{i}] tool-Nachricht ohne tool_call_id";
}
// ── Regel 3: jede tool-Nachricht gehört zum unmittelbar vorausgehenden
// assistant-Block mit passender tool_call-Id ──
// ── Rule 3: every tool message belongs to the immediately preceding
// assistant block with a matching tool_call ID ──
var openCalls = new HashSet<string>();
var answered = new HashSet<string>();
@@ -62,7 +62,7 @@ internal static class ContextInvariants
if (msg.Role == "assistant" && msg.ToolCalls is { Count: > 0 })
{
// Vorheriger Block muss vollständig beantwortet sein
// The previous block must be completely answered
var unanswered = openCalls.Except(answered).ToList();
if (unanswered.Count > 0)
yield return $"[{i}] neuer assistant-Block, aber tool_call(s) noch unbeantwortet: {string.Join(", ", unanswered)}";
@@ -87,7 +87,7 @@ internal static class ContextInvariants
}
else
{
// user/system/assistant-ohne-tool_calls beenden den Block
// user/system/assistant-without-tool_calls end the block
var unanswered = openCalls.Except(answered).ToList();
if (unanswered.Count > 0)
yield return $"[{i}] {msg.Role}-Nachricht, aber tool_call(s) unbeantwortet: {string.Join(", ", unanswered)}";
@@ -97,13 +97,13 @@ internal static class ContextInvariants
}
}
// ── Regel 4: am Ende darf kein tool_call offen sein ──
// ── Rule 4: no tool_call may be open at the end ──
var stillOpen = openCalls.Except(answered).ToList();
if (stillOpen.Count > 0)
yield return $"[Ende] unbeantwortete tool_call(s): {string.Join(", ", stillOpen)}";
}
/// <summary>Kompakte, lesbare Darstellung für Fehlermeldungen.</summary>
/// <summary>Compact, readable representation for error messages.</summary>
public static string Describe(IReadOnlyList<ChatMessage> messages)
{
var sb = new StringBuilder();
@@ -3,7 +3,7 @@ using ClawdDotNet.Core.Api.Models;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Baut gültige Nachrichtenfolgen für Tests — lesbar und garantiert API-konform.
/// Builds valid message sequences for tests — readable and guaranteed API-compliant.
/// </summary>
internal sealed class Conversation
{
@@ -31,8 +31,8 @@ internal sealed class Conversation
}
/// <summary>
/// Fügt einen vollständigen Tool-Zyklus hinzu: assistant mit N tool_calls,
/// gefolgt von genau N passenden tool-Antworten.
/// Adds a complete tool cycle: assistant with N tool_calls,
/// followed by exactly N matching tool answers.
/// </summary>
public Conversation ToolCycle(int count = 1, int resultLength = 50, string toolName = "TestTool")
{
@@ -54,7 +54,7 @@ internal sealed class Conversation
return this;
}
/// <summary>Wiederholt ein Muster aus User-Nachricht und Tool-Zyklus.</summary>
/// <summary>Repeats a pattern of user message and tool cycle.</summary>
public Conversation Repeat(int times, int toolCallsPerCycle = 1, int resultLength = 50)
{
for (var i = 0; i < times; i++)
@@ -5,17 +5,17 @@ using ClawdDotNet.Core.Tools;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Ein Tool, das einen anderen Agenten beauftragt — der Nachbau dessen, was
/// <c>AgentComm.send_message</c> und <c>AgentSpawn.spawn_agent</c> tun.
/// A tool that commissions another agent — the replica of what
/// <c>AgentComm.send_message</c> and <c>AgentSpawn.spawn_agent</c> do.
///
/// <para>Der Nachbau ist nötig, weil beide Werkzeuge in eigenen Projekten liegen, die
/// <c>ClawdDotNet.Core.Tests</c> nicht referenziert. Entscheidend ist ohnehin nur die
/// eine Zeile, in der sie sich hier gleichen: Die Delegationskette des eigenen Laufs
/// wird weitergereicht. Ein Tool, das sie unterschlägt, macht aus einem Zyklus eine
/// neue Beauftragung — genau der Fall, den B8 beschreibt.</para>
/// <para>The replica is necessary because both tools lie in projects of their own that
/// <c>ClawdDotNet.Core.Tests</c> does not reference. All that matters anyway is the
/// one line in which they are the same here: the delegation chain of the own run
/// is passed on. A tool that suppresses it turns a cycle into a
/// new commission — exactly the case B8 describes.</para>
///
/// <para>Wen ein Agent beauftragt, steht in <paramref name="routes"/>: agentId → Ziel.
/// Wer dort nicht vorkommt, delegiert nicht und antwortet schlicht.</para>
/// <para>Whom an agent commissions is in <paramref name="routes"/>: agentId → target.
/// Whoever does not appear there does not delegate and simply answers.</para>
/// </summary>
internal sealed class DelegatingTool : IAgentTool
{
@@ -30,13 +30,13 @@ internal sealed class DelegatingTool : IAgentTool
{ "type": "object", "properties": { "message": { "type": "string" } } }
""").RootElement.Clone();
/// <summary>Was zurückkam — Aufrufer, Ziel und das Ergebnis der Weitergabe.</summary>
/// <summary>What came back — caller, target and the result of the hand-off.</summary>
public ConcurrentBag<(string From, string To, bool Delivered, string? Error)> Handoffs { get; } = new();
/// <param name="delayBeforeHandoff">
/// Wartezeit, bevor weitergegeben wird. Der Fake-Client antwortet synchron; ohne
/// diese Pause läuft ein Chat vollständig durch, bevor der nächste überhaupt
/// beginnt, und zwei Ketten können sich nicht überschneiden.
/// Waiting time before handing off. The fake client answers synchronously; without
/// this pause a chat runs through completely before the next one even
/// begins, and two chains cannot overlap.
/// </param>
public DelegatingTool(
IReadOnlyDictionary<string, string> routes,
@@ -74,8 +74,8 @@ internal sealed class DelegatingTool : IAgentTool
Handoffs.Add((context.AgentId, target, result.Delivered, result.Error));
// Wie das echte Tool: Eine Ablehnung ist kein Wurf, sondern ein Ergebnis, das
// der Agent lesen und auf das er anders reagieren kann.
// Like the real tool: a rejection is not a throw, but a result that
// the agent can read and react to differently.
return result.Delivered
? ToolResult.Ok(result.Response ?? "")
: ToolResult.Ok(JsonSerializer.Serialize(new { delivered = false, error = result.Error }));
@@ -9,8 +9,8 @@ using ClawdDotNet.Core.Tools;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Baut eine einsatzbereite AgentEngine mit Fake-Client, Fake-Tools und
/// In-Memory-State zusammen.
/// Assembles a ready-to-use AgentEngine with fake client, fake tools and
/// in-memory state.
/// </summary>
internal sealed class EngineFixture
{
@@ -21,7 +21,7 @@ internal sealed class EngineFixture
private readonly List<AgentConfig> _agents = new();
/// <summary>Die registrierten Agenten — für Tests, die einen Dispatcher selbst bauen.</summary>
/// <summary>The registered agents — for tests that build a dispatcher themselves.</summary>
public IReadOnlyList<AgentConfig> Agents => _agents;
public EngineFixture(
@@ -44,7 +44,7 @@ internal sealed class EngineFixture
TranscriptStore = transcripts
};
// Kein Agent-Verzeichnis → keine Persistenz auf die Platte während der Tests.
// No agent directory → no persistence to disk during the tests.
Engine.SetAgentConfigProvider(() => _agents, "test-instance", _ => null);
}
@@ -67,7 +67,7 @@ internal sealed class EngineFixture
MaxSteps = 50,
MaxCumulativeTokens = 10_000_000,
TimeoutSeconds = 60,
MaxContextTokens = 10_000_000 // Compaction in Engine-Tests ausschalten
MaxContextTokens = 10_000_000 // switch off compaction in engine tests
}
};
@@ -5,12 +5,12 @@ using ClawdDotNet.Core.Api.Models;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Ersetzt den OpenRouterClient in Tests. Liefert eine vorprogrammierte Antwortfolge
/// und schreibt jeden empfangenen Request mit.
/// Replaces the OpenRouterClient in tests. Delivers a preprogrammed sequence of answers
/// and records every request received.
///
/// Wichtig: Requests werden tief kopiert. Die Engine reicht dieselbe List&lt;ChatMessage&gt;
/// weiter und verändert sie danach — ohne Kopie würden Tests den Endzustand prüfen
/// statt dessen, was tatsächlich gesendet wurde.
/// Important: requests are deep-copied. The engine passes on the same List&lt;ChatMessage&gt;
/// and changes it afterwards — without a copy tests would check the final state
/// instead of what was actually sent.
/// </summary>
internal sealed class FakeChatClient : IChatCompletionClient
{
@@ -18,12 +18,12 @@ internal sealed class FakeChatClient : IChatCompletionClient
private Func<ChatRequest, ChatResponse>? _fallback;
private readonly Lock _gate = new();
/// <summary>Alle empfangenen Requests, als tiefe Kopien.</summary>
/// <summary>All requests received, as deep copies.</summary>
public List<ChatRequest> ReceivedRequests { get; } = new();
public int CallCount => ReceivedRequests.Count;
// ─── Programmierung der Antworten ───
// ─── Programming the answers ───
public FakeChatClient RespondsWithText(string text, Usage? usage = null)
{
@@ -42,7 +42,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
private static ChatResponse ToolCallResponse((string Tool, string Args, string? Id)[] calls)
{
// Ids müssen pro Antwort eindeutig sein, sonst kollidieren parallele Läufe.
// IDs must be unique per answer, otherwise parallel runs collide.
var toolCalls = calls.Select(c => new ToolCall
{
Id = c.Id ?? "call_" + Guid.NewGuid().ToString("N")[..12],
@@ -58,7 +58,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
};
}
/// <summary>Antwortet mit einer bestimmten Prompt-Token-Zahl — für Compaction-Schwellen.</summary>
/// <summary>Answers with a particular prompt token count — for compaction thresholds.</summary>
public FakeChatClient RespondsWithTokens(int promptTokens, string text = "fertig")
{
_responses.Enqueue(_ => TextResponse(text, new Usage
@@ -77,7 +77,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
}
/// <summary>
/// Antwortet mit einem an der Ausgabegrenze abgeschnittenen Text (#548).
/// Answers with a text cut off at the output limit (#548).
/// </summary>
public FakeChatClient RespondsWithTruncatedText(string text, string finishReason = "length")
{
@@ -99,8 +99,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
}
/// <summary>
/// Antwortet mit Werkzeugaufrufen UND Begleittext bzw. Denkverlauf — der Fall, den
/// #547 und #549 betreffen.
/// Answers with tool calls AND accompanying text or reasoning — the case
/// #547 and #549 concern.
/// </summary>
public FakeChatClient RespondsWithToolCallAndContent(
string toolName,
@@ -122,7 +122,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
return this;
}
/// <summary>Antwort für alle Aufrufe, die über die programmierte Folge hinausgehen.</summary>
/// <summary>Answer for all calls that go beyond the programmed sequence.</summary>
public FakeChatClient AlwaysRespondsWithText(string text)
{
_fallback = _ => TextResponse(text);
@@ -136,9 +136,9 @@ internal sealed class FakeChatClient : IChatCompletionClient
}
/// <summary>
/// Antwortet passend zum jeweiligen Gesprächsstand: nach einer Tool-Antwort
/// kommt Text, sonst ein Tool-Aufruf. Damit verhält sich der Fake auch dann
/// sinnvoll, wenn mehrere Konversationen ineinander verschränkt laufen.
/// Answers matching the respective state of the conversation: after a tool answer
/// comes text, otherwise a tool call. That way the fake behaves sensibly
/// even when several conversations run interleaved.
/// </summary>
public FakeChatClient RespondsContextually(string toolName = "TestTool")
{
@@ -154,8 +154,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
{
ct.ThrowIfCancellationRequested();
// Erst kopieren, dann auswerten: Die Engine mutiert die Original-Liste
// weiter, und bei Nebenläufigkeitstests soll der Fake daran nicht scheitern.
// Copy first, then evaluate: the engine keeps mutating the original list,
// and in concurrency tests the fake should not fail because of that.
var snapshot = DeepClone(request);
lock (_gate)
ReceivedRequests.Add(snapshot);
@@ -172,7 +172,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
return Task.FromResult(responder(snapshot));
}
// ─── Helfer ───
// ─── Helpers ───
private static ChatResponse TextResponse(string text, Usage? usage = null) => new()
{
@@ -189,8 +189,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
MaxTokens = request.MaxTokens,
ToolChoice = request.ToolChoice,
Tools = request.Tools?.ToList(),
// Die Gateway-Felder gehören mitkopiert, sonst prüft ein Test die Härtung
// am Wire-Format vorbei — dieselbe Falle wie bei CacheBreakpoint.
// The gateway fields must be copied too, otherwise a test checks the hardening
// past the wire format — the same trap as with CacheBreakpoint.
Provider = request.Provider,
Usage = request.Usage,
Reasoning = request.Reasoning,
@@ -202,7 +202,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
Role = m.Role,
Content = m.Content,
ToolCallId = m.ToolCallId,
// Muss mitkopiert werden, sonst prüfen Caching-Tests am Wire-Format vorbei.
// Must be copied too, otherwise caching tests check past the wire format.
CacheBreakpoint = m.CacheBreakpoint,
Reasoning = m.Reasoning,
ReasoningDetails = m.ReasoningDetails,
@@ -3,7 +3,7 @@ using System.Net;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Liefert vorprogrammierte HTTP-Antworten, ohne dass ein Netzwerkzugriff stattfindet.
/// Delivers preprogrammed HTTP responses without any network access taking place.
/// </summary>
internal sealed class FakeHttpMessageHandler : HttpMessageHandler
{
@@ -1,9 +1,9 @@
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Ein steuerbarer <see cref="TimeProvider"/> für die Zeit-abhängigen Tests (R2 der
/// Teststrategie). Bewusst schlank: die Tests treiben den Scanner über
/// <c>ScanOnceAsync</c> direkt, sie brauchen nur eine feste, verstellbare Uhr.
/// A controllable <see cref="TimeProvider"/> for the time-dependent tests (R2 of the
/// test strategy). Deliberately lean: the tests drive the scanner directly via
/// <c>ScanOnceAsync</c>, they only need a fixed, adjustable clock.
/// </summary>
internal sealed class FakeTimeProvider : TimeProvider
{
@@ -5,8 +5,8 @@ using ClawdDotNet.Core.Tools;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Konfigurierbares Tool für Engine-Tests: liefert feste Ergebnisse, kann verzögern
/// (um Nebenläufigkeit zu provozieren) oder werfen.
/// Configurable tool for engine tests: delivers fixed results, can delay
/// (to provoke concurrency) or throw.
/// </summary>
internal sealed class FakeTool : IAgentTool
{
@@ -19,18 +19,18 @@ internal sealed class FakeTool : IAgentTool
{ "type": "object", "properties": { "action": { "type": "string" } } }
""").RootElement.Clone();
/// <summary>Jeder Aufruf wird mitgeschrieben — inklusive der aufrufenden Agent-Id.</summary>
/// <summary>Every call is recorded — including the calling agent ID.</summary>
public ConcurrentBag<string> Invocations { get; } = new();
/// <summary>Ob das Werkzeug sich für gleichzeitige Ausführung freigibt (#551).</summary>
/// <summary>Whether the tool declares itself fit for simultaneous execution (#551).</summary>
public bool IsParallelSafe { get; private init; }
/// <summary>
/// Höchste Zahl gleichzeitig laufender Aufrufe, die dieses Werkzeug gesehen hat.
/// Highest number of simultaneously running calls this tool has seen.
///
/// Das ist der einzige Nachweis, der zählt: Eine kürzere Gesamtlaufzeit könnte auch
/// von einer schnellen Maschine kommen, aber ein Höchststand von 2 kann nur
/// entstehen, wenn zwei Aufrufe einander tatsächlich überlappt haben.
/// That is the only proof that counts: a shorter total runtime could also
/// come from a fast machine, but a peak of 2 can only
/// arise if two calls actually overlapped each other.
/// </summary>
public int MaxConcurrent => _maxConcurrent;
@@ -44,8 +44,8 @@ internal sealed class FakeTool : IAgentTool
}
/// <summary>
/// Ein langsames Werkzeug, das sich für Nebenläufigkeit freigibt und mitzählt,
/// wie viele Aufrufe sich überlappt haben.
/// A slow tool that declares itself fit for concurrency and counts
/// how many calls overlapped.
/// </summary>
public static FakeTool SlowParallelSafe(TimeSpan delay, string result = "ok", string name = "ParallelTool")
=> new(name, async (_, _, ct) =>
@@ -55,7 +55,7 @@ internal sealed class FakeTool : IAgentTool
})
{ IsParallelSafe = true };
/// <summary>Dasselbe, aber ohne Freigabe — muss seriell bleiben.</summary>
/// <summary>The same, but without declaring itself fit — must stay serial.</summary>
public static FakeTool SlowSerial(TimeSpan delay, string result = "ok", string name = "SerialTool")
=> new(name, async (_, _, ct) =>
{
@@ -66,7 +66,7 @@ internal sealed class FakeTool : IAgentTool
public static FakeTool Returning(string result, string name = "TestTool")
=> new(name, (_, _, _) => Task.FromResult(ToolResult.Ok(result)));
/// <summary>Verzögert die Ausführung — vergrößert das Zeitfenster für Races.</summary>
/// <summary>Delays execution — enlarges the time window for races.</summary>
public static FakeTool Slow(TimeSpan delay, string result = "ok", string name = "TestTool")
=> new(name, async (_, _, ct) =>
{
@@ -74,7 +74,7 @@ internal sealed class FakeTool : IAgentTool
return ToolResult.Ok(result);
});
/// <summary>Scheitert jedes Mal mit derselben Meldung - fuer die Wiederholungswache (#581).</summary>
/// <summary>Fails every time with the same message - for the repetition guard (#581).</summary>
public static FakeTool Failing(string error, string name = "TestTool")
=> new(name, (_, _, _) => Task.FromResult(ToolResult.Fail(error)));
@@ -95,8 +95,8 @@ internal sealed class FakeTool : IAgentTool
var now = Interlocked.Increment(ref _current);
// Höchststand fortschreiben, ohne ihn je zu senken: Vergleichen und Tauschen,
// bis der gespeicherte Wert nicht mehr kleiner ist.
// Advance the peak without ever lowering it: compare and swap
// until the stored value is no longer smaller.
int seen;
while (now > (seen = Volatile.Read(ref _maxConcurrent)))
Interlocked.CompareExchange(ref _maxConcurrent, now, seen);
@@ -4,8 +4,8 @@ using ClawdDotNet.Core.Audit;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// In-Memory-Attrappe des Audit-Logs für Engine-Tests — hält Einträge und Belege in
/// Listen, damit ein Test prüfen kann, was die Engine stempelt, ohne eine Datei.
/// In-memory fake of the audit log for engine tests — keeps entries and receipts in
/// lists, so a test can check what the engine stamps, without a file.
/// </summary>
internal sealed class InMemoryAuditRepository : IAuditRepository
{
@@ -46,7 +46,7 @@ internal sealed class InMemoryAuditRepository : IAuditRepository
public Task<int> CountAsync(CancellationToken ct) => Task.FromResult(Entries.Count);
/// <summary>Spiegelt die SQL-Regel: Started ohne späteren Abschluss derselben CallId.</summary>
/// <summary>Mirrors the SQL rule: Started without a later completion of the same CallId.</summary>
public Task<IReadOnlyList<AuditEntry>> ListOpenCallsAsync(CancellationToken ct)
{
var alle = Entries.ToList();
@@ -3,7 +3,7 @@ using ClawdDotNet.Core.Staging;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>In-Memory-Attrappe der Staging-Warteschlange für Engine-Tests.</summary>
/// <summary>In-memory fake of the staging queue for engine tests.</summary>
internal sealed class InMemoryStagingRepository : IStagingRepository
{
private readonly ConcurrentDictionary<long, StagedCall> _calls = new();
@@ -89,7 +89,7 @@ internal sealed class InMemoryStagingRepository : IStagingRepository
=> Task.FromResult<IReadOnlyList<StagedCall>>(
_calls.Values.Where(c => c.Approver is not null && c.CreatedAt >= since).OrderBy(c => c.Id).ToList());
/// <summary>Verschiebt den Anlagezeitpunkt eines Vorschlags in die Vergangenheit - fuer Verfallstests.</summary>
/// <summary>Moves the creation time of a proposal into the past - for expiry tests.</summary>
public void Altern(long id, TimeSpan um)
{
if (_calls.TryGetValue(id, out var call))
@@ -1,15 +1,15 @@
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Ein Test, der nur unter Linux etwas aussagt.
/// A test that only says something under Linux.
///
/// Nötig geworden mit der Linux-Portierung: Groß-/Kleinschreibung und symbolische
/// Verknüpfungen verhalten sich je Dateisystem unterschiedlich. Solche Fälle unter
/// Windows mitlaufen zu lassen, würde entweder fehlschlagen oder — schlimmer — grün
/// sein, ohne das Gemeinte geprüft zu haben.
/// Became necessary with the Linux port: upper/lower case and symbolic
/// links behave differently per file system. Letting such cases run along under
/// Windows would either fail or — worse — be green
/// without having checked what was meant.
///
/// Bewusst ein Übersprung mit Begründung statt eines stillen <c>return</c>: Im
/// Testbericht ist dann sichtbar, dass hier etwas <em>nicht</em> geprüft wurde.
/// Deliberately a skip with a reason instead of a silent <c>return</c>: in the
/// test report it is then visible that something was <em>not</em> checked here.
/// </summary>
public sealed class LinuxFactAttribute : FactAttribute
{
@@ -20,7 +20,7 @@ public sealed class LinuxFactAttribute : FactAttribute
}
}
/// <summary>Ein Test, der nur unter Windows etwas aussagt. Siehe <see cref="LinuxFactAttribute"/>.</summary>
/// <summary>A test that only says something under Windows. See <see cref="LinuxFactAttribute"/>.</summary>
public sealed class WindowsFactAttribute : FactAttribute
{
public WindowsFactAttribute()
@@ -4,10 +4,10 @@ using Xunit.Sdk;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Führt einen Theory-Test mehrfach aus.
/// Runs a theory test several times.
///
/// Race Conditions treten sporadisch auf — ein einmaliger Durchlauf sagt wenig.
/// Der Testparameter ist der Durchlauf-Index und wird üblicherweise ignoriert.
/// Race conditions occur sporadically — a single run says little.
/// The test parameter is the run index and is usually ignored.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class RepeatAttribute(int count) : DataAttribute
@@ -4,20 +4,20 @@ using ClawdDotNet.Core.Security;
namespace ClawdDotNet.Core.Tests.Infrastructure;
/// <summary>
/// Hält den Schlüssel von <see cref="SecretKeyStore"/> aus den Tests heraus.
/// Keeps the key of <see cref="SecretKeyStore"/> out of the tests.
///
/// Zwei Dinge müssen zusammenkommen:
/// Two things must come together:
///
/// 1. <see cref="Init"/> verlegt den Schlüssel <b>einmal beim Laden</b> der
/// Testassembly in ein Wegwerfverzeichnis. Ohne das würden die Tests den echten
/// Benutzerschlüssel anlegen oder lesen — auf dem Entwicklungsrechner wie auf dem
/// Bauserver.
/// 1. <see cref="Init"/> relocates the key <b>once when loading</b> the
/// test assembly into a throwaway directory. Without that the tests would create or read
/// the real user key — on the development machine as on the
/// build server.
///
/// 2. Wer den Schlüssel <em>während</em> eines Tests wechselt (etwa um zu prüfen, dass
/// ein fremder Schlüssel nicht liest), muss in dieser Sammlung laufen. Der Speicher
/// ist statisch; ohne abgeschaltete Parallelität zöge ein solcher Wechsel einer
/// gleichzeitig laufenden Klasse den Schlüssel unter den Füßen weg. Genau das ist
/// zwischen den Geheimnis- und den Sicherungstests passiert.
/// 2. Whoever changes the key <em>during</em> a test (for example to check that
/// a foreign key does not read) must run in this collection. The store
/// is static; without parallelism switched off, such a change would pull the key
/// out from under a class running at the same time. Exactly that
/// happened between the secret and the backup tests.
/// </summary>
[CollectionDefinition(Name, DisableParallelization = true)]
public sealed class SecretKeyCollection