using System.Runtime.CompilerServices; using System.Security.Cryptography; namespace ZB.MOM.WW.MxGateway.Tests.TestSupport; /// /// Defaults the host environment to Development and isolates the test process's on-disk /// gateway paths, for the whole test assembly. /// /// /// Many tests build the full gateway host through GatewayApplication.Build against the dev /// appsettings.json (which ships Ldap:Transport=None and other dev-only defaults). /// An unset environment resolves to Production, where the production guards fail startup /// by design — so those host-building tests would trip the guards. Setting the environment to /// Development once, before any test runs, keeps that suite exercising app wiring rather than /// production-config validation. Tests that specifically assert production behavior construct /// GatewayOptionsValidator with its isProduction constructor (no host environment) and /// are unaffected; a test that needs Production can still pass --environment=Production, which /// overrides this default. /// /// The self-signed-cert path is also defaulted to a per-process temp file. Otherwise every /// full-host test that triggers HTTPS-cert generation writes the shared /// CommonApplicationData/MxGateway/certs/gateway-selfsigned.pfx default — parallel xUnit /// test classes then collide on that path's temp file (Windows: "the process cannot access the /// file ... because it is being used by another process"), and on a shared CI/dev box the suite /// also fights the deployed gateway service for the same file. Pointing at a per-process path /// isolates the suite: the first host-building test generates the cert, the rest load it. /// /// internal static class TestHostEnvironmentInitializer { /// /// Applies the Development environment and isolated self-signed-cert path defaults described /// on this type, run once by the runtime before any test in the assembly executes. /// [ModuleInitializer] internal static void SetDevelopmentEnvironmentDefault() { if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"))) { Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); } if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MxGateway__Tls__SelfSignedCertPath"))) { // ProcessId keeps the path stable within this test process (so a valid cert // generated by the first host-building test is reused by the rest) yet unique // across processes (so concurrent test runs / the deployed service never share it). string certPath = Path.Combine( Path.GetTempPath(), $"mxgw-tests-{Environment.ProcessId}", "gateway-selfsigned.pfx"); Environment.SetEnvironmentVariable("MxGateway__Tls__SelfSignedCertPath", certPath); } // Host-building tests must not depend on a seeded secret store. The shipped appsettings.json // sources the LDAP bind password from ${secret:ldap/mxgateway/bind}, which the pre-host // expander in GatewayApplication.CreateBuilder resolves before the host is built; with no // seeded store (as on CI) that resolution fails closed with SecretNotFoundException. Supplying // the bind password as an environment override — the same supported way an operator can — makes // config["MxGateway:Ldap:ServiceAccountPassword"] a plain literal (the env provider outranks the // JSON provider), so the expander sees no ${secret:} prefix and skips it. No store/seed needed. if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MxGateway__Ldap__ServiceAccountPassword"))) { Environment.SetEnvironmentVariable("MxGateway__Ldap__ServiceAccountPassword", "test-bind-password"); } // The runtime AddZbSecrets registration still runs SqliteSecretsStoreMigrator.MigrateAsync on // startup, which needs a valid master key and a writable store path. Point the store at a // per-process temp file (not the test working dir) and supply a throwaway base64 32-byte key so // migration succeeds without touching any real/shared secrets store. if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ZB_SECRETS_MASTER_KEY"))) { Environment.SetEnvironmentVariable( "ZB_SECRETS_MASTER_KEY", Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))); } if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("Secrets__SqlitePath"))) { string secretsPath = Path.Combine( Path.GetTempPath(), $"mxgw-tests-{Environment.ProcessId}", "secrets.db"); Environment.SetEnvironmentVariable("Secrets__SqlitePath", secretsPath); } // Starting the full host eagerly opens the auth SQLite store. Since SEC-33 the shipped // appsettings.json no longer carries an Authentication:SqlitePath, and the CommonApplicationData // code default resolves under an unwritable /usr/share on macOS. Point every host-building test at // a per-process temp store (same pattern as Secrets__SqlitePath above) so host-start tests are // auto-covered without a per-test override; a test that needs its own store still overrides this. if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MxGateway__Authentication__SqlitePath"))) { string authPath = Path.Combine( Path.GetTempPath(), $"mxgw-tests-{Environment.ProcessId}", "gateway-auth.db"); Environment.SetEnvironmentVariable("MxGateway__Authentication__SqlitePath", authPath); } } }