533414a16d
#113: AuditLogPartitionMaintenance (monthly SPLIT RANGE) and the backfill-source-node endpoint now use the AuditLog:MaintenanceConnectionString login when set (unset = unchanged). Migration AuditMaintenanceGrants gives scadabridge_audit_purger ALTER ANY DATASPACE and column-level UPDATE (SourceNode). #114: archive cleanup deletes the writer's own *.tmp files and manifest-less data files older than 24 h inside {Directory}/{YYYY} (DeletedOrphan, dry-run aware). #115: head-export failures bump scadabridge.audit.chain_head_export.failures and Degrade the /health/ready entry audit-chain-head-export (failing or stale > 2 intervals). Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
46 lines
1.9 KiB
C#
46 lines
1.9 KiB
C#
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using ZB.MOM.WW.ScadaBridge.AuditLog.Central.HeadExport;
|
|
using ZB.MOM.WW.ScadaBridge.Host.Health;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
|
|
|
|
/// <summary>ScadaBridge#115: the chain-head export readiness entry.</summary>
|
|
public sealed class AuditChainHeadExportHealthCheckTests
|
|
{
|
|
private sealed class Clock(DateTime now) : TimeProvider
|
|
{
|
|
public DateTime Now { get; set; } = now;
|
|
public override DateTimeOffset GetUtcNow() => new(DateTime.SpecifyKind(Now, DateTimeKind.Utc));
|
|
}
|
|
|
|
private static readonly DateTime T0 = new(2026, 11, 10, 12, 0, 0, DateTimeKind.Utc);
|
|
|
|
private static async Task<HealthStatus> Check(AuditChainHeadExportStatus s, Clock c)
|
|
=> (await new AuditChainHeadExportHealthCheck(s, c).CheckHealthAsync(new HealthCheckContext())).Status;
|
|
|
|
/// <summary>Disabled export is healthy; failing or stale export is Degraded; recovery clears it.</summary>
|
|
[Fact]
|
|
public async Task Status_follows_export_outcome()
|
|
{
|
|
var clock = new Clock(T0);
|
|
var status = new AuditChainHeadExportStatus();
|
|
Assert.Equal(HealthStatus.Healthy, await Check(status, clock)); // disabled
|
|
|
|
status.MarkStarted(60, T0);
|
|
Assert.Equal(HealthStatus.Healthy, await Check(status, clock));
|
|
|
|
status.RecordFailure(T0, "share offline");
|
|
var result = await new AuditChainHeadExportHealthCheck(status, clock).CheckHealthAsync(new HealthCheckContext());
|
|
Assert.Equal(HealthStatus.Degraded, result.Status);
|
|
Assert.Contains("share offline", result.Description);
|
|
|
|
status.RecordSuccess(T0);
|
|
Assert.Equal(HealthStatus.Healthy, await Check(status, clock));
|
|
|
|
clock.Now = T0.AddMinutes(121); // no success for > 2 intervals
|
|
Assert.Equal(HealthStatus.Degraded, await Check(status, clock));
|
|
status.RecordSuccess(clock.Now);
|
|
Assert.Equal(HealthStatus.Healthy, await Check(status, clock));
|
|
}
|
|
}
|