diff --git a/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/ConfigLoader.cs b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/ConfigLoader.cs new file mode 100644 index 00000000..3ab9f8af --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/ConfigLoader.cs @@ -0,0 +1,38 @@ +using System.Text.Json; + +namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier; + +/// Reads and interprets the notifier's configuration and secret. Parse logic is string-based so it is unit-testable without touching disk. +internal static class ConfigLoader +{ + private const string ApiKeyEnvVar = "SCADABRIDGE_API_KEY"; + + /// Deserialize the appsettings.json text via the source-gen context. + public static NotifierConfig Load(string jsonText) => + JsonSerializer.Deserialize(jsonText, NotifierJsonContext.Default.NotifierConfig) ?? new NotifierConfig(); + + /// Read and parse the appsettings.json sitting next to the executable. + public static NotifierConfig LoadFromDefaultFile() + { + var path = Path.Combine(AppContext.BaseDirectory, "appsettings.json"); + return Load(File.ReadAllText(path)); + } + + /// Split a comma-separated base-URL list into trimmed, non-empty entries. + public static string[] SplitBaseUrls(string? baseUrls) + { + if (string.IsNullOrWhiteSpace(baseUrls)) + { + return []; + } + + return baseUrls.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + } + + /// Resolve the API key from SCADABRIDGE_API_KEY; null/whitespace → null. Env accessor is injected for testability. + public static string? ResolveApiKey(Func envGet) + { + var key = envGet(ApiKeyEnvVar); + return string.IsNullOrWhiteSpace(key) ? null : key; + } +} diff --git a/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierConfig.cs b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierConfig.cs new file mode 100644 index 00000000..d2c32ad7 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierConfig.cs @@ -0,0 +1,19 @@ +namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier; + +/// Root of appsettings.json; mirrors the ScadaBridge section only. +internal sealed class NotifierConfig +{ + public ScadaBridgeSection ScadaBridge { get; set; } = new(); +} + +internal sealed class ScadaBridgeSection +{ + /// Comma-separated failover list of base URLs (the method path is appended by the app). + public string? BaseUrls { get; set; } + + /// Per-attempt HTTP timeout in seconds. + public int TimeoutSeconds { get; set; } = 30; + + /// Optional diagnostic log file (relative to the exe); null/omitted → stderr only. + public string? LogPath { get; set; } +} diff --git a/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierJsonContext.cs b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierJsonContext.cs index e2684855..91a9cc6f 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierJsonContext.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/NotifierJsonContext.cs @@ -4,4 +4,5 @@ namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier; [JsonSerializable(typeof(RecipeDownload))] [JsonSerializable(typeof(RecipeDownloadResult))] +[JsonSerializable(typeof(NotifierConfig))] internal partial class NotifierJsonContext : JsonSerializerContext; diff --git a/tests/ZB.MOM.WW.ScadaBridge.DelmiaNotifier.Tests/ConfigLoaderTests.cs b/tests/ZB.MOM.WW.ScadaBridge.DelmiaNotifier.Tests/ConfigLoaderTests.cs new file mode 100644 index 00000000..79fae0f8 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.DelmiaNotifier.Tests/ConfigLoaderTests.cs @@ -0,0 +1,50 @@ +using ZB.MOM.WW.ScadaBridge.DelmiaNotifier; + +namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier.Tests; + +public class ConfigLoaderTests +{ + [Fact] + public void SplitBaseUrls_trims_and_drops_empties() + { + var urls = ConfigLoader.SplitBaseUrls("a, b ,,c"); + Assert.Equal(new[] { "a", "b", "c" }, urls); + } + + [Fact] + public void SplitBaseUrls_null_or_whitespace_returns_empty() + { + Assert.Empty(ConfigLoader.SplitBaseUrls(null)); + Assert.Empty(ConfigLoader.SplitBaseUrls(" ")); + } + + [Fact] + public void Load_defaults_timeout_to_30_when_omitted() + { + var cfg = ConfigLoader.Load("{\"ScadaBridge\":{\"BaseUrls\":\"http://x\"}}"); + Assert.Equal(30, cfg.ScadaBridge.TimeoutSeconds); + Assert.Equal("http://x", cfg.ScadaBridge.BaseUrls); + } + + [Fact] + public void Load_reads_all_fields() + { + var cfg = ConfigLoader.Load("{\"ScadaBridge\":{\"BaseUrls\":\"a,b\",\"TimeoutSeconds\":5,\"LogPath\":\"l.log\"}}"); + Assert.Equal("a,b", cfg.ScadaBridge.BaseUrls); + Assert.Equal(5, cfg.ScadaBridge.TimeoutSeconds); + Assert.Equal("l.log", cfg.ScadaBridge.LogPath); + } + + [Fact] + public void ResolveApiKey_returns_null_when_unset_or_whitespace() + { + Assert.Null(ConfigLoader.ResolveApiKey(_ => null)); + Assert.Null(ConfigLoader.ResolveApiKey(_ => " ")); + } + + [Fact] + public void ResolveApiKey_returns_value_when_present() + { + Assert.Equal("sbk_x", ConfigLoader.ResolveApiKey(n => n == "SCADABRIDGE_API_KEY" ? "sbk_x" : null)); + } +}