feat(secrets): SecretReferenceScanner derives a site's secret refs from its data-connection configs

This commit is contained in:
Joseph Doherty
2026-09-28 17:27:28 -04:00
parent bf7d2db0b1
commit 01cb1f5652
2 changed files with 99 additions and 0 deletions
@@ -0,0 +1,35 @@
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Secrets;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Types.Secrets;
/// <summary>
/// <see cref="SecretReferenceScanner"/> derives, per site, the distinct <c>secret:&lt;name&gt;</c>
/// references found anywhere in its data connections' primary/backup JSON (design §3.1).
/// </summary>
public class SecretReferenceScannerTests
{
private static DataConnectionArtifact Dc(string name, string? primary, string? backup = null)
=> new(name, "MxGateway", primary, backup, 3);
[Fact]
public void FindsNestedRefs_CaseInsensitive_AcrossPrimaryAndBackup()
{
var refs = SecretReferenceScanner.Scan([
Dc("GW1", """{"Endpoint":"http://gw:5000","ApiKey":"SECRET: mxgw-key "}"""),
Dc("GW2", """{"a":{"b":["x","secret:mxgw-key"]}}""", """{"ApiKey":"secret:backup-key"}"""),
]);
Assert.Equal(["backup-key", "mxgw-key"], refs.Select(r => r.Name));
Assert.Equal(["GW1", "GW2"], refs.Single(r => r.Name == "mxgw-key").ReferencedBy);
}
[Theory]
[InlineData("""{"ApiKey":"${secret:node-local}"}""")]
[InlineData("""{"Note":"see secret:x in docs"}""")]
[InlineData("""{"ApiKey":"secret:"}""")]
[InlineData("""{"Port":5000}""")]
[InlineData("not json")]
[InlineData("""{"Psk":"secret:SB-GRPC-PSK-site-a"}""")]
public void IgnoresNonReferences(string json)
=> Assert.Empty(SecretReferenceScanner.Scan([Dc("C", json)]));
}