diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
index 22146f07..16db7dbe 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
@@ -481,6 +481,22 @@ public static partial class CliRunner
return RequireId(doc, "shared-script create");
}
+ ///
+ /// Creates an LDAP→role mapping via security role-mapping create and returns its new id.
+ ///
+ /// LDAP group name to map (typically from ).
+ /// Role to grant members of the group; defaults to Designer.
+ ///
+ /// The CLI failed, or the response did not carry an integer id.
+ ///
+ public static async Task CreateRoleMappingAsync(string ldapGroup, string role = "Designer")
+ {
+ using var doc = await RunJsonAsync(
+ "security", "role-mapping", "create",
+ "--ldap-group", ldapGroup, "--role", role);
+ return RequireId(doc, "security role-mapping create");
+ }
+
///
/// Returns the ids of all external systems whose name starts with
/// , via external-system list. Used to delete an
@@ -514,6 +530,34 @@ public static partial class CliRunner
/// Best-effort delete of a shared script via shared-script delete for teardown.
public static Task DeleteSharedScriptAsync(int id) => BestEffortAsync("shared-script", "delete", id);
+ ///
+ /// Best-effort delete of an LDAP→role mapping via security role-mapping delete for teardown;
+ /// swallows any failure (the entity may already be gone).
+ ///
+ /// Role-mapping id.
+ ///
+ /// This method intentionally does NOT delegate to
+ /// even though the behaviour is identical. models
+ /// two-word commands (<group> <verb>), whereas
+ /// security role-mapping delete is a three-word command; extracting it would
+ /// require changing 's signature or adding an overload.
+ /// The inline try/catch is kept here deliberately — same pattern as
+ /// .
+ ///
+ public static async Task DeleteRoleMappingAsync(int id)
+ {
+ try
+ {
+ await RunAsync(
+ "security", "role-mapping", "delete",
+ "--id", id.ToString(System.Globalization.CultureInfo.InvariantCulture));
+ }
+ catch
+ {
+ // Best-effort teardown — never mask the test's own failure.
+ }
+ }
+
///
/// Exports a Transport bundle scoped to a single template via
/// bundle export.
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunnerHelpersTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunnerHelpersTests.cs
index 75d458ef..76793453 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunnerHelpersTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunnerHelpersTests.cs
@@ -165,4 +165,27 @@ public class CliRunnerHelpersTests
try { Assert.True(id > 0); }
finally { await CliRunner.DeleteSharedScriptAsync(id); }
}
+
+ ///
+ /// A freshly created LDAP→role mapping returns a positive id, is discoverable by its
+ /// ldapGroupName in security role-mapping list, and is cleanly deleted in
+ /// teardown, exercising and
+ /// as a round-trip.
+ ///
+ [SkippableFact]
+ public async Task CreateThenDeleteRoleMapping_RoundTrips()
+ {
+ Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
+ var group = CliRunner.UniqueName("grp");
+ var id = await CliRunner.CreateRoleMappingAsync(group, "Designer");
+ try
+ {
+ Assert.True(id > 0);
+ using var list = await CliRunner.RunJsonAsync("security", "role-mapping", "list");
+ Assert.Contains(
+ list.RootElement.EnumerateArray(),
+ e => e.TryGetProperty("ldapGroupName", out var n) && n.GetString() == group);
+ }
+ finally { await CliRunner.DeleteRoleMappingAsync(id); }
+ }
}