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>
108 lines
4.0 KiB
C#
108 lines
4.0 KiB
C#
using ClawdDotNet.Core.Accounting;
|
|
using ClawdDotNet.Core.Config;
|
|
using ClawdDotNet.Core.Deploymentcenter.Watchdog;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Core.Tests.Deploymentcenter;
|
|
|
|
public sealed class InstanceHealthProviderTests
|
|
{
|
|
/// <summary>Delivers a fixed daily consumption, without a DB.</summary>
|
|
private sealed class FakeUsage(DailyUsage daily) : IUsageRepository
|
|
{
|
|
public Task RecordAsync(RunUsage usage, CancellationToken ct) => Task.CompletedTask;
|
|
|
|
public Task<DailyUsage> GetDailyAsync(DateOnly date, string agentId, CancellationToken ct)
|
|
=> Task.FromResult(daily);
|
|
|
|
public Task<IReadOnlyList<RunUsage>> GetRecentAsync(int limit, CancellationToken ct)
|
|
=> Task.FromResult((IReadOnlyList<RunUsage>)Array.Empty<RunUsage>());
|
|
|
|
public Task<int> PurgeBeforeAsync(DateOnly date, CancellationToken ct) => Task.FromResult(0);
|
|
}
|
|
|
|
private static DailyUsage Usage(decimal cost, int tokens = 0) =>
|
|
new(DateOnly.FromDateTime(DateTime.Now), tokens, 0, cost, true, 1);
|
|
|
|
[Fact]
|
|
public async Task Ohne_API_Key_meldet_error()
|
|
{
|
|
var provider = new InstanceHealthProvider(
|
|
"Test", agentsEnabled: false, InstanceBudget.Unlimited,
|
|
usage: null, () => 2, () => 0);
|
|
|
|
var health = await provider.GetAsync(default);
|
|
|
|
health.Status.ShouldBe(WatchdogStatus.Error);
|
|
health.Metrics["agentCount"].ShouldBe(2);
|
|
health.Checks["agents"].Ok.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Erschoepftes_Kostenbudget_meldet_warning()
|
|
{
|
|
var budget = new InstanceBudget { DailyCostUsd = 5m };
|
|
var provider = new InstanceHealthProvider(
|
|
"Test", agentsEnabled: true, budget,
|
|
new FakeUsage(Usage(cost: 5m)), () => 1, () => 0);
|
|
|
|
var health = await provider.GetAsync(default);
|
|
|
|
health.Status.ShouldBe(WatchdogStatus.Warning);
|
|
health.Checks["budget"].Ok.ShouldBeFalse();
|
|
health.Message.ShouldNotBeNull();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A heartbeat only proves that a thread is running. If the scanner stands still, the
|
|
/// instance no longer works through anything — and exactly that is what the check should make visible.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Stehender_Scanner_meldet_warning()
|
|
{
|
|
var provider = new InstanceHealthProvider(
|
|
"Test", agentsEnabled: true, InstanceBudget.Unlimited,
|
|
new FakeUsage(Usage(cost: 0m)), () => 1, () => 0,
|
|
schedulerRunning: () => false);
|
|
|
|
var health = await provider.GetAsync(default);
|
|
|
|
health.Status.ShouldBe(WatchdogStatus.Warning);
|
|
health.Checks["scheduler"].Ok.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Normalbetrieb_meldet_ok_mit_Metriken()
|
|
{
|
|
var provider = new InstanceHealthProvider(
|
|
"Test", agentsEnabled: true, InstanceBudget.Unlimited,
|
|
new FakeUsage(Usage(cost: 0.5m, tokens: 1200)), () => 3, () => 1,
|
|
schedulerRunning: () => true);
|
|
|
|
var health = await provider.GetAsync(default);
|
|
|
|
health.Status.ShouldBe(WatchdogStatus.Ok);
|
|
health.Metrics["runningChats"].ShouldBe(1);
|
|
health.Metrics.Keys.ShouldContain("todayCostUsd");
|
|
health.Metrics.Keys.ShouldContain("todayTokens");
|
|
health.Checks.Values.ShouldAllBe(c => c.Ok);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metrics are only evaluated server-side if they are numbers — everything else
|
|
/// the history silently discards. Descriptive things belong in the message.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Instanzname_steht_in_der_Meldung_nicht_in_den_Metriken()
|
|
{
|
|
var provider = new InstanceHealthProvider(
|
|
"Produktion", agentsEnabled: true, InstanceBudget.Unlimited,
|
|
usage: null, () => 1, () => 0);
|
|
|
|
var health = await provider.GetAsync(default);
|
|
|
|
health.Message.ShouldNotBeNull().ShouldContain("Produktion");
|
|
health.Metrics.Keys.ShouldNotContain("instanceName");
|
|
}
|
|
}
|