using ZB.MOM.WW.MxGateway.Contracts; namespace ZB.MOM.WW.MxGateway.IntegrationTests; public static class IntegrationTestEnvironment { /// /// Sourced from /// so the env-var literal is shared with /// ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport.LiveMxAccessFactAttribute /// (Worker.Tests-025). /// public const string LiveMxAccessVariableName = GatewayContractInfo.LiveMxAccessOptInVariableName; public const string LiveMxAccessWorkerExecutableVariableName = "MXGATEWAY_LIVE_MXACCESS_WORKER_EXE"; public const string LiveMxAccessItemVariableName = "MXGATEWAY_LIVE_MXACCESS_ITEM"; public const string LiveMxAccessClientNameVariableName = "MXGATEWAY_LIVE_MXACCESS_CLIENT_NAME"; public const string LiveMxAccessEventTimeoutSecondsVariableName = "MXGATEWAY_LIVE_MXACCESS_EVENT_TIMEOUT_SECONDS"; /// Gets whether live MXAccess tests are enabled. public static bool LiveMxAccessTestsEnabled => IsEnabled(LiveMxAccessVariableName); /// /// Gets whether an opt-in live-test suite is enabled, by comparing the named /// environment variable to 1. Shared by every Live*FactAttribute /// so the opt-in check has a single implementation. /// /// The environment variable that gates the suite. /// when the variable is exactly 1. public static bool IsEnabled(string variableName) => string.Equals( Environment.GetEnvironmentVariable(variableName), "1", StringComparison.Ordinal); /// Gets the MXAccess item name for live tests. public static string LiveMxAccessItem => GetOptionalEnvironmentVariable( LiveMxAccessItemVariableName, "TestChildObject.TestInt"); /// Gets the client name for live tests. public static string LiveMxAccessClientName => GetOptionalEnvironmentVariable( LiveMxAccessClientNameVariableName, "ZB.MOM.WW.MxGateway.IntegrationTests"); /// Gets the timeout for waiting on events in live tests. public static TimeSpan LiveMxAccessEventTimeout => TimeSpan.FromSeconds(GetPositiveIntegerEnvironmentVariable( LiveMxAccessEventTimeoutSecondsVariableName, defaultValue: 15)); /// Resolves the path to the worker executable for live tests. /// Path to ZB.MOM.WW.MxGateway.Worker.exe. public static string ResolveLiveMxAccessWorkerExecutablePath() { string? configuredPath = Environment.GetEnvironmentVariable(LiveMxAccessWorkerExecutableVariableName); if (!string.IsNullOrWhiteSpace(configuredPath)) { return Path.GetFullPath(configuredPath); } string repositoryRoot = ResolveRepositoryRoot(AppContext.BaseDirectory); string[] candidatePaths = [ Path.Combine(repositoryRoot, "src", "ZB.MOM.WW.MxGateway.Worker", "bin", "x86", "Debug", "net48", "ZB.MOM.WW.MxGateway.Worker.exe"), Path.Combine(repositoryRoot, "src", "ZB.MOM.WW.MxGateway.Worker", "bin", "Debug", "net48", "ZB.MOM.WW.MxGateway.Worker.exe"), Path.Combine(repositoryRoot, "src", "ZB.MOM.WW.MxGateway.Worker", "bin", "x86", "Release", "net48", "ZB.MOM.WW.MxGateway.Worker.exe"), Path.Combine(repositoryRoot, "src", "ZB.MOM.WW.MxGateway.Worker", "bin", "Release", "net48", "ZB.MOM.WW.MxGateway.Worker.exe"), Path.Combine(repositoryRoot, "src", "ZB.MOM.WW.MxGateway.Worker", "bin", "x86", "Release", "ZB.MOM.WW.MxGateway.Worker.exe"), ]; return candidatePaths.FirstOrDefault(File.Exists) ?? candidatePaths[0]; } private static string GetOptionalEnvironmentVariable( string name, string defaultValue) { string? value = Environment.GetEnvironmentVariable(name); return string.IsNullOrWhiteSpace(value) ? defaultValue : value; } private static int GetPositiveIntegerEnvironmentVariable( string name, int defaultValue) { string? value = Environment.GetEnvironmentVariable(name); if (int.TryParse(value, out int parsed) && parsed > 0) { return parsed; } return defaultValue; } /// /// Resolves the root directory of the repository by walking parents for a /// src/ directory next to either a .git marker or a /// .sln/.slnx file. Throws /// when no root is found so a misconfigured run fails fast with an actionable /// message rather than silently falling back to the current working directory /// (which previously produced a misleading "worker exe not found" pointing at /// a fabricated path — see IntegrationTests-022). The /// environment variable /// remains the escape hatch for unusual deployments. /// /// Starting directory to search from. /// The repository root path. /// /// Thrown when the parent walk exhausts without finding a repository root. /// internal static string ResolveRepositoryRoot(string startDirectory) { DirectoryInfo? directory = new(startDirectory); while (directory is not null) { if (IsRepositoryRoot(directory)) { return directory.FullName; } directory = directory.Parent; } throw new InvalidOperationException( $"Could not resolve repository root by walking parents of '{startDirectory}'. " + "Expected to find a directory containing a 'src' subdirectory next to either a '.git' marker " + "or a '*.sln' / '*.slnx' file in 'src'. " + $"Set the '{LiveMxAccessWorkerExecutableVariableName}' environment variable to bypass repository-root resolution."); } private static bool IsRepositoryRoot(DirectoryInfo directory) { string srcPath = Path.Combine(directory.FullName, "src"); if (!Directory.Exists(srcPath)) { return false; } // Accept a checked-out git repo OR an unpacked working tree that ships a // .sln/.slnx alongside src/. The .sln/.slnx fallback lets the integration // tests run in copies that have no .git folder (e.g. an extracted zip). if (Directory.Exists(Path.Combine(directory.FullName, ".git")) || File.Exists(Path.Combine(directory.FullName, ".git"))) { return true; } return Directory.EnumerateFiles(srcPath, "*.slnx").Any() || Directory.EnumerateFiles(srcPath, "*.sln").Any(); } }