forked from xiongyuxing/tiku-backend.net
169 lines
7.0 KiB
C#
169 lines
7.0 KiB
C#
using System.Net;
|
|
using System.Text.Json;
|
|
using Tiku.Application.Backoffice;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Domain.Common;
|
|
using Tiku.Domain.Identity;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Auth;
|
|
|
|
namespace Tiku.IntegrationTests.Api;
|
|
|
|
public sealed class BackofficeUiBootstrapTests
|
|
{
|
|
[Fact]
|
|
public async Task TenantUiBootstrap_ReturnsOnlyMenusAllowedByEffectivePermissions()
|
|
{
|
|
var commandRecorder = new RecordingDbCommandInterceptor();
|
|
await using var factory = new ApiTestFactory(dbCommandInterceptor: commandRecorder);
|
|
await factory.SeedBuiltinBackofficeCatalogAsync();
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var roleId = Guid.NewGuid();
|
|
var phone = "13710000000";
|
|
await factory.SeedAsync(
|
|
new Tenant
|
|
{
|
|
Id = tenantId,
|
|
Slug = tenantId.ToString("N"),
|
|
Name = "Scoped UI Tenant",
|
|
Status = TenantStatus.Active,
|
|
Metadata = JsonDefaults.Object()
|
|
},
|
|
new User
|
|
{
|
|
Id = userId,
|
|
Phone = phone,
|
|
Name = "Dashboard Operator"
|
|
}.WithTestPassword(),
|
|
new TenantMembership
|
|
{
|
|
TenantId = tenantId,
|
|
UserId = userId,
|
|
Role = TenantRole.Student,
|
|
Status = MembershipStatus.Active
|
|
},
|
|
new TenantBackendRole
|
|
{
|
|
Id = roleId,
|
|
TenantId = tenantId,
|
|
Code = "dashboard_operator",
|
|
Name = "Dashboard Operator",
|
|
Status = BackendRoleStatus.Active,
|
|
DataScope = JsonSerializer.SerializeToElement(new { mode = "self" })
|
|
},
|
|
new TenantBackendRolePermission
|
|
{
|
|
TenantId = tenantId,
|
|
RoleId = roleId,
|
|
PermissionCode = BackendPermissions.TenantDashboardView
|
|
},
|
|
new TenantBackendUserRole
|
|
{
|
|
TenantId = tenantId,
|
|
UserId = userId,
|
|
RoleId = roleId
|
|
});
|
|
|
|
using var client = factory.CreateClient();
|
|
client.UseAccessToken(await client.LoginAsTenantAsync(tenantId, phone));
|
|
|
|
commandRecorder.Reset();
|
|
using var response = await client.GetAsync("/api/backoffice/tenant/ui-bootstrap");
|
|
using var bootstrap = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
|
var firstRequestCommands = commandRecorder.Snapshot();
|
|
commandRecorder.Reset();
|
|
using var repeatedResponse = await client.GetAsync("/api/backoffice/tenant/ui-bootstrap");
|
|
var repeatedRequestCommands = commandRecorder.Snapshot();
|
|
using var roleManagementResponse = await client.GetAsync("/api/backoffice/tenant/bootstrap");
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal(HttpStatusCode.OK, repeatedResponse.StatusCode);
|
|
Assert.Equal(
|
|
[BackendPermissions.TenantDashboardView],
|
|
bootstrap.RootElement.GetProperty("permissionCodes").EnumerateArray().Select(item => item.GetString()));
|
|
Assert.Equal(
|
|
["tenant.dashboard"],
|
|
bootstrap.RootElement.GetProperty("menus").EnumerateArray().Select(item => item.GetProperty("code").GetString()));
|
|
Assert.True(
|
|
firstRequestCommands.Count is >= 1 and <= 10,
|
|
$"Expected at most 10 SQL commands, but captured {firstRequestCommands.Count}:{Environment.NewLine}{string.Join($"{Environment.NewLine}---{Environment.NewLine}", firstRequestCommands)}");
|
|
Assert.InRange(repeatedRequestCommands.Count, 1, firstRequestCommands.Count);
|
|
AssertNoPerCodeCatalogExistenceQueries(firstRequestCommands);
|
|
AssertNoPerCodeCatalogExistenceQueries(repeatedRequestCommands);
|
|
Assert.Equal(HttpStatusCode.Forbidden, roleManagementResponse.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PlatformUiBootstrap_DoesNotProbeCatalogPerCode()
|
|
{
|
|
var commandRecorder = new RecordingDbCommandInterceptor();
|
|
await using var factory = new ApiTestFactory(
|
|
configurationOverrides: new Dictionary<string, string?>
|
|
{
|
|
["Tenancy:Resolution:PlatformHosts:0"] = "localhost"
|
|
},
|
|
dbCommandInterceptor: commandRecorder);
|
|
await factory.SeedBuiltinBackofficeCatalogAsync();
|
|
var userId = Guid.NewGuid();
|
|
var roleId = Guid.NewGuid();
|
|
var email = $"platform-{Guid.NewGuid():N}@example.test";
|
|
await factory.SeedAsync(
|
|
new User
|
|
{
|
|
Id = userId,
|
|
Email = email,
|
|
NormalizedEmail = email.ToUpperInvariant(),
|
|
UserName = email,
|
|
NormalizedUserName = email.ToUpperInvariant(),
|
|
Name = "Platform UI Operator",
|
|
PrimaryRole = "platform_admin",
|
|
RawProfile = JsonDefaults.Object()
|
|
}.WithTestPassword(),
|
|
new PlatformBackendRole
|
|
{
|
|
Id = roleId,
|
|
Code = $"platform_ui_{Guid.NewGuid():N}",
|
|
Name = "Platform UI Operator",
|
|
Status = BackendRoleStatus.Active
|
|
},
|
|
new PlatformBackendRolePermission
|
|
{
|
|
RoleId = roleId,
|
|
PermissionCode = BackendPermissions.PlatformDashboardView
|
|
},
|
|
new PlatformBackendUserRole
|
|
{
|
|
UserId = userId,
|
|
RoleId = roleId
|
|
});
|
|
|
|
using var client = factory.CreateClient();
|
|
client.UseAccessToken(await client.LoginAsPlatformAsync(email));
|
|
|
|
commandRecorder.Reset();
|
|
using var response = await client.GetAsync("/api/backoffice/platform/ui-bootstrap");
|
|
using var bootstrap = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
|
var commands = commandRecorder.Snapshot();
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal(
|
|
[BackendPermissions.PlatformDashboardView],
|
|
bootstrap.RootElement.GetProperty("permissionCodes").EnumerateArray().Select(item => item.GetString()));
|
|
Assert.Equal(
|
|
["platform.dashboard"],
|
|
bootstrap.RootElement.GetProperty("menus").EnumerateArray().Select(item => item.GetProperty("code").GetString()));
|
|
Assert.InRange(commands.Count, 1, 10);
|
|
AssertNoPerCodeCatalogExistenceQueries(commands);
|
|
}
|
|
|
|
private static void AssertNoPerCodeCatalogExistenceQueries(IEnumerable<string> commands)
|
|
{
|
|
string[] catalogTables = ["saas_features", "permission_modules", "backend_permissions", "backend_menus"];
|
|
Assert.DoesNotContain(commands, command =>
|
|
command.TrimStart().StartsWith("SELECT EXISTS", StringComparison.OrdinalIgnoreCase) &&
|
|
catalogTables.Any(table => command.Contains(table, StringComparison.OrdinalIgnoreCase)));
|
|
}
|
|
}
|