513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
149 lines
6.4 KiB
C#
149 lines
6.4 KiB
C#
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// Inbound-API key re-arch (C3): unit tests for <see cref="ApiMethodKeyScopeReconciler"/>, which
|
|
/// inverts the API-method form's "Approved API Keys" selection into per-key method-scope edits.
|
|
/// Covers approve, revoke (preserving other scopes), the empty-last-scope guard, and the no-op case.
|
|
/// </summary>
|
|
public sealed class ApiMethodKeyScopeReconcilerTests
|
|
{
|
|
private static IReadOnlyDictionary<string, IReadOnlyList<string>> Current(
|
|
params (string KeyId, string[] Methods)[] entries) =>
|
|
entries.ToDictionary(
|
|
e => e.KeyId,
|
|
e => (IReadOnlyList<string>)e.Methods.ToList(),
|
|
StringComparer.Ordinal);
|
|
|
|
private static IReadOnlyDictionary<string, string> Names(params (string KeyId, string Name)[] entries) =>
|
|
entries.ToDictionary(e => e.KeyId, e => e.Name, StringComparer.Ordinal);
|
|
|
|
/// <summary>Verifies that approve adds method to key preserving existing scopes.</summary>
|
|
[Fact]
|
|
public void Approve_AddsMethodToKey_PreservingExistingScopes()
|
|
{
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string> { "k1" },
|
|
initialKeyIds: new HashSet<string>(),
|
|
currentMethodsByKey: Current(("k1", new[] { "GetStatus" })),
|
|
keyNamesById: Names(("k1", "Key One")));
|
|
|
|
Assert.Empty(result.EmptyScopeKeyNames);
|
|
var update = Assert.Single(result.Updates);
|
|
Assert.Equal("k1", update.KeyId);
|
|
Assert.Equal(new[] { "GetStatus", "PlaceOrder" }, update.NewMethods);
|
|
}
|
|
|
|
/// <summary>Verifies that approve key with no existing scopes gets just this method.</summary>
|
|
[Fact]
|
|
public void Approve_KeyWithNoExistingScopes_GetsJustThisMethod()
|
|
{
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string> { "k1" },
|
|
initialKeyIds: new HashSet<string>(),
|
|
currentMethodsByKey: Current(("k1", Array.Empty<string>())),
|
|
keyNamesById: Names(("k1", "Key One")));
|
|
|
|
var update = Assert.Single(result.Updates);
|
|
Assert.Equal(new[] { "PlaceOrder" }, update.NewMethods);
|
|
}
|
|
|
|
/// <summary>Verifies that revoke removes method leaving other scopes intact.</summary>
|
|
[Fact]
|
|
public void Revoke_RemovesMethod_LeavingOtherScopesIntact()
|
|
{
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string>(),
|
|
initialKeyIds: new HashSet<string> { "k1" },
|
|
currentMethodsByKey: Current(("k1", new[] { "PlaceOrder", "GetStatus" })),
|
|
keyNamesById: Names(("k1", "Key One")));
|
|
|
|
Assert.Empty(result.EmptyScopeKeyNames);
|
|
var update = Assert.Single(result.Updates);
|
|
Assert.Equal("k1", update.KeyId);
|
|
Assert.Equal(new[] { "GetStatus" }, update.NewMethods);
|
|
}
|
|
|
|
/// <summary>Verifies that revoke last scope reported as empty conflict and not in updates.</summary>
|
|
[Fact]
|
|
public void Revoke_LastScope_ReportedAsEmptyConflict_AndNotInUpdates()
|
|
{
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string>(),
|
|
initialKeyIds: new HashSet<string> { "k1" },
|
|
currentMethodsByKey: Current(("k1", new[] { "PlaceOrder" })),
|
|
keyNamesById: Names(("k1", "Key One")));
|
|
|
|
Assert.Empty(result.Updates);
|
|
var emptyName = Assert.Single(result.EmptyScopeKeyNames);
|
|
Assert.Equal("Key One", emptyName);
|
|
}
|
|
|
|
/// <summary>Verifies that mixed approve one revoke another produces both updates.</summary>
|
|
[Fact]
|
|
public void Mixed_ApproveOneRevokeAnother_ProducesBothUpdates()
|
|
{
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string> { "k2" }, // approve k2
|
|
initialKeyIds: new HashSet<string> { "k1" }, // revoke k1
|
|
currentMethodsByKey: Current(
|
|
("k1", new[] { "PlaceOrder", "GetStatus" }),
|
|
("k2", new[] { "Ping" })),
|
|
keyNamesById: Names(("k1", "Key One"), ("k2", "Key Two")));
|
|
|
|
Assert.Empty(result.EmptyScopeKeyNames);
|
|
Assert.Equal(2, result.Updates.Count);
|
|
|
|
var k1 = result.Updates.Single(u => u.KeyId == "k1");
|
|
Assert.Equal(new[] { "GetStatus" }, k1.NewMethods);
|
|
|
|
var k2 = result.Updates.Single(u => u.KeyId == "k2");
|
|
Assert.Equal(new[] { "Ping", "PlaceOrder" }, k2.NewMethods);
|
|
}
|
|
|
|
/// <summary>Verifies that no change produces no updates.</summary>
|
|
[Fact]
|
|
public void NoChange_ProducesNoUpdates()
|
|
{
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string> { "k1" },
|
|
initialKeyIds: new HashSet<string> { "k1" },
|
|
currentMethodsByKey: Current(("k1", new[] { "PlaceOrder" })),
|
|
keyNamesById: Names(("k1", "Key One")));
|
|
|
|
Assert.Empty(result.Updates);
|
|
Assert.Empty(result.EmptyScopeKeyNames);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concurrent-edit guard: if selected == initial (no diff), the reconciler must produce
|
|
/// ZERO updates even when the live store shows different scopes on that key. The reconciler
|
|
/// only acts on keys that appear in the diff (added or removed relative to <c>initialKeyIds</c>)
|
|
/// — it must never touch keys that are not in the diff, regardless of what their current
|
|
/// live scopes look like.
|
|
/// </summary>
|
|
[Fact]
|
|
public void NoDiff_ProducesNoUpdates_EvenWhenLiveScopesDiffer()
|
|
{
|
|
// k1 was approved at load time and is still approved — no diff.
|
|
// However, a concurrent edit changed k1's live scopes to include an extra method.
|
|
var result = ApiMethodKeyScopeReconciler.Reconcile(
|
|
methodName: "PlaceOrder",
|
|
selectedKeyIds: new HashSet<string> { "k1" },
|
|
initialKeyIds: new HashSet<string> { "k1" },
|
|
currentMethodsByKey: Current(("k1", new[] { "PlaceOrder", "OtherMethod" })),
|
|
keyNamesById: Names(("k1", "Key One")));
|
|
|
|
// k1 is not in the diff → reconciler must not touch it.
|
|
Assert.Empty(result.Updates);
|
|
Assert.Empty(result.EmptyScopeKeyNames);
|
|
}
|
|
}
|