Files
tiku-backend.net/Tiku.IntegrationTests/Api/PlatformApprovalEndpointTests.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

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;
}
}