Files
ClawdDotNet/tests/ClawdDotNet.Core.Tests/Audit/AuditRepositoryTests.cs
T
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

128 lines
4.0 KiB
C#

using ClawdDotNet.Core.Audit;
using ClawdDotNet.Core.Storage;
using Shouldly;
namespace ClawdDotNet.Core.Tests.Audit;
/// <summary>
/// A3: the append-only audit log and the receipts. Against real SQLite, because schema and
/// persistence are the point.
/// </summary>
public sealed class AuditRepositoryTests : IDisposable
{
private readonly string _directory;
private readonly SqliteStorage _storage;
private readonly SqliteAuditRepository _repo;
public AuditRepositoryTests()
{
_directory = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
_storage = new SqliteStorage(Path.Combine(_directory, "state.db"));
_repo = new SqliteAuditRepository(_storage);
}
public void Dispose()
{
ClawdDotNet.Core.Storage.SqliteStorage.ReleasePooledConnections(_directory);
try { Directory.Delete(_directory, recursive: true); }
catch { /* Cleaning up is secondary */ }
}
private AuditEntry Entry(string runId, string tool, AuditStatus status = AuditStatus.Ok) => new()
{
RunId = runId,
AgentId = "agent-a",
Model = "test/model",
Source = "task",
Tool = tool,
Arguments = "{\"x\":1}",
Status = status,
Summary = "",
DurationMs = 5,
OccurredAt = DateTime.UtcNow
};
[Fact]
public async Task An_entry_is_written_and_found_again()
{
await _repo.AppendAsync(Entry("run-1", "FileRW"), default);
var recent = await _repo.ListRecentAsync(10, default);
recent.Count.ShouldBe(1);
recent[0].Tool.ShouldBe("FileRW");
recent[0].RunId.ShouldBe("run-1");
(await _repo.CountAsync(default)).ShouldBe(1);
}
[Fact]
public async Task The_calls_of_a_run_come_in_order()
{
await _repo.AppendAsync(Entry("run-1", "Memory"), default);
await _repo.AppendAsync(Entry("run-1", "FileRW"), default);
await _repo.AppendAsync(Entry("run-2", "Mail"), default);
var forRun = await _repo.ListForRunAsync("run-1", default);
forRun.Select(e => e.Tool).ShouldBe(["Memory", "FileRW"]);
}
[Fact]
public async Task The_newest_entries_come_first()
{
await _repo.AppendAsync(Entry("run-1", "erst"), default);
await _repo.AppendAsync(Entry("run-1", "zuletzt"), default);
(await _repo.ListRecentAsync(10, default))[0].Tool.ShouldBe("zuletzt");
}
[Fact]
public async Task The_status_survives_the_round_trip()
{
await _repo.AppendAsync(Entry("r", "X", AuditStatus.Denied), default);
(await _repo.ListRecentAsync(1, default))[0].Status.ShouldBe(AuditStatus.Denied);
}
// ─── Receipts ───
[Fact]
public async Task A_receipt_links_run_and_task()
{
await _repo.RecordReceiptAsync(new RunReceipt
{
RunId = "run-9",
AgentId = "agent-a",
Model = "test/model",
Source = "task",
TaskId = "t-42",
Status = "Completed",
StepCount = 3,
PromptTokens = 100,
CompletionTokens = 20,
CachedTokens = 10,
CostUsd = 0.0123m,
CostIsKnown = true,
DurationMs = 1200,
ResultRef = "fertig",
OccurredAt = DateTime.UtcNow
}, default);
var byRun = await _repo.GetReceiptForRunAsync("run-9", default);
byRun.ShouldNotBeNull();
byRun!.TaskId.ShouldBe("t-42");
byRun.CostUsd.ShouldBe(0.0123m);
byRun.CostIsKnown.ShouldBeTrue();
var byTask = await _repo.ListReceiptsForTaskAsync("t-42", default);
byTask.Count.ShouldBe(1);
byTask[0].RunId.ShouldBe("run-9");
}
[Fact]
public async Task Entries_outlast_closing_the_database()
{
await _repo.AppendAsync(Entry("run-1", "FileRW"), default);
var reopened = new SqliteAuditRepository(new SqliteStorage(Path.Combine(_directory, "state.db")));
(await reopened.CountAsync(default)).ShouldBe(1);
}
}