3336ec08c7
Routes the host's secrets registration through a new AddOtOpcUaSecrets extension that gates the ISecretStore implementation on Secrets:Replication:Enabled. Opt-in gate (default FALSE) This call decides which ISecretStore every node resolves — including driver-role nodes with no auth/AdminUI, where a wrong store surfaces as drivers failing to open sessions rather than as a failing test. With the flag false the wiring is the pre-existing AddZbSecrets(config, "Secrets") call, unchanged, so current behavior is byte-identical. With it true, AddZbSecretsAkkaReplication replaces that call (it invokes AddZbSecrets internally; calling both would double-register). Extracted to a named extension specifically so the registration is testable: Program.cs is top-level statements and cannot be exercised by a container test, which is how a "registered but never resolvable" defect ships unnoticed. Serializer HOCON AkkaSecretsReplication.SerializationConfig is merged into the ActorSystem config inside the AddAkka configurator, conditionally on the same gate — a non-replicating node carries no bindings for messages it will never see. Merged via AddHocon(..., HoconAddMode.Append), Akka.Hosting's fallback merge and the same mode the existing base-config merge uses; a raw Config.WithFallback would fight the builder's own assembly. Lazy-actor mitigation The replication actor is created lazily on first ISecretStore resolution, so a node that never touches a secret would never announce a manifest and would silently never converge. SecretReplicationStarter (IHostedService) resolves the store once at startup to make participation unconditional. KNOWN BLOCKER — replication is currently NON-FUNCTIONAL; do not enable ZB.MOM.WW.Secrets.Replicator.AkkaDotNet 0.2.0 never binds its own ISecretReplicator. AddZbSecretsAkkaReplication calls AddZbSecrets FIRST, which does TryAddSingleton<ISecretReplicator, NoOpSecretReplicator>(); the package's own TryAddSingleton<ISecretReplicator>(AkkaSecretReplicator) that follows is therefore a no-op. Verified empirically in a built container: with Enabled=true, ISecretReplicator resolves to NoOpSecretReplicator, so ReplicatingSecretStore publishes into a sink and no actor is ever spawned. Consequence: the startup hook cannot create the actor, and the test asserting it does is committed Skipped with the evidence. Not worked around here — the fix belongs upstream (AddSingleton, or register before calling AddZbSecrets). Because the flag defaults false, this commit is inert in production. Tests: SecretsReplicationRegistrationTests (new) — disabled path resolves plain SqliteSecretStore and needs no ActorSystem; enabled path resolves ReplicatingSecretStore AND the undecorated concrete SqliteSecretStore the decorator is built from (the exact registration gap that shipped once); startup hook registered only when enabled. Red before wiring (4 assertion failures), green after: 6 pass, 1 skipped (blocker above). Build: 861 warnings / 0 errors, unchanged from baseline (full --no-incremental A/B). Host.IntegrationTests: 123 pass, 6 skip, 1 fail — AbCip_Green_AgainstSim, verified pre-existing on the stashed tree (fixture-gated). Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
ZB.MOM.WW.OtOpcUa.Host.IntegrationTests
Two-node Akka cluster integration tests on top of TwoNodeClusterHarness.
Default mode (no infra required)
dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests
Uses Microsoft.EntityFrameworkCore.InMemory for ConfigDb and a stub ILdapAuthService that
accepts any username when the password is valid-password. Each harness instance creates a
unique in-memory database scoped to its lifetime. This is the mode CI runs by default.
Real-infra mode (SQL Server + OpenLDAP)
When you need to exercise EF behaviors that diverge between providers (index uniqueness,
RowVersion concurrency, JSON columns, migration application) or a real LDAP bind, bring up
the bundled compose stack and set the env-var switches:
docker compose -f tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/docker-compose.yml up -d
export OTOPCUA_HARNESS_USE_SQL=1
export OTOPCUA_HARNESS_USE_LDAP=1
dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests
docker compose -f tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/docker-compose.yml down -v
SQL Server mode (OTOPCUA_HARNESS_USE_SQL=1)
- Container:
mcr.microsoft.com/mssql/server:2022-latestonlocalhost:14331 - Each
TwoNodeClusterHarness.StartAsync()creates a unique databaseOtOpcUa_Harness_{guid}viaDatabase.EnsureCreatedAsync()and drops it onDisposeAsync()(best-effort). - Port
14331chosen to avoid colliding with thedocker-dev/fleet (which uses14330).
LDAP mode (OTOPCUA_HARNESS_USE_LDAP=1)
- Container:
bitnami/openldap:2.6onlocalhost:3894 - Users
alice/alice123andbob/bob123, all underou=FleetAdmin. - Port
3894chosen to avoid colliding with thedocker-dev/fleet (which uses3893).
Local-dev caveat
This dev VM (DESKTOP-6JL3KKO) does not run Docker locally. Real-infra mode runs on the
shared Linux Docker host (10.100.0.35) per docs/v2/dev-environment.md, or in CI on Linux.