using System.Text.Json;
using Akka.Actor;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.ManagementService;
namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests;
///
/// Inbound-API key re-arch (C2): the ManagementActor API-key management path now drives the
/// Commons seam (added in C1) instead of the SQL Server
/// IInboundApiRepository. These tests exercise the actor against an in-memory fake of
/// the seam (the real LibraryInboundApiKeyAdmin + SQLite mapping is covered end-to-end
/// by the Security project's LibraryInboundApiKeyAdminTests). They verify the actor's
/// dispatch, response shapes (string keyId, one-time token, methods), the preserved ScadaBridge
/// management-audit calls, and that the "Admin" role gate still applies to all five commands.
///
public class ApiKeyCreationTests : TestKit, IDisposable
{
private readonly ServiceCollection _services = new();
private readonly FakeInboundApiKeyAdmin _admin = new();
private readonly IAuditService _auditService = Substitute.For();
public ApiKeyCreationTests()
{
_services.AddScoped(_ => _admin);
_services.AddScoped(_ => _auditService);
}
private IActorRef CreateActor()
{
var sp = _services.BuildServiceProvider();
return Sys.ActorOf(Props.Create(() => new ManagementActor(sp, NullLogger.Instance)));
}
private static ManagementEnvelope Envelope(object command, params string[] roles) =>
new(new AuthenticatedUser("admin", "Admin User", roles, Array.Empty()),
command, Guid.NewGuid().ToString("N"));
void IDisposable.Dispose() => Shutdown();
[Fact]
public void CreateApiKey_ReturnsKeyIdAndOneTimeToken()
{
var actor = CreateActor();
actor.Tell(Envelope(new CreateApiKeyCommand("MES-Production", new[] { "MethodA", "MethodB" }), "Admin"));
var response = ExpectMsg(TimeSpan.FromSeconds(5));
using var doc = JsonDocument.Parse(response.JsonData);
var root = doc.RootElement;
var keyId = root.GetProperty("keyId").GetString();
var token = root.GetProperty("token").GetString();
var name = root.GetProperty("name").GetString();
Assert.False(string.IsNullOrWhiteSpace(keyId));
Assert.False(string.IsNullOrWhiteSpace(token));
Assert.StartsWith($"sbk_{keyId}_", token);
Assert.Equal("MES-Production", name);
// The seam was driven with the supplied name + methods.
var created = Assert.Single(_admin.Keys.Values);
Assert.Equal("MES-Production", created.Name);
Assert.Equal(new[] { "MethodA", "MethodB" }, created.Methods);
}
[Fact]
public void CreateApiKey_AuditsTheCreate()
{
var actor = CreateActor();
actor.Tell(Envelope(new CreateApiKeyCommand("MES-Production", new[] { "MethodA" }), "Admin"));
ExpectMsg(TimeSpan.FromSeconds(5));
_auditService.Received(1).LogAsync(
"admin", "Create", "ApiKey", Arg.Any(), "MES-Production", Arg.Any