forked from gongxuegit/tiku-backend.net
feat: enforce tenant isolation and shared question bank
This commit is contained in:
114
Tiku.IntegrationTests/TenantDomainLifecycleTests.cs
Normal file
114
Tiku.IntegrationTests/TenantDomainLifecycleTests.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Domain.Tenancy;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
using Tiku.IntegrationTests.Api;
|
||||
|
||||
namespace Tiku.IntegrationTests;
|
||||
|
||||
public sealed class TenantDomainLifecycleTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Domain_becomes_active_only_after_dns_and_tls_are_ready()
|
||||
{
|
||||
await using var factory = new ApiTestFactory(
|
||||
domainOwnershipVerifier: new FakeOwnershipVerifier(true),
|
||||
domainGatewayProvisioner: new FakeGatewayProvisioner(true));
|
||||
var tenantId = Guid.NewGuid();
|
||||
var domainId = Guid.NewGuid();
|
||||
await factory.SeedAsync(
|
||||
new Tenant
|
||||
{
|
||||
Id = tenantId,
|
||||
Slug = "tenant-a",
|
||||
Name = "Tenant A",
|
||||
Status = TenantStatus.Active,
|
||||
Mode = TenantMode.Saas
|
||||
},
|
||||
new TenantDomain
|
||||
{
|
||||
Id = domainId,
|
||||
TenantId = tenantId,
|
||||
Host = "learn.tenant-a.example",
|
||||
DomainType = TenantDomainType.Custom,
|
||||
Status = TenantDomainStatus.Pending,
|
||||
VerificationToken = "verification-token"
|
||||
});
|
||||
|
||||
using (var scope = factory.CreateSystemScope("Process domain lifecycle"))
|
||||
{
|
||||
var processed = await scope.ServiceProvider
|
||||
.GetRequiredService<ITenantDomainLifecycleService>()
|
||||
.ProcessPendingAsync();
|
||||
Assert.Equal(1, processed);
|
||||
}
|
||||
|
||||
using var verificationScope = factory.CreateSystemScope();
|
||||
var domain = await verificationScope.ServiceProvider.GetRequiredService<TikuDbContext>()
|
||||
.TenantDomains.SingleAsync(item => item.Id == domainId);
|
||||
Assert.Equal(TenantDomainStatus.Active, domain.Status);
|
||||
Assert.NotNull(domain.DnsVerifiedAt);
|
||||
Assert.NotNull(domain.TlsReadyAt);
|
||||
Assert.Null(domain.LastFailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Missing_gateway_configuration_keeps_domain_pending()
|
||||
{
|
||||
await using var factory = new ApiTestFactory(
|
||||
domainOwnershipVerifier: new FakeOwnershipVerifier(true),
|
||||
domainGatewayProvisioner: new FakeGatewayProvisioner(false, configured: false));
|
||||
var tenantId = Guid.NewGuid();
|
||||
var domainId = Guid.NewGuid();
|
||||
await factory.SeedAsync(
|
||||
new Tenant
|
||||
{
|
||||
Id = tenantId,
|
||||
Slug = "tenant-a",
|
||||
Name = "Tenant A",
|
||||
Status = TenantStatus.Active,
|
||||
Mode = TenantMode.Saas
|
||||
},
|
||||
new TenantDomain
|
||||
{
|
||||
Id = domainId,
|
||||
TenantId = tenantId,
|
||||
Host = "pending.tenant-a.example",
|
||||
Status = TenantDomainStatus.Pending,
|
||||
VerificationToken = "verification-token"
|
||||
});
|
||||
|
||||
using (var scope = factory.CreateSystemScope("Process pending domain"))
|
||||
{
|
||||
await scope.ServiceProvider.GetRequiredService<ITenantDomainLifecycleService>().ProcessPendingAsync();
|
||||
}
|
||||
|
||||
using var verificationScope = factory.CreateSystemScope();
|
||||
var domain = await verificationScope.ServiceProvider.GetRequiredService<TikuDbContext>()
|
||||
.TenantDomains.SingleAsync(item => item.Id == domainId);
|
||||
Assert.Equal(TenantDomainStatus.Pending, domain.Status);
|
||||
Assert.NotNull(domain.DnsVerifiedAt);
|
||||
Assert.Contains("not configured", domain.LastFailureReason, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private sealed class FakeOwnershipVerifier(bool verified) : IDomainOwnershipVerifier
|
||||
{
|
||||
public Task<DomainOwnershipResult> VerifyAsync(
|
||||
string host,
|
||||
string verificationToken,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(new DomainOwnershipResult(verified, true, verified ? null : "DNS failed"));
|
||||
}
|
||||
|
||||
private sealed class FakeGatewayProvisioner(bool ready, bool configured = true) : IDomainGatewayProvisioner
|
||||
{
|
||||
public Task<DomainGatewayResult> EnsureTlsAsync(
|
||||
string host,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(new DomainGatewayResult(
|
||||
ready,
|
||||
configured,
|
||||
ready ? null : configured ? "TLS pending" : "Gateway is not configured"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user