ConfigOverviewService now reports the runtime mode and (on Client only)
the fleet-push configuration + live push-state per resource. The token
is reduced to a boolean (TokenConfigured) in the DTO — never a string —
so it cannot be accidentally serialised.
Backend
- FleetIngestInfoDto: { enabled, url, intervalSeconds, batchSize,
batchMaxBytes, tokenConfigured }. No Token property at all.
- FleetPushStateRowDto: { resourceType, lastCursor, lastSyncedAt,
consecutiveFailures, lastError }.
- ConfigOverviewDto gains RunMode (string), nullable FleetIngest +
FleetPushState (null in Admin mode).
- ConfigOverviewService becomes async + injects IServiceProvider so it
can read AppDbContext in Client mode without that DbContext being
required (GetService returns null in Admin mode, where it's not
registered).
- AdminConfigEndpoints awaits the async call.
Tests (+3, 56/56 passing)
- FleetIngestInfoDto has no Token property (reflection check).
- Serialised DTO never contains the literal token value (string scan).
- ConfigOverviewDto's FleetIngest + FleetPushState are nullable so
Admin-mode payloads serialise them as absent rather than empty.
Frontend
- ConfigOverviewCard adds a Run mode row to the Application section
(gold tag for Admin, blue for Client).
- New "Fleet push (Client → Admin)" descriptions card (enabled, token
configured, url, interval, batch sizes) — hidden in Admin mode.
- "Push state per resource" table — resource, last cursor, last sync,
consecutive failures (color-coded), last error.
Verified end-to-end on the dev host
- /api/admin/config-overview on the Client returns runMode=Client +
fleetIngest={enabled,url,interval,batchSize,batchMaxBytes,tokenConfigured}
+ fleetPushState[3] rows (sites/devices/measurements, failures=0).
- The 64-char dev token (from the running Client's .env) is verified
absent from the response body via direct string search.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
56 lines
2.2 KiB
C#
56 lines
2.2 KiB
C#
using System.Text.Json;
|
|
using Tau.Acuvim.Portal.DTOs;
|
|
|
|
namespace Tau.Acuvim.Portal.Tests;
|
|
|
|
// Locks down the invariant that the App-config payload never carries the
|
|
// fleet-ingest token. Token is reduced to a boolean in the DTO, so even if a
|
|
// future change accidentally tries to copy the value it would fail to compile.
|
|
public class ConfigOverviewRedactionTests
|
|
{
|
|
[Fact]
|
|
public void FleetIngestInfoDto_HasNoTokenProperty()
|
|
{
|
|
var properties = typeof(FleetIngestInfoDto).GetProperties().Select(p => p.Name).ToArray();
|
|
Assert.DoesNotContain(properties, p => p.Equals("Token", StringComparison.OrdinalIgnoreCase));
|
|
Assert.Contains("TokenConfigured", properties);
|
|
}
|
|
|
|
[Fact]
|
|
public void Serialised_FleetIngestInfoDto_DoesNotMentionTokenValue()
|
|
{
|
|
var supposedSecret = "this-should-never-be-in-the-payload";
|
|
var dto = new FleetIngestInfoDto(
|
|
Enabled: true,
|
|
Url: "https://admin.example.com/api/fleet/ingest",
|
|
IntervalSeconds: 60,
|
|
BatchSize: 5000,
|
|
BatchMaxBytes: 1_048_576,
|
|
TokenConfigured: !string.IsNullOrWhiteSpace(supposedSecret));
|
|
|
|
var json = JsonSerializer.Serialize(dto);
|
|
Assert.DoesNotContain(supposedSecret, json);
|
|
Assert.Contains("tokenConfigured", json, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConfigOverviewDto_FleetIngestNullable_ForAdminMode()
|
|
{
|
|
// Admin mode never has fleet push state — both FleetIngest and FleetPushState
|
|
// are nullable on the DTO so they serialise as absent rather than empty.
|
|
var dto = new ConfigOverviewDto(
|
|
RunMode: "Admin",
|
|
Application: new("X", "Production", "https://x"),
|
|
Database: new("PostgreSQL", "h", 5432, "d", true, false, "src"),
|
|
Grafana: new("u", "u", "/g", "iframe", "", "anonymous-local-only", 0),
|
|
Monitoring: new("7 days", false),
|
|
Authentication: new("c", false, "a@b.c"),
|
|
Build: new("1", "10.0.0", DateTime.UtcNow),
|
|
FleetIngest: null,
|
|
FleetPushState: null);
|
|
|
|
Assert.Null(dto.FleetIngest);
|
|
Assert.Null(dto.FleetPushState);
|
|
}
|
|
}
|