feat(management): Admin-gated actor dispatch for cert-trust commands (arch-review C4)
Made CommunicationService.TrustServerCertAsync/RemoveServerCertAsync/ListServerCertsAsync virtual to support the test seam (mirrors existing virtual WriteTagAsync). Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -3250,6 +3250,84 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Admin-gated actor dispatch for the site cert-trust commands
|
||||
// (arch-review C4): TrustServerCert / RemoveServerCert / ListServerCerts.
|
||||
// These mutate the site's trusted-peer PKI store, so they are gated
|
||||
// Administrator — matching the Admin-gated cert-management UI. SiteIdentifier
|
||||
// is REQUIRED; the handler only relays (site-side reconcile is CertStoreActor).
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void TrustServerCert_DesignerOnly_Unauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new TrustServerCertCommand("conn", "ZGVy", "AB12", "SITE1"), "Designer"));
|
||||
ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrustServerCert_WithoutSiteIdentifier_ReturnsError()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new TrustServerCertCommand("conn", "ZGVy", "AB12"), "Administrator"));
|
||||
var resp = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("SiteIdentifier", resp.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrustServerCert_Admin_RelaysToSite()
|
||||
{
|
||||
var stub = new StubCommunicationService();
|
||||
_services.AddSingleton<CommunicationService>(stub);
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new TrustServerCertCommand("conn", "ZGVy", "AB12", "SITE1"), "Administrator"));
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(1, stub.TrustCount);
|
||||
Assert.Equal("SITE1", stub.LastTrustSite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveServerCert_Admin_RelaysToSite()
|
||||
{
|
||||
var stub = new StubCommunicationService();
|
||||
_services.AddSingleton<CommunicationService>(stub);
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new RemoveServerCertCommand("AB12", "SITE1"), "Administrator"));
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(1, stub.RemoveCount);
|
||||
Assert.Equal("SITE1", stub.LastRemoveSite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveServerCert_WithoutSiteIdentifier_ReturnsError()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new RemoveServerCertCommand("AB12"), "Administrator"));
|
||||
var resp = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("SiteIdentifier", resp.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListServerCerts_Admin_RelaysToSite()
|
||||
{
|
||||
var stub = new StubCommunicationService();
|
||||
_services.AddSingleton<CommunicationService>(stub);
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new ListServerCertsCommand("SITE1"), "Administrator"));
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(1, stub.ListCertsCount);
|
||||
Assert.Equal("SITE1", stub.LastListCertsSite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListServerCerts_DesignerOnly_Unauthorized()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(new ListServerCertsCommand("SITE1"), "Designer"));
|
||||
ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records remote-query relays for the actor-dispatch tests. The browse /
|
||||
/// search / verify / cert-trust methods on <see cref="CommunicationService"/>
|
||||
@@ -3268,6 +3346,36 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
public string? LastSearchSite { get; private set; }
|
||||
public int VerifyCount { get; private set; }
|
||||
public string? LastVerifySite { get; private set; }
|
||||
public int TrustCount { get; private set; }
|
||||
public string? LastTrustSite { get; private set; }
|
||||
public int RemoveCount { get; private set; }
|
||||
public string? LastRemoveSite { get; private set; }
|
||||
public int ListCertsCount { get; private set; }
|
||||
public string? LastListCertsSite { get; private set; }
|
||||
|
||||
public override Task<CertTrustResult> TrustServerCertAsync(
|
||||
string siteId, TrustServerCertCommand command, CancellationToken ct = default)
|
||||
{
|
||||
TrustCount++;
|
||||
LastTrustSite = siteId;
|
||||
return Task.FromResult(new CertTrustResult(true, null, null));
|
||||
}
|
||||
|
||||
public override Task<CertTrustResult> RemoveServerCertAsync(
|
||||
string siteId, RemoveServerCertCommand command, CancellationToken ct = default)
|
||||
{
|
||||
RemoveCount++;
|
||||
LastRemoveSite = siteId;
|
||||
return Task.FromResult(new CertTrustResult(true, null, null));
|
||||
}
|
||||
|
||||
public override Task<CertTrustResult> ListServerCertsAsync(
|
||||
string siteId, ListServerCertsCommand command, CancellationToken ct = default)
|
||||
{
|
||||
ListCertsCount++;
|
||||
LastListCertsSite = siteId;
|
||||
return Task.FromResult(new CertTrustResult(true, null, null));
|
||||
}
|
||||
|
||||
public override Task<BrowseNodeResult> BrowseNodeAsync(
|
||||
string siteId, BrowseNodeCommand command, CancellationToken ct = default)
|
||||
|
||||
Reference in New Issue
Block a user