feat(delmia-notifier): config loader + SCADABRIDGE_API_KEY resolution
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier;
|
||||||
|
|
||||||
|
/// <summary>Reads and interprets the notifier's configuration and secret. Parse logic is string-based so it is unit-testable without touching disk.</summary>
|
||||||
|
internal static class ConfigLoader
|
||||||
|
{
|
||||||
|
private const string ApiKeyEnvVar = "SCADABRIDGE_API_KEY";
|
||||||
|
|
||||||
|
/// <summary>Deserialize the <c>appsettings.json</c> text via the source-gen context.</summary>
|
||||||
|
public static NotifierConfig Load(string jsonText) =>
|
||||||
|
JsonSerializer.Deserialize(jsonText, NotifierJsonContext.Default.NotifierConfig) ?? new NotifierConfig();
|
||||||
|
|
||||||
|
/// <summary>Read and parse the <c>appsettings.json</c> sitting next to the executable.</summary>
|
||||||
|
public static NotifierConfig LoadFromDefaultFile()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||||
|
return Load(File.ReadAllText(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Split a comma-separated base-URL list into trimmed, non-empty entries.</summary>
|
||||||
|
public static string[] SplitBaseUrls(string? baseUrls)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(baseUrls))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrls.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Resolve the API key from <c>SCADABRIDGE_API_KEY</c>; null/whitespace → null. Env accessor is injected for testability.</summary>
|
||||||
|
public static string? ResolveApiKey(Func<string, string?> envGet)
|
||||||
|
{
|
||||||
|
var key = envGet(ApiKeyEnvVar);
|
||||||
|
return string.IsNullOrWhiteSpace(key) ? null : key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier;
|
||||||
|
|
||||||
|
/// <summary>Root of <c>appsettings.json</c>; mirrors the <c>ScadaBridge</c> section only.</summary>
|
||||||
|
internal sealed class NotifierConfig
|
||||||
|
{
|
||||||
|
public ScadaBridgeSection ScadaBridge { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ScadaBridgeSection
|
||||||
|
{
|
||||||
|
/// <summary>Comma-separated failover list of base URLs (the method path is appended by the app).</summary>
|
||||||
|
public string? BaseUrls { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Per-attempt HTTP timeout in seconds.</summary>
|
||||||
|
public int TimeoutSeconds { get; set; } = 30;
|
||||||
|
|
||||||
|
/// <summary>Optional diagnostic log file (relative to the exe); null/omitted → stderr only.</summary>
|
||||||
|
public string? LogPath { get; set; }
|
||||||
|
}
|
||||||
@@ -4,4 +4,5 @@ namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier;
|
|||||||
|
|
||||||
[JsonSerializable(typeof(RecipeDownload))]
|
[JsonSerializable(typeof(RecipeDownload))]
|
||||||
[JsonSerializable(typeof(RecipeDownloadResult))]
|
[JsonSerializable(typeof(RecipeDownloadResult))]
|
||||||
|
[JsonSerializable(typeof(NotifierConfig))]
|
||||||
internal partial class NotifierJsonContext : JsonSerializerContext;
|
internal partial class NotifierJsonContext : JsonSerializerContext;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user