96 lines
5.6 KiB
C#
96 lines
5.6 KiB
C#
using Akka.Actor;
|
|
using Bunit;
|
|
using Microsoft.AspNetCore.Components.Authorization;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Admin;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
|
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
|
|
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories;
|
|
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Services;
|
|
using ZB.MOM.WW.ScadaBridge.ManagementService;
|
|
using ZB.MOM.WW.ScadaBridge.Security;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Auth;
|
|
|
|
public class UiAuditRegressionTests : BunitContext
|
|
{
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public async Task LdapCreateFromUi_PersistsAuditWithGeneratedMappingId_OrRollsBack(bool failAudit)
|
|
{
|
|
using var connection = new SqliteConnection("Data Source=:memory:");
|
|
connection.Open();
|
|
var options = new DbContextOptionsBuilder<ScadaBridgeDbContext>().UseSqlite(connection).Options;
|
|
Services.AddScoped(_ => new ScadaBridgeDbContext(options));
|
|
Services.AddScoped<ISecurityRepository, SecurityRepository>();
|
|
Services.AddScoped<IConfigurationReadSession, ConfigurationReadSession>();
|
|
Services.AddSingleton(Substitute.For<ISiteRepository>());
|
|
Services.AddScoped<IAuditCorrelationContext, AuditCorrelationContext>();
|
|
if (failAudit)
|
|
{
|
|
var failing = Substitute.For<IAuditService>();
|
|
failing.LogAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<object?>(), Arg.Any<CancellationToken>()).Returns(Task.FromException(new InvalidOperationException("audit unavailable")));
|
|
Services.AddSingleton(failing);
|
|
}
|
|
else Services.AddScoped<IAuditService, AuditService>();
|
|
Services.AddSingleton(Substitute.For<IDialogService>());
|
|
var user = SessionClaimBuilder.Build("administrator", "Administrator", ["admins"],
|
|
new RoleMappingResult([Roles.Administrator], [], true), DateTimeOffset.UtcNow);
|
|
Services.AddSingleton<AuthenticationStateProvider>(new StaticAuth(user));
|
|
Services.AddAuthorizationCore();
|
|
Services.AddScoped<ZB.MOM.WW.ScadaBridge.CentralUI.Services.ConfigurationCommandService>();
|
|
Services.AddSingleton<IConfigurationCommandTransaction, ConfigurationCommandTransaction>();
|
|
Services.AddScoped<ConfigurationCommitActions>();
|
|
Services.AddScoped<IConfigurationCommitActions>(sp => sp.GetRequiredService<ConfigurationCommitActions>());
|
|
Services.AddLogging();
|
|
using var schema = new ScadaBridgeDbContext(options);
|
|
await schema.Database.EnsureCreatedAsync();
|
|
var page = Render<LdapMappingForm>();
|
|
page.Find("input").Change("NewDirectoryGroup");
|
|
page.Find("select").Change(Roles.Viewer);
|
|
page.FindAll("button").Single(b => b.TextContent.Contains("Save")).Click();
|
|
if (failAudit)
|
|
{
|
|
page.WaitForAssertion(() => Assert.Contains("audit unavailable", page.Markup));
|
|
Assert.Empty(await schema.LdapGroupMappings.Where(x => x.LdapGroupName == "NewDirectoryGroup").ToListAsync());
|
|
Assert.Empty(await schema.AuditLogEntries.ToListAsync());
|
|
return;
|
|
}
|
|
page.WaitForAssertion(() => Assert.Single(schema.LdapGroupMappings.AsNoTracking().Where(x => x.LdapGroupName == "NewDirectoryGroup").ToList()));
|
|
var mapping = await schema.LdapGroupMappings.AsNoTracking().SingleAsync(x => x.LdapGroupName == "NewDirectoryGroup");
|
|
var audit = Assert.Single(await schema.AuditLogEntries.AsNoTracking().ToListAsync());
|
|
Assert.Equal(mapping.Id.ToString(), audit.EntityId);
|
|
Assert.Equal("Create", audit.Action);
|
|
// Existing edit form uses the same durable boundary; generated IDs are retained.
|
|
page.Dispose();
|
|
var edit = Render<LdapMappingForm>(parameters => parameters.Add(p => p.Id, mapping.Id));
|
|
edit.Find("input").Change("RenamedDirectoryGroup");
|
|
edit.FindAll("button").Single(b => b.TextContent.Contains("Save")).Click();
|
|
edit.WaitForAssertion(() => Assert.Equal("RenamedDirectoryGroup", schema.LdapGroupMappings.AsNoTracking().Single(x => x.Id == mapping.Id).LdapGroupName));
|
|
edit.Dispose();
|
|
var list = Render<LdapMappings>();
|
|
list.WaitForAssertion(() => Assert.Contains("RenamedDirectoryGroup", list.Markup));
|
|
var row = list.FindAll("tr").Single(r => r.TextContent.Contains("RenamedDirectoryGroup"));
|
|
await row.QuerySelector("button.dropdown-item")!.ClickAsync(new());
|
|
list.WaitForAssertion(() => Assert.DoesNotContain("RenamedDirectoryGroup", list.Markup));
|
|
Assert.False(await schema.LdapGroupMappings.AnyAsync(x => x.Id == mapping.Id));
|
|
var auditRows = await schema.AuditLogEntries.AsNoTracking().OrderBy(x => x.Id).ToListAsync();
|
|
Assert.Equal(new[] { "Create", "Update", "Delete" }, auditRows.Select(x => x.Action));
|
|
Assert.All(auditRows, row => Assert.Equal(mapping.Id.ToString(), row.EntityId));
|
|
}
|
|
|
|
private sealed class StaticAuth(System.Security.Claims.ClaimsPrincipal principal) : AuthenticationStateProvider
|
|
{
|
|
public override Task<AuthenticationState> GetAuthenticationStateAsync() => Task.FromResult(new AuthenticationState(principal));
|
|
}
|
|
}
|