feat(saas): implement marketplace and tenant onboarding

This commit is contained in:
2026-07-29 13:58:59 +08:00
parent 76606029e2
commit 6db200a2fc
145 changed files with 16862 additions and 94529 deletions

View File

@@ -158,6 +158,7 @@ public sealed class AuthServiceTests
services.AddScoped<ISmsVerificationService, RejectingSmsVerificationService>();
services.AddScoped<IWechatOAuthClient, RejectingWechatClient>();
services.AddScoped<ITenantExternalProviderConfigService, RejectingProviderConfigService>();
services.AddScoped<IFeatureAccessService, UnlimitedFeatureAccessService>();
services.AddScoped<IAuthService, AuthService>();
var provider = services.BuildServiceProvider();
@@ -167,6 +168,46 @@ public sealed class AuthServiceTests
return fixture;
}
private sealed class UnlimitedFeatureAccessService : IFeatureAccessService
{
public Task<FeatureAccessDecision> EvaluateAsync(
Guid tenantId,
string featureCode,
FeatureAccessOperation operation,
CancellationToken cancellationToken = default) =>
Task.FromResult(new FeatureAccessDecision(true, null, featureCode, operation));
public Task<IReadOnlySet<string>> GetEnabledFeaturesAsync(
Guid tenantId,
FeatureAccessOperation operation = FeatureAccessOperation.Read,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlySet<string>>(new HashSet<string>(SaasFeatureCatalog.All, StringComparer.Ordinal));
public Task<IReadOnlySet<string>> FilterPermissionCodesAsync(
Guid tenantId,
IEnumerable<string> permissionCodes,
FeatureAccessOperation operation = FeatureAccessOperation.Read,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlySet<string>>(permissionCodes.ToHashSet(StringComparer.Ordinal));
public Task<IReadOnlyCollection<FeatureQuotaSnapshot>> GetQuotaSummaryAsync(
Guid tenantId,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlyCollection<FeatureQuotaSnapshot>>([]);
public Task<bool> TryConsumeQuotaAsync(
Guid tenantId,
string metricCode,
long amount,
CancellationToken cancellationToken = default) => Task.FromResult(true);
public Task ReleaseQuotaAsync(
Guid tenantId,
string metricCode,
long amount,
CancellationToken cancellationToken = default) => Task.CompletedTask;
}
private async Task SeedAsync(bool includeBackendPermission)
{
var tenant = new Tenant { Id = Guid.NewGuid(), Slug = Guid.NewGuid().ToString("N"), Name = "Test" };
@@ -198,7 +239,7 @@ public sealed class AuthServiceTests
Code = BackendPermissions.TenantDashboardView,
Name = "Dashboard",
Area = BackendPermissionArea.Tenant,
Module = "dashboard"
PermissionModuleCode = "tenant_dashboard"
});
DbContext.TenantBackendUserRoles.Add(new TenantBackendUserRole
{