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>
81 lines
3.2 KiB
C#
81 lines
3.2 KiB
C#
using ClawdDotNet.Core.Accounting;
|
|
using ClawdDotNet.Core.Config;
|
|
using ClawdDotNet.Core.Engine;
|
|
using ClawdDotNet.Core.Tests.Infrastructure;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Core.Tests.Budget;
|
|
|
|
/// <summary>
|
|
/// #608: A run rejected because of an exhausted budget left only a log warning -
|
|
/// no receipt, no indication on the task. In test operation all
|
|
/// weather data was therefore missing from 18:20 on, without it being visible outside the log.
|
|
/// </summary>
|
|
public sealed class BudgetsperreSichtbarTests
|
|
{
|
|
/// <summary>Reports for every agent a daily consumption above every usual limit.</summary>
|
|
private sealed class ErschoepftesBudget : IUsageRepository
|
|
{
|
|
public Task RecordAsync(RunUsage usage, CancellationToken ct) => Task.CompletedTask;
|
|
|
|
public Task<DailyUsage> GetDailyAsync(DateOnly date, string agentId, CancellationToken ct)
|
|
=> Task.FromResult(new DailyUsage(date, 400_000, 20_000, 0.408m, true, 28));
|
|
|
|
public Task<IReadOnlyList<RunUsage>> GetRecentAsync(int limit, CancellationToken ct)
|
|
=> Task.FromResult<IReadOnlyList<RunUsage>>([]);
|
|
|
|
public Task<int> PurgeBeforeAsync(DateOnly date, CancellationToken ct) => Task.FromResult(0);
|
|
}
|
|
|
|
private static (EngineFixture Fixture, InMemoryAuditRepository Audit, AgentConfig Agent) Aufbau()
|
|
{
|
|
var audit = new InMemoryAuditRepository();
|
|
var fixture = new EngineFixture(audit, usage: new ErschoepftesBudget());
|
|
var agent = fixture.AddAgent("wetterdienst");
|
|
agent.Budget.DailyCostUsd = 0.40m;
|
|
return (fixture, audit, agent);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Abgelehnter_frischer_Aufgabenlauf_hinterlaesst_einen_Beleg_an_der_Aufgabe()
|
|
{
|
|
var (fixture, audit, agent) = Aufbau();
|
|
|
|
var result = await fixture.Engine.RunAsync(
|
|
agent, "Hole die Warnungen", "inst", CancellationToken.None, source: "task", taskId: "t-unwetter");
|
|
|
|
result.Status.ShouldBe(AgentRunStatus.BudgetExceeded);
|
|
var beleg = audit.Receipts.ShouldHaveSingleItem();
|
|
beleg.TaskId.ShouldBe("t-unwetter");
|
|
beleg.AgentId.ShouldBe("wetterdienst");
|
|
beleg.Status.ShouldBe(nameof(AgentRunStatus.BudgetExceeded));
|
|
beleg.ResultRef.ShouldContain("Budget erschöpft");
|
|
beleg.ResultRef.ShouldContain("0,40", Case.Insensitive);
|
|
beleg.PromptTokens.ShouldBe(0);
|
|
beleg.CostUsd.ShouldBe(0m);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Abgelehnter_Lauf_im_Chatverlauf_hinterlaesst_ebenfalls_einen_Beleg()
|
|
{
|
|
var (fixture, audit, agent) = Aufbau();
|
|
|
|
var result = await fixture.Engine.ChatAsync(
|
|
agent, "Digest", "inst", CancellationToken.None, source: "task", taskId: "t-digest");
|
|
|
|
result.Status.ShouldBe(AgentRunStatus.BudgetExceeded);
|
|
audit.Receipts.ShouldHaveSingleItem().TaskId.ShouldBe("t-digest");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Jede_weitere_Ablehnung_des_Tages_steht_als_eigener_Beleg_da()
|
|
{
|
|
var (fixture, audit, agent) = Aufbau();
|
|
|
|
for (var i = 0; i < 3; i++)
|
|
await fixture.Engine.RunAsync(agent, "Los", "inst", CancellationToken.None, source: "task", taskId: "t-" + i);
|
|
|
|
audit.Receipts.Select(r => r.TaskId).ShouldBe(["t-0", "t-1", "t-2"]);
|
|
}
|
|
}
|