review(Driver.Galaxy.Browser): fix mis-shifted MapSecurityClass codes (High)

Review at HEAD 7286d320. Driver.Galaxy.Browser-001 (High): MapSecurityClass codes 2-6 were
all shifted vs the runtime SecurityClassification enum (wrong security labels in the picker)
-> corrected all 7 arms + tests. -002: DisposeAsync swallows concurrent ObjectDisposedException.
-003 (ResolveApiKey dup) deferred to Contracts.
This commit is contained in:
Joseph Doherty
2026-06-19 10:52:23 -04:00
parent 3c908f1df0
commit 960d76ffcb
3 changed files with 236 additions and 7 deletions
@@ -112,4 +112,57 @@ public sealed class GalaxyBrowseSessionTests
await Should.ThrowAsync<ArgumentException>(
() => session.ExpandAsync("Galaxy.Unknown", TestContext.Current.CancellationToken));
}
/// <summary>
/// Verifies that each of the seven defined Galaxy security_classification codes
/// maps to the correct label string — matching the <c>SecurityClassification</c>
/// enum ordinals and the runtime <c>SecurityMap</c> in Driver.Galaxy.
/// (Regression for Driver.Galaxy.Browser-001: codes 26 were all wrong before
/// this fix.)
/// </summary>
[Theory]
[InlineData(0, "FreeAccess")]
[InlineData(1, "Operate")]
[InlineData(2, "SecuredWrite")]
[InlineData(3, "VerifiedWrite")]
[InlineData(4, "Tune")]
[InlineData(5, "Configure")]
[InlineData(6, "ViewOnly")]
public void MapSecurityClass_maps_all_known_codes(int code, string expectedLabel)
{
GalaxyBrowseSession.MapSecurityClass(code).ShouldBe(expectedLabel);
}
/// <summary>
/// An unrecognised Galaxy security_classification code must produce an
/// <c>Unknown(N)</c> label — not throw and not silently return a valid class.
/// </summary>
[Theory]
[InlineData(7)]
[InlineData(99)]
[InlineData(-1)]
public void MapSecurityClass_unknown_code_returns_Unknown_label(int code)
{
var label = GalaxyBrowseSession.MapSecurityClass(code);
label.ShouldStartWith("Unknown(");
label.ShouldContain(code.ToString());
}
/// <summary>
/// Two concurrent <see cref="GalaxyBrowseSession.DisposeAsync"/> calls on the
/// same session must not throw. The registry reaper and a browser-side Close
/// can race in production.
/// (Regression for Driver.Galaxy.Browser-002: the second caller could throw
/// <see cref="ObjectDisposedException"/> from <c>SemaphoreSlim.Dispose()</c>.)
/// </summary>
[Fact]
public async Task DisposeAsync_concurrent_calls_do_not_throw()
{
var session = new GalaxyBrowseSession(NewClient());
// Fire two concurrent dispose calls and await both — neither must throw.
await Should.NotThrowAsync(async () =>
{
await Task.WhenAll(session.DisposeAsync().AsTask(), session.DisposeAsync().AsTask());
});
}
}