test+docs(resilience): prove authored ResilienceConfig reaches the runtime pipeline (#456) #461
@@ -138,6 +138,59 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
node.DeviceHost.ShouldBe("10.9.9.9:8193");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a <see cref="DriverInstance"/>'s per-instance <c>ResilienceConfig</c> override
|
||||
/// survives the real <see cref="ConfigComposer.SnapshotAndFlattenAsync"/> →
|
||||
/// <see cref="DeploymentArtifact.ParseDriverInstances(ReadOnlySpan{byte})"/> seam (arch-review
|
||||
/// follow-up #10 sub-gap). The composer serialises the whole entity, so the override rides the
|
||||
/// artifact into <c>DriverInstanceSpec.ResilienceConfig</c> — which <c>DriverHostActor</c> layers
|
||||
/// onto the driver's Polly pipeline. Guards the one leg the hand-rolled
|
||||
/// <c>DeploymentArtifactTests.ParseDriverInstances_carries_ResilienceConfig_onto_the_spec</c>
|
||||
/// cannot: if the composer ever stopped emitting the column (a projection, a <c>[JsonIgnore]</c>),
|
||||
/// authored resilience policy would silently revert to tier defaults in production while the
|
||||
/// hand-built artifact tests stayed green.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ResilienceConfig_survives_ConfigComposer_to_ParseDriverInstances_round_trip()
|
||||
{
|
||||
const string resilienceJson =
|
||||
"{\"capabilityPolicies\":{\"Read\":{\"timeoutSeconds\":5,\"retryCount\":7,\"breakerFailureThreshold\":3}}}";
|
||||
|
||||
var f = NewInMemoryDbFactory();
|
||||
await using (var db = f.CreateDbContext())
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("c1"));
|
||||
db.Namespaces.Add(new Namespace
|
||||
{
|
||||
NamespaceId = "ns-eq", ClusterId = "c1",
|
||||
Kind = NamespaceKind.Equipment, NamespaceUri = "urn:eq",
|
||||
});
|
||||
db.DriverInstances.Add(new DriverInstance
|
||||
{
|
||||
DriverInstanceId = "drv-1", ClusterId = "c1", NamespaceId = "ns-eq",
|
||||
Name = "Modbus", DriverType = "Modbus", DriverConfig = "{}",
|
||||
ResilienceConfig = resilienceJson,
|
||||
});
|
||||
// A second instance with no override proves the null case rides the same real composer path.
|
||||
db.DriverInstances.Add(new DriverInstance
|
||||
{
|
||||
DriverInstanceId = "drv-2", ClusterId = "c1", NamespaceId = "ns-eq",
|
||||
Name = "Modbus2", DriverType = "Modbus", DriverConfig = "{}",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await using var readDb = f.CreateDbContext();
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb);
|
||||
var specs = DeploymentArtifact.ParseDriverInstances(artifact.Blob);
|
||||
|
||||
var withOverride = specs.Single(s => s.DriverInstanceId == "drv-1");
|
||||
withOverride.ResilienceConfig.ShouldBe(resilienceJson, "the authored override must ride the artifact byte-for-byte");
|
||||
|
||||
var withoutOverride = specs.Single(s => s.DriverInstanceId == "drv-2");
|
||||
withoutOverride.ResilienceConfig.ShouldBeNull("an unset override stays null ⇒ tier defaults at spawn");
|
||||
}
|
||||
|
||||
private static readonly DateTime FixedTimestamp = new(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private static ServerCluster NewCluster(string id) => new()
|
||||
|
||||
Reference in New Issue
Block a user