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

@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.QuestionBanks;
using Tiku.Application.Learning;
using Tiku.Application.Security;
using Tiku.Domain.Common;
using Tiku.Domain.Commerce;
using Tiku.Domain.Catalog;
@@ -9,6 +10,7 @@ using Tiku.Domain.Content;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Identity;
using Tiku.Domain.Learning;
using Tiku.Domain.Platform;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;
using Tiku.IntegrationTests.Api;
@@ -138,18 +140,22 @@ public sealed class PhaseThreeTenantIsolationTests
}
[Theory]
[InlineData(TenantSubscriptionStatus.Trial, true)]
[InlineData(TenantSubscriptionStatus.Active, true)]
[InlineData(TenantSubscriptionStatus.PastDue, false)]
[InlineData(TenantSubscriptionStatus.Cancelled, false)]
[InlineData(TenantSaasSubscriptionStatus.Trial, true)]
[InlineData(TenantSaasSubscriptionStatus.Active, true)]
[InlineData(TenantSaasSubscriptionStatus.PastDue, false)]
[InlineData(TenantSaasSubscriptionStatus.Cancelled, false)]
public async Task Public_question_reference_requires_current_subscription(
TenantSubscriptionStatus subscriptionStatus,
TenantSaasSubscriptionStatus subscriptionStatus,
bool allowed)
{
await using var factory = new ApiTestFactory();
var platformId = Guid.NewGuid();
var tenantId = Guid.NewGuid();
var questionId = Guid.NewGuid();
var offeringId = Guid.NewGuid();
var versionId = Guid.NewGuid();
var subscriptionId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
await factory.SeedAsync(
Tenant(platformId, "platform", TenantMode.PlatformOwned),
Tenant(tenantId, "tenant-a"),
@@ -160,13 +166,42 @@ public sealed class PhaseThreeTenantIsolationTests
Type = "choice",
Status = QuestionStatus.Published
},
new TenantSubscription
new SaasFeature
{
Code = SaasFeatureCatalog.Practice,
Name = "Practice",
Category = "learning",
Status = SaasFeatureStatus.Active
},
new SaasOffering { Id = offeringId, Code = "standard", Name = "Standard", Type = SaasOfferingType.BasePlan, Status = SaasOfferingStatus.Active },
new SaasOfferingVersion
{
Id = versionId,
OfferingId = offeringId,
Status = SaasOfferingVersionStatus.Published,
OriginalAmountCents = 100,
AmountCents = 100,
PublishedAt = now.AddDays(-1)
},
new SaasOfferingVersionFeature { OfferingVersionId = versionId, FeatureCode = SaasFeatureCatalog.Practice },
new TenantSaasSubscription
{
Id = subscriptionId,
TenantId = tenantId,
BaseOfferingVersionId = versionId,
Status = subscriptionStatus,
StartsAt = now.AddDays(-1),
CurrentPeriodStart = now.AddDays(-1),
CurrentPeriodEnd = now.AddDays(1)
},
new TenantSaasSubscriptionItem
{
TenantId = tenantId,
PlanCode = "standard",
Status = subscriptionStatus,
StartsAt = DateTimeOffset.UtcNow.AddDays(-1),
ExpiresAt = DateTimeOffset.UtcNow.AddDays(1)
SubscriptionId = subscriptionId,
OfferingVersionId = versionId,
Status = TenantSaasSubscriptionItemStatus.Active,
StartsAt = now.AddDays(-1),
EndsAt = now.AddDays(1)
});
using var scope = factory.CreateTenantScope(tenantId, "tenant-a");
@@ -210,14 +245,7 @@ public sealed class PhaseThreeTenantIsolationTests
await factory.SeedAsync(
Tenant(platformId, "platform", TenantMode.PlatformOwned),
Tenant(tenantId, "tenant-a"),
new User { Id = userId, Phone = "13800009999" },
new TenantSubscription
{
TenantId = tenantId,
PlanCode = "standard",
Status = TenantSubscriptionStatus.Active,
ExpiresAt = DateTimeOffset.UtcNow.AddDays(30)
});
new User { Id = userId, Phone = "13800009999" });
await factory.SeedQuestionWithVersionAsync(
new Question
{