fix(adminui): confirm before deleting role grants, clusters, nodes and ACLs (lmxopcua#785)
v2-ci / unit-tests (push) Waiting to run
v2-ci / unit-tests (push) Waiting to run
The four Delete buttons deleted on the first click. They now open the shared RawConfirmDialog naming the item; only Confirm deletes. Adds bUnit (test-only) render tests: modal opens without deleting, Cancel keeps the item, Confirm deletes. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
@rendermode RenderMode.InteractiveServer
|
@rendermode RenderMode.InteractiveServer
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using ZB.MOM.WW.OtOpcUa.AdminUI.Acls
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Acls
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration
|
@using ZB.MOM.WW.OtOpcUa.Configuration
|
||||||
@@ -148,12 +149,16 @@ else
|
|||||||
<button type="submit" class="btn btn-primary" disabled="@_busy">@(IsNew ? "Create" : "Save changes")</button>
|
<button type="submit" class="btn btn-primary" disabled="@_busy">@(IsNew ? "Create" : "Save changes")</button>
|
||||||
<a href="/clusters/@ClusterId/acls" class="btn btn-outline-secondary">Cancel</a>
|
<a href="/clusters/@ClusterId/acls" class="btn btn-outline-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
@if (!IsNew) { <button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">Delete</button> }
|
@if (!IsNew) { <button type="button" class="btn btn-outline-danger ms-auto" @onclick="() => _confirmDeleteVisible = true" disabled="@_busy">Delete</button> }
|
||||||
</div>
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
||||||
<AclScopePickerModal Visible="_pickerVisible" ClusterId="@ClusterId"
|
<AclScopePickerModal Visible="_pickerVisible" ClusterId="@ClusterId"
|
||||||
OnPicked="ScopePicked" OnCancel="() => _pickerVisible = false" />
|
OnPicked="ScopePicked" OnCancel="() => _pickerVisible = false" />
|
||||||
|
|
||||||
|
<RawConfirmDialog @bind-Visible="_confirmDeleteVisible" Title="Delete ACL"
|
||||||
|
Message="@($"Delete ACL '{NodeAclId}' (principal '{_form.Principal}') from cluster '{ClusterId}'? This cannot be undone.")"
|
||||||
|
OnConfirm="DeleteAsync" />
|
||||||
}
|
}
|
||||||
</PageErrorBoundary>
|
</PageErrorBoundary>
|
||||||
|
|
||||||
@@ -175,6 +180,7 @@ else
|
|||||||
private NodeAcl? _existing;
|
private NodeAcl? _existing;
|
||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
|
private bool _confirmDeleteVisible;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
private bool _pickerVisible;
|
private bool _pickerVisible;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
@rendermode RenderMode.InteractiveServer
|
@rendermode RenderMode.InteractiveServer
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration
|
@using ZB.MOM.WW.OtOpcUa.Configuration
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
||||||
@@ -99,11 +100,15 @@ else
|
|||||||
</button>
|
</button>
|
||||||
<a href="/clusters/@ClusterId" class="btn btn-outline-secondary">Cancel</a>
|
<a href="/clusters/@ClusterId" class="btn btn-outline-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">
|
<button type="button" class="btn btn-outline-danger ms-auto" @onclick="() => _confirmDeleteVisible = true" disabled="@_busy">
|
||||||
Delete cluster
|
Delete cluster
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
||||||
|
<RawConfirmDialog @bind-Visible="_confirmDeleteVisible" Title="Delete cluster"
|
||||||
|
Message="@($"Delete cluster '{ClusterId}'? This cannot be undone.")"
|
||||||
|
OnConfirm="DeleteAsync" />
|
||||||
}
|
}
|
||||||
</PageErrorBoundary>
|
</PageErrorBoundary>
|
||||||
|
|
||||||
@@ -114,6 +119,7 @@ else
|
|||||||
private ServerCluster? _existing;
|
private ServerCluster? _existing;
|
||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
|
private bool _confirmDeleteVisible;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@rendermode RenderMode.InteractiveServer
|
@rendermode RenderMode.InteractiveServer
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration
|
@using ZB.MOM.WW.OtOpcUa.Configuration
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
||||||
@@ -116,12 +117,16 @@ else
|
|||||||
</div>
|
</div>
|
||||||
@if (!IsNew)
|
@if (!IsNew)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">
|
<button type="button" class="btn btn-outline-danger ms-auto" @onclick="() => _confirmDeleteVisible = true" disabled="@_busy">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
||||||
|
<RawConfirmDialog @bind-Visible="_confirmDeleteVisible" Title="Delete node"
|
||||||
|
Message="@($"Delete node '{NodeId}' from cluster '{ClusterId}'? This cannot be undone.")"
|
||||||
|
OnConfirm="DeleteAsync" />
|
||||||
}
|
}
|
||||||
</PageErrorBoundary>
|
</PageErrorBoundary>
|
||||||
|
|
||||||
@@ -135,6 +140,7 @@ else
|
|||||||
private ClusterNode? _existing;
|
private ClusterNode? _existing;
|
||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
|
private bool _confirmDeleteVisible;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@attribute [Authorize(Policy = AdminUiPolicies.FleetAdmin)]
|
@attribute [Authorize(Policy = AdminUiPolicies.FleetAdmin)]
|
||||||
@rendermode RenderMode.InteractiveServer
|
@rendermode RenderMode.InteractiveServer
|
||||||
@using Microsoft.Extensions.Options
|
@using Microsoft.Extensions.Options
|
||||||
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Services
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Services
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td title="@r.LdapGroup"><span class="mono">@DisplayText.Abbreviate(r.LdapGroup, 48)</span></td>
|
<td title="@r.LdapGroup"><span class="mono">@DisplayText.Abbreviate(r.LdapGroup, 48)</span></td>
|
||||||
<td><span class="chip chip-idle">@r.Role</span></td>
|
<td><span class="chip chip-idle">@r.Role</span></td>
|
||||||
<td><button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteAsync(r.Id)" disabled="@_busy">Delete</button></td>
|
<td><button class="btn btn-sm btn-outline-danger" @onclick="() => _pendingDelete = r" disabled="@_busy">Delete</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,6 +75,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<RawConfirmDialog Visible="@(_pendingDelete is not null)" VisibleChanged="@(v => { if (!v) _pendingDelete = null; })"
|
||||||
|
Title="Delete role grant"
|
||||||
|
Message="@($"Delete the role grant mapping LDAP group '{_pendingDelete?.LdapGroup}' to role {_pendingDelete?.Role}? This cannot be undone.")"
|
||||||
|
OnConfirm="ConfirmDeleteAsync" />
|
||||||
|
|
||||||
@if (_options is not null)
|
@if (_options is not null)
|
||||||
{
|
{
|
||||||
<section class="panel rise mt-3" style="animation-delay:.14s">
|
<section class="panel rise mt-3" style="animation-delay:.14s">
|
||||||
@@ -113,6 +119,7 @@
|
|||||||
private AdminRole _newRole = AdminRole.Viewer;
|
private AdminRole _newRole = AdminRole.Viewer;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
|
private LdapGroupRoleMapping? _pendingDelete;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
@@ -143,6 +150,13 @@
|
|||||||
finally { _busy = false; }
|
finally { _busy = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ConfirmDeleteAsync()
|
||||||
|
{
|
||||||
|
var id = _pendingDelete?.Id;
|
||||||
|
_pendingDelete = null;
|
||||||
|
if (id is Guid g) await DeleteAsync(g);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeleteAsync(Guid id)
|
private async Task DeleteAsync(Guid id)
|
||||||
{
|
{
|
||||||
_error = null; _busy = true;
|
_error = null; _busy = true;
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
using Bunit;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Acls;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Clusters;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Acls;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Services;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Pages;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// lmxopcua#785: the Delete buttons on RoleGrants, ClusterEdit, NodeEdit and AclEdit deleted on the
|
||||||
|
/// first click. Each must open the shared <c>RawConfirmDialog</c>; only Confirm deletes, Cancel keeps the item.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class DeleteConfirmRenderTests : BunitContext
|
||||||
|
{
|
||||||
|
private const string ModalSelector = ".modal-dialog";
|
||||||
|
|
||||||
|
private IDbContextFactory<OtOpcUaConfigDbContext> NewDb(Action<OtOpcUaConfigDbContext> seed)
|
||||||
|
{
|
||||||
|
var name = $"del-{Guid.NewGuid():N}";
|
||||||
|
using (var db = RawTreeTestDb.CreateNamed(name)) { seed(db); db.SaveChanges(); }
|
||||||
|
var factory = RawTreeTestDb.Factory(name);
|
||||||
|
Services.AddSingleton(factory);
|
||||||
|
Services.AddSingleton<AuthenticationStateProvider>(new FakeAuth());
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SeedCluster(OtOpcUaConfigDbContext db) =>
|
||||||
|
db.ServerClusters.Add(new ServerCluster
|
||||||
|
{
|
||||||
|
ClusterId = "C1", Name = "Cluster One", Enterprise = "zb", Site = "s1",
|
||||||
|
RedundancyMode = RedundancyMode.None, CreatedBy = "test",
|
||||||
|
});
|
||||||
|
|
||||||
|
private static void ClickDelete(IRenderedComponent<IComponent> cut) =>
|
||||||
|
cut.Find("button.btn-outline-danger").Click();
|
||||||
|
|
||||||
|
// ---------------- ClusterEdit ----------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ClusterEdit_Delete_confirms_first()
|
||||||
|
{
|
||||||
|
var f = NewDb(SeedCluster);
|
||||||
|
var cut = Render<ClusterEdit>(p => p.Add(x => x.ClusterId, "C1"));
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
|
||||||
|
ClickDelete(cut);
|
||||||
|
cut.Find(ModalSelector).TextContent.ShouldContain("C1");
|
||||||
|
ClusterCount(f).ShouldBe(1); // nothing deleted by merely opening the modal
|
||||||
|
|
||||||
|
cut.Find(".modal-footer .btn-outline-secondary").Click(); // Cancel
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
ClusterCount(f).ShouldBe(1);
|
||||||
|
|
||||||
|
ClickDelete(cut);
|
||||||
|
cut.Find(".modal-footer .btn-danger").Click(); // Confirm
|
||||||
|
cut.WaitForAssertion(() => ClusterCount(f).ShouldBe(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- NodeEdit ----------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NodeEdit_Delete_confirms_first()
|
||||||
|
{
|
||||||
|
var f = NewDb(db =>
|
||||||
|
{
|
||||||
|
SeedCluster(db);
|
||||||
|
db.ClusterNodes.Add(new ClusterNode
|
||||||
|
{
|
||||||
|
NodeId = "n1", ClusterId = "C1", Host = "h1", ApplicationUri = "urn:n1", CreatedBy = "test",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var cut = Render<NodeEdit>(p => p.Add(x => x.ClusterId, "C1").Add(x => x.NodeId, "n1"));
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
|
||||||
|
ClickDelete(cut);
|
||||||
|
cut.Find(ModalSelector).TextContent.ShouldContain("n1");
|
||||||
|
NodeCount(f).ShouldBe(1);
|
||||||
|
|
||||||
|
cut.Find(".modal-footer .btn-outline-secondary").Click();
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
NodeCount(f).ShouldBe(1);
|
||||||
|
|
||||||
|
ClickDelete(cut);
|
||||||
|
cut.Find(".modal-footer .btn-danger").Click();
|
||||||
|
cut.WaitForAssertion(() => NodeCount(f).ShouldBe(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- AclEdit ----------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AclEdit_Delete_confirms_first()
|
||||||
|
{
|
||||||
|
var f = NewDb(db =>
|
||||||
|
{
|
||||||
|
SeedCluster(db);
|
||||||
|
db.NodeAcls.Add(new NodeAcl
|
||||||
|
{
|
||||||
|
NodeAclId = "acl-1", ClusterId = "C1", Principal = "ops-team",
|
||||||
|
ScopeKind = NodeAclScopeKind.Cluster, PermissionFlags = NodePermissions.Browse,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Services.AddSingleton<IAclScopeService>(
|
||||||
|
new AclScopeService(f, new RawTreeService(f), new UnsTreeService(f)));
|
||||||
|
var cut = Render<AclEdit>(p => p.Add(x => x.ClusterId, "C1").Add(x => x.NodeAclId, "acl-1"));
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
|
||||||
|
ClickDelete(cut);
|
||||||
|
cut.Find(ModalSelector).TextContent.ShouldContain("acl-1");
|
||||||
|
AclCount(f).ShouldBe(1);
|
||||||
|
|
||||||
|
cut.Find(".modal-footer .btn-outline-secondary").Click();
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
AclCount(f).ShouldBe(1);
|
||||||
|
|
||||||
|
ClickDelete(cut);
|
||||||
|
cut.Find(".modal-footer .btn-danger").Click();
|
||||||
|
cut.WaitForAssertion(() => AclCount(f).ShouldBe(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- RoleGrants ----------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RoleGrants_Delete_confirms_first()
|
||||||
|
{
|
||||||
|
var svc = new FakeMappings(new LdapGroupRoleMapping
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(), LdapGroup = "UG_Admins", Role = AdminRole.Viewer, IsSystemWide = true,
|
||||||
|
});
|
||||||
|
Services.AddSingleton<ILdapGroupRoleMappingService>(svc);
|
||||||
|
Services.AddSingleton<IOptionsSnapshot<LdapOptions>>(new FakeOptions());
|
||||||
|
var cut = Render<RoleGrants>();
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
|
||||||
|
cut.Find("table button.btn-outline-danger").Click();
|
||||||
|
cut.Find(ModalSelector).TextContent.ShouldContain("UG_Admins");
|
||||||
|
svc.DeleteCalls.ShouldBe(0);
|
||||||
|
|
||||||
|
cut.Find(".modal-footer .btn-outline-secondary").Click();
|
||||||
|
cut.FindAll(ModalSelector).ShouldBeEmpty();
|
||||||
|
svc.DeleteCalls.ShouldBe(0);
|
||||||
|
cut.Markup.ShouldContain("UG_Admins");
|
||||||
|
|
||||||
|
cut.Find("table button.btn-outline-danger").Click();
|
||||||
|
cut.Find(".modal-footer .btn-danger").Click();
|
||||||
|
cut.WaitForAssertion(() => svc.DeleteCalls.ShouldBe(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ClusterCount(IDbContextFactory<OtOpcUaConfigDbContext> f) { using var d = f.CreateDbContext(); return d.ServerClusters.Count(); }
|
||||||
|
private static int NodeCount(IDbContextFactory<OtOpcUaConfigDbContext> f) { using var d = f.CreateDbContext(); return d.ClusterNodes.Count(); }
|
||||||
|
private static int AclCount(IDbContextFactory<OtOpcUaConfigDbContext> f) { using var d = f.CreateDbContext(); return d.NodeAcls.Count(); }
|
||||||
|
|
||||||
|
private sealed class FakeAuth : AuthenticationStateProvider
|
||||||
|
{
|
||||||
|
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
|
||||||
|
Task.FromResult(new AuthenticationState(new System.Security.Claims.ClaimsPrincipal(
|
||||||
|
new System.Security.Claims.ClaimsIdentity("test"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeOptions : IOptionsSnapshot<LdapOptions>
|
||||||
|
{
|
||||||
|
public LdapOptions Value { get; } = new();
|
||||||
|
public LdapOptions Get(string? name) => Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeMappings(LdapGroupRoleMapping row) : ILdapGroupRoleMappingService
|
||||||
|
{
|
||||||
|
private readonly List<LdapGroupRoleMapping> _rows = [row];
|
||||||
|
public int DeleteCalls { get; private set; }
|
||||||
|
public Task<IReadOnlyList<LdapGroupRoleMapping>> GetByGroupsAsync(IEnumerable<string> g, CancellationToken ct) =>
|
||||||
|
Task.FromResult<IReadOnlyList<LdapGroupRoleMapping>>(_rows.ToList());
|
||||||
|
public Task<IReadOnlyList<LdapGroupRoleMapping>> ListAllAsync(CancellationToken ct) =>
|
||||||
|
Task.FromResult<IReadOnlyList<LdapGroupRoleMapping>>(_rows.ToList());
|
||||||
|
public Task<LdapGroupRoleMapping> CreateAsync(LdapGroupRoleMapping r, CancellationToken ct) { _rows.Add(r); return Task.FromResult(r); }
|
||||||
|
public Task DeleteAsync(Guid id, CancellationToken ct) { DeleteCalls++; _rows.RemoveAll(r => r.Id == id); return Task.CompletedTask; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="xunit"/>
|
<PackageReference Include="xunit"/>
|
||||||
<PackageReference Include="Shouldly"/>
|
<PackageReference Include="Shouldly"/>
|
||||||
|
<PackageReference Include="bunit"/>
|
||||||
|
<!-- bUnit (test-only) pulls AngleSharp 1.1.1 (GHSA-pgww-w46g-26qg, moderate, HTML parsing of untrusted
|
||||||
|
input). Tests only parse our own rendered markup; patched AngleSharp releases break bunit 2.0.33-preview
|
||||||
|
(TypeLoadException), so the advisory is suppressed here rather than overriding the version. -->
|
||||||
|
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-pgww-w46g-26qg"/>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory"/>
|
||||||
<!-- AdminUiSqlServerFixture drives a real SQL Server (create/migrate/drop) for the two suites
|
<!-- AdminUiSqlServerFixture drives a real SQL Server (create/migrate/drop) for the two suites
|
||||||
whose subject is the SQL provider's LINQ translation, not the C# around it. SqlConnection
|
whose subject is the SQL provider's LINQ translation, not the C# around it. SqlConnection
|
||||||
|
|||||||
Reference in New Issue
Block a user