| | | 1 | | using Microsoft.Extensions.Configuration; |
| | | 2 | | |
| | | 3 | | namespace ShopGuard.Core.Configuration; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Loads <see cref="TestSettings"/> from a JSON file and environment variables. |
| | | 7 | | /// Precedence (highest wins): SHOPGUARD_* env vars > environment section > root section. |
| | | 8 | | /// </summary> |
| | | 9 | | public static class SettingsLoader |
| | | 10 | | { |
| | | 11 | | public const string EnvVarPrefix = "SHOPGUARD_"; |
| | | 12 | | |
| | | 13 | | /// <summary> |
| | | 14 | | /// Loads settings for the given environment (e.g. "local", "staging"). |
| | | 15 | | /// The JSON file contains a root "TestSettings" section plus optional |
| | | 16 | | /// per-environment override sections ("Environments:{name}"). |
| | | 17 | | /// </summary> |
| | | 18 | | public static TestSettings Load(string jsonPath, string environment = "local") |
| | | 19 | | { |
| | 5 | 20 | | if (!File.Exists(jsonPath)) |
| | | 21 | | { |
| | 1 | 22 | | throw new FileNotFoundException($"Test configuration not found: {jsonPath}", jsonPath); |
| | | 23 | | } |
| | | 24 | | |
| | 4 | 25 | | var configuration = new ConfigurationBuilder() |
| | 4 | 26 | | .AddJsonFile(jsonPath, optional: false) |
| | 4 | 27 | | .AddEnvironmentVariables(EnvVarPrefix) |
| | 4 | 28 | | .Build(); |
| | | 29 | | |
| | 4 | 30 | | var settings = configuration.GetSection("TestSettings").Get<TestSettings>() |
| | 4 | 31 | | ?? throw new InvalidOperationException("Section 'TestSettings' is missing or invalid."); |
| | | 32 | | |
| | | 33 | | // Apply per-environment overrides on top of the defaults. |
| | 4 | 34 | | var overrides = configuration.GetSection($"Environments:{environment}"); |
| | 4 | 35 | | if (overrides.Exists()) |
| | | 36 | | { |
| | 1 | 37 | | settings = new TestSettings |
| | 1 | 38 | | { |
| | 1 | 39 | | ShopBaseUrl = overrides["ShopBaseUrl"] ?? settings.ShopBaseUrl, |
| | 1 | 40 | | ApiBaseUrl = overrides["ApiBaseUrl"] ?? settings.ApiBaseUrl, |
| | 1 | 41 | | ApiUsername = overrides["ApiUsername"] ?? settings.ApiUsername, |
| | 1 | 42 | | ApiPassword = overrides["ApiPassword"] ?? settings.ApiPassword, |
| | 1 | 43 | | DbConnectionString = overrides["DbConnectionString"] ?? settings.DbConnectionString, |
| | 1 | 44 | | Headless = bool.TryParse(overrides["Headless"], out var headless) ? headless : settings.Headless, |
| | 1 | 45 | | DefaultTimeoutMs = int.TryParse(overrides["DefaultTimeoutMs"], out var timeout) |
| | 1 | 46 | | ? timeout |
| | 1 | 47 | | : settings.DefaultTimeoutMs, |
| | 1 | 48 | | }; |
| | | 49 | | } |
| | | 50 | | |
| | | 51 | | // HEADLESS=false (CI convenience variable) wins over everything else. |
| | 4 | 52 | | var headlessEnv = Environment.GetEnvironmentVariable("HEADLESS"); |
| | 4 | 53 | | if (bool.TryParse(headlessEnv, out var headlessOverride)) |
| | | 54 | | { |
| | 1 | 55 | | settings = settings with { Headless = headlessOverride }; |
| | | 56 | | } |
| | | 57 | | |
| | 4 | 58 | | return settings; |
| | | 59 | | } |
| | | 60 | | } |