Files
ClawdDotNet/tests/ClawdDotNet.Core.Tests/Deploymentcenter/WatchdogHeartbeatServiceTests.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

88 lines
3.1 KiB
C#

using ClawdDotNet.Core.Deploymentcenter.Watchdog;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
namespace ClawdDotNet.Core.Tests.Deploymentcenter;
public sealed class WatchdogHeartbeatServiceTests
{
/// <summary>Remembers what was sent, without a network.</summary>
private sealed class RecordingClient : IWatchdogClient
{
public List<(string Status, string? Message)> Heartbeats { get; } = [];
public List<(string Kind, string? Message)> Events { get; } = [];
public Task<WatchdogPingResult> SendHeartbeatAsync(
InstanceHealth health, int intervalSeconds, CancellationToken ct)
{
Heartbeats.Add((health.Status, health.Message));
return Task.FromResult(new WatchdogPingResult("up", []));
}
public Task SendEventAsync(
string kind, string severity, string? message, object? meta, CancellationToken ct)
{
Events.Add((kind, message));
return Task.CompletedTask;
}
}
private sealed class FixedHealth(InstanceHealth health) : IInstanceHealthProvider
{
public Task<InstanceHealth> GetAsync(CancellationToken ct) => Task.FromResult(health);
}
private static WatchdogHeartbeatService Build(RecordingClient client) =>
new(client, new FixedHealth(InstanceHealth.Ok("läuft")), 3600, NullLogger.Instance);
[Fact]
public async Task Regular_end_reports_stopped()
{
var client = new RecordingClient();
var service = Build(client);
service.Start();
await service.DisposeAsync();
client.Heartbeats[^1].Status.ShouldBe(WatchdogStatus.Stopped);
client.Events[^1].Kind.ShouldBe(WatchdogEventKind.StoppedGraceful);
}
/// <summary>
/// On update the instance is about to go away, but comes back. <c>stopped</c> would be
/// the wrong information: the monitor would be left alone until someone touches it.
/// </summary>
[Fact]
public async Task Announced_maintenance_reports_maintenance()
{
var client = new RecordingClient();
var service = Build(client);
service.Start();
service.AnnounceMaintenance("Update auf 0.1.1 wird eingespielt.");
await service.DisposeAsync();
var last = client.Heartbeats[^1];
last.Status.ShouldBe(WatchdogStatus.Maintenance);
last.Message.ShouldNotBeNull().ShouldContain("0.1.1");
client.Events[^1].Kind.ShouldBe(WatchdogEventKind.MaintenanceStart);
}
/// <summary>The first beat goes out immediately, not only after a full interval.</summary>
[Fact]
public async Task First_heartbeat_comes_immediately()
{
var client = new RecordingClient();
var service = Build(client);
service.Start();
await service.DisposeAsync();
// Start event, at least one beat, then the deregistration.
client.Events[0].Kind.ShouldBe(WatchdogEventKind.Started);
client.Heartbeats.Count.ShouldBeGreaterThanOrEqualTo(2);
client.Heartbeats[0].Status.ShouldBe(WatchdogStatus.Ok);
}
}