feat(opcuaclient): subscription tuning, OperationLimits chunking, diagnostics counters, revocation options, DiscoveryUrl (#273 #275 #276 #277 #278)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 14:40:20 -04:00
parent 55eac5f68b
commit ba62022e88
12 changed files with 1308 additions and 100 deletions
@@ -156,6 +156,50 @@ public sealed class OpcUaClientDriverPageFormSerializationTests
result.ProbeTimeoutSeconds.ShouldBe(25);
}
/// <summary>
/// #273/#275/#277/#278: the typed form rewrites the whole record on save, so a key it does not model
/// is silently dropped (#576). Every new option must survive FromRecord -> ToRecord.
/// </summary>
[Fact]
public void FormModel_RoundTrip_PreservesTuningLimitsRevocationAndDiscovery()
{
var original = new OpcUaClientDriverOptions
{
SubscriptionPublishingInterval = TimeSpan.FromMilliseconds(2500),
SubscriptionKeepAliveCount = 20,
SubscriptionLifetimeCount = 600,
SubscriptionMaxNotificationsPerPublish = 500,
SubscriptionPriority = 9,
HonorServerOperationLimits = false,
CertificateValidation = new OpcUaCertificateValidationOptions
{
RejectUnknownRevocationStatus = true,
IssuerStorePath = "/pki/issuers",
},
DiscoveryUrl = "opc.tcp://lds:4840",
};
var back = OpcUaClientDriverForm.OpcUaClientFormModel.FromRecord(original).ToRecord([]);
back.SubscriptionPublishingInterval.ShouldBe(TimeSpan.FromMilliseconds(2500));
back.SubscriptionKeepAliveCount.ShouldBe(20u);
back.SubscriptionLifetimeCount.ShouldBe(600u);
back.SubscriptionMaxNotificationsPerPublish.ShouldBe(500u);
back.SubscriptionPriority.ShouldBe((byte)9);
back.HonorServerOperationLimits.ShouldBeFalse();
back.CertificateValidation.RejectUnknownRevocationStatus.ShouldBeTrue();
back.CertificateValidation.IssuerStorePath.ShouldBe("/pki/issuers");
back.DiscoveryUrl.ShouldBe("opc.tcp://lds:4840");
// Defaults round-trip as defaults (blank override stays null, not 0 ms).
var def = OpcUaClientDriverForm.OpcUaClientFormModel.FromRecord(new OpcUaClientDriverOptions()).ToRecord([]);
def.SubscriptionPublishingInterval.ShouldBeNull();
def.SubscriptionKeepAliveCount.ShouldBe(10u);
def.SubscriptionLifetimeCount.ShouldBe(1000u);
def.HonorServerOperationLimits.ShouldBeTrue();
def.DiscoveryUrl.ShouldBeNull();
}
[Fact]
public void EndpointUrlRow_FromUrl_ToUrl_Trims()
{