fix(code-review): resolve Batch 2 open findings (AbCip, AbLegacy, Galaxy, FOCAS)

- Driver.AbCip.Contracts-001: parse 'writable' from TagConfig JSON (default true) instead of hardcoding
- Driver.AbCip.Contracts-002/-003: Dt type comment; drop dead [Display]/[Range] annotations
- Driver.AbCip.Contracts-004: dedicated AbCipEquipmentTagParser test class (+15)
- Driver.AbCip-017: document Tick severity Low-fallback on Bad severity read
- Driver.AbLegacy.Contracts-002/-003/-004: isArray-scalar remarks (+tests), MaxTagBytes/ForFamily docs
- Driver.Galaxy.Browser-003 + Driver.Galaxy.Contracts-003: extract ResolveApiKey -> GalaxySecretRef (dedup)
- Driver.Galaxy-019: cache buffered-interval only on Ok + ILogger warnings + ClassifyIntervalReply (+tests)
- Driver.FOCAS.Contracts-002: thread WriteIdempotent through DiscoverAsync (+test)
This commit is contained in:
Joseph Doherty
2026-06-20 22:43:36 -04:00
parent 3cc6a5f30d
commit ab57e53b92
26 changed files with 577 additions and 220 deletions
@@ -1,14 +1,17 @@
using Microsoft.Extensions.Logging;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests;
/// <summary>
/// Follow-up #2 — pins the three resolution forms supported by
/// <see cref="GalaxyDriver.ResolveApiKey"/>: <c>env:NAME</c>, <c>file:PATH</c>,
/// <see cref="GalaxySecretRef.ResolveApiKey"/>: <c>env:NAME</c>, <c>file:PATH</c>,
/// and the literal-string fallback. A future DPAPI arm slots in here without
/// touching the call site.
/// touching the call site. (The resolver was extracted from <c>GalaxyDriver</c> to
/// the shared <c>GalaxySecretRef</c> in Driver.Galaxy.Contracts so the runtime
/// driver and the AdminUI browser share one copy.)
/// </summary>
public sealed class GalaxyDriverApiKeyResolverTests
{
@@ -16,7 +19,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
[Fact]
public void Literal_string_is_returned_unchanged()
{
GalaxyDriver.ResolveApiKey("plain-text-key").ShouldBe("plain-text-key");
GalaxySecretRef.ResolveApiKey("plain-text-key").ShouldBe("plain-text-key");
}
/// <summary>Verifies that env: prefix resolves to an environment variable.</summary>
@@ -27,7 +30,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
Environment.SetEnvironmentVariable(name, "key-from-env");
try
{
GalaxyDriver.ResolveApiKey($"env:{name}").ShouldBe("key-from-env");
GalaxySecretRef.ResolveApiKey($"env:{name}").ShouldBe("key-from-env");
}
finally
{
@@ -43,7 +46,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
Environment.SetEnvironmentVariable(name, null);
var ex = Should.Throw<InvalidOperationException>(() =>
GalaxyDriver.ResolveApiKey($"env:{name}"));
GalaxySecretRef.ResolveApiKey($"env:{name}"));
ex.Message.ShouldContain(name);
ex.Message.ShouldContain("unset");
}
@@ -56,7 +59,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
File.WriteAllText(path, " key-from-file \n");
try
{
GalaxyDriver.ResolveApiKey($"file:{path}").ShouldBe("key-from-file");
GalaxySecretRef.ResolveApiKey($"file:{path}").ShouldBe("key-from-file");
}
finally
{
@@ -70,7 +73,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
{
var path = Path.Combine(Path.GetTempPath(), $"does-not-exist-{Guid.NewGuid():N}.txt");
var ex = Should.Throw<InvalidOperationException>(() =>
GalaxyDriver.ResolveApiKey($"file:{path}"));
GalaxySecretRef.ResolveApiKey($"file:{path}"));
ex.Message.ShouldContain(path);
ex.Message.ShouldContain("doesn't exist");
}
@@ -85,7 +88,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
// in the DriverConfig JSON. The resolver must surface a warning so an
// operator who committed one by accident sees it at startup.
var logger = new CaptureLogger();
var key = GalaxyDriver.ResolveApiKey("plain-text-key", logger);
var key = GalaxySecretRef.ResolveApiKey("plain-text-key", logger);
key.ShouldBe("plain-text-key");
logger.Entries.ShouldContain(e =>
@@ -100,7 +103,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
// key (dev / parity rig). The resolver must accept it AND suppress the
// warning so production logs aren't polluted on a deliberate dev choice.
var logger = new CaptureLogger();
var key = GalaxyDriver.ResolveApiKey("dev:plain-text-key", logger);
var key = GalaxySecretRef.ResolveApiKey("dev:plain-text-key", logger);
key.ShouldBe("plain-text-key");
logger.Entries.ShouldNotContain(e => e.Level == LogLevel.Warning);
@@ -115,7 +118,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
try
{
var logger = new CaptureLogger();
GalaxyDriver.ResolveApiKey($"env:{name}", logger);
GalaxySecretRef.ResolveApiKey($"env:{name}", logger);
logger.Entries.ShouldNotContain(e => e.Level == LogLevel.Warning);
}
finally
@@ -150,7 +153,7 @@ public sealed class GalaxyDriverApiKeyResolverTests
try
{
var ex = Should.Throw<InvalidOperationException>(() =>
GalaxyDriver.ResolveApiKey($"file:{path}"));
GalaxySecretRef.ResolveApiKey($"file:{path}"));
ex.Message.ShouldContain("empty");
}
finally