166 lines
7.8 KiB
C#
166 lines
7.8 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Api.Contracts;
|
|
using Tiku.Application.PlatformAdmin;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Domain.Common;
|
|
using Tiku.Domain.Identity;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Domain.Platform;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.IntegrationTests.Api;
|
|
|
|
public sealed class PlatformApprovalEndpointTests
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = CreateJsonOptions();
|
|
|
|
[Fact]
|
|
public async Task Tenant_archive_requires_another_authorized_operator_and_executes_once()
|
|
{
|
|
await using var factory = new ApiTestFactory(configurationOverrides: new Dictionary<string, string?>
|
|
{
|
|
["Tenancy:Resolution:PlatformHosts:0"] = "localhost"
|
|
});
|
|
var permissions = new[]
|
|
{
|
|
BackendPermissions.PlatformDashboardView,
|
|
BackendPermissions.PlatformTenantManage,
|
|
BackendPermissions.PlatformApprovalView,
|
|
BackendPermissions.PlatformApprovalDecide
|
|
};
|
|
var (requester, approver) = await SeedActorsAsync(factory, permissions);
|
|
var tenant = new Tenant
|
|
{
|
|
Slug = $"approval-{Guid.NewGuid():N}"[..30], Name = "Approval Tenant", Status = TenantStatus.Active,
|
|
BillingStatus = BillingStatus.Active
|
|
};
|
|
await factory.SeedAsync(
|
|
tenant,
|
|
new PlatformApprovalPolicy
|
|
{
|
|
Code = PlatformApprovalPolicyCodes.TenantArchive,
|
|
Name = "Tenant archive",
|
|
RequiredPermission = BackendPermissions.PlatformTenantManage,
|
|
AlwaysRequireApproval = true
|
|
});
|
|
|
|
using var requesterClient = factory.CreateClient();
|
|
requesterClient.UseAccessToken(await requesterClient.LoginAsPlatformAsync(requester.Email));
|
|
using var archive = new HttpRequestMessage(HttpMethod.Patch, "/api/platform/tenants/status")
|
|
{
|
|
Content = JsonContent.Create(new UpdatePlatformTenantStatusDto
|
|
{ TenantId = tenant.Id, Status = TenantStatus.Archived, Reason = "Close expired customer" })
|
|
};
|
|
archive.Headers.Add("Idempotency-Key", "archive-approval-1");
|
|
var submission = await requesterClient.SendAsync(archive);
|
|
Assert.Equal(HttpStatusCode.Accepted, submission.StatusCode);
|
|
var body = await submission.Content.ReadFromJsonAsync<PlatformCommandSubmission>(JsonOptions);
|
|
Assert.NotNull(body?.ApprovalRequest);
|
|
|
|
var selfApproval = await requesterClient.PostAsJsonAsync(
|
|
$"/api/platform/approvals/{body.ApprovalRequest.Id}/approve",
|
|
new PlatformApprovalDecisionDto("Self approval"));
|
|
Assert.Equal(HttpStatusCode.Conflict, selfApproval.StatusCode);
|
|
|
|
using var approverClient = factory.CreateClient();
|
|
approverClient.UseAccessToken(await approverClient.LoginAsPlatformAsync(approver.Email));
|
|
var approval = await approverClient.PostAsJsonAsync(
|
|
$"/api/platform/approvals/{body.ApprovalRequest.Id}/approve",
|
|
new PlatformApprovalDecisionDto("Independent verification complete"));
|
|
Assert.Equal(HttpStatusCode.OK, approval.StatusCode);
|
|
var approved = await approval.Content.ReadFromJsonAsync<PlatformApprovalRequestItem>(JsonOptions);
|
|
Assert.Equal(PlatformApprovalRequestStatus.Approved, approved?.Status);
|
|
|
|
using (var executionScope = factory.CreateSystemScope("Execute approved platform command"))
|
|
{
|
|
var processor = executionScope.ServiceProvider.GetRequiredService<IPlatformApprovalService>();
|
|
Assert.Equal(1, await processor.ProcessApprovedAsync());
|
|
}
|
|
|
|
var replay = await approverClient.PostAsJsonAsync($"/api/platform/approvals/{body.ApprovalRequest.Id}/approve",
|
|
new PlatformApprovalDecisionDto("Replay"));
|
|
Assert.Equal(HttpStatusCode.Conflict, replay.StatusCode);
|
|
using var scope = factory.CreateSystemScope("Verify platform approval execution");
|
|
var db = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
|
var executed = await db.PlatformApprovalRequests.AsNoTracking()
|
|
.SingleAsync(item => item.Id == body.ApprovalRequest.Id);
|
|
Assert.True(executed.Status == PlatformApprovalRequestStatus.Succeeded,
|
|
$"Approval execution failed: {executed.Error}");
|
|
Assert.Equal(TenantStatus.Archived,
|
|
await db.Tenants.Where(item => item.Id == tenant.Id).Select(item => item.Status).SingleAsync());
|
|
Assert.True(await db.AuditLogs.AnyAsync(item =>
|
|
item.Action == "platform.approval.executed" && item.TargetId == body.ApprovalRequest.Id.ToString("N")));
|
|
}
|
|
|
|
private static async Task<((Guid UserId, string Email) Requester, (Guid UserId, string Email) Approver)>
|
|
SeedActorsAsync(
|
|
ApiTestFactory factory,
|
|
IReadOnlyCollection<string> permissionCodes)
|
|
{
|
|
var requester = (UserId: Guid.NewGuid(), Email: $"approval-requester-{Guid.NewGuid():N}@example.test");
|
|
var approver = (UserId: Guid.NewGuid(), Email: $"approval-approver-{Guid.NewGuid():N}@example.test");
|
|
var requesterRole = Guid.NewGuid();
|
|
var approverRole = Guid.NewGuid();
|
|
var modules = permissionCodes.Select(PermissionModuleCatalog.ResolvePermissionModuleCode)
|
|
.Distinct(StringComparer.Ordinal)
|
|
.Select(code => new PermissionModule
|
|
{
|
|
Code = code, Name = code, Area = BackendPermissionArea.Platform,
|
|
RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code]
|
|
});
|
|
var permissions = permissionCodes.Select(code => new BackendPermission
|
|
{
|
|
Code = code, Name = code, Area = BackendPermissionArea.Platform,
|
|
PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code), IsSystem = true
|
|
});
|
|
await factory.SeedAsync([
|
|
.. modules, .. permissions,
|
|
User(requester.UserId, requester.Email), User(approver.UserId, approver.Email),
|
|
new PlatformBackendRole
|
|
{
|
|
Id = requesterRole, Code = $"approval_requester_{requesterRole:N}", Name = "Approval Requester",
|
|
Status = BackendRoleStatus.Active
|
|
},
|
|
new PlatformBackendRole
|
|
{
|
|
Id = approverRole, Code = $"approval_approver_{approverRole:N}", Name = "Approval Approver",
|
|
Status = BackendRoleStatus.Active
|
|
},
|
|
.. permissionCodes.Select(code => new PlatformBackendRolePermission
|
|
{ RoleId = requesterRole, PermissionCode = code }),
|
|
.. permissionCodes.Select(code => new PlatformBackendRolePermission
|
|
{ RoleId = approverRole, PermissionCode = code }),
|
|
new PlatformBackendUserRole { UserId = requester.UserId, RoleId = requesterRole },
|
|
new PlatformBackendUserRole { UserId = approver.UserId, RoleId = approverRole }
|
|
]);
|
|
return (requester, approver);
|
|
}
|
|
|
|
private static User User(Guid id, string email)
|
|
{
|
|
return new User
|
|
{
|
|
Id = id,
|
|
Email = email,
|
|
NormalizedEmail = email.ToUpperInvariant(),
|
|
UserName = email,
|
|
NormalizedUserName = email.ToUpperInvariant(),
|
|
Name = email,
|
|
PrimaryRole = "platform_admin",
|
|
RawProfile = JsonDefaults.Object()
|
|
}.WithTestPassword();
|
|
}
|
|
|
|
private static JsonSerializerOptions CreateJsonOptions()
|
|
{
|
|
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
|
options.Converters.Add(new JsonStringEnumConverter());
|
|
return options;
|
|
}
|
|
} |