forked from gongxuegit/tiku-backend.net
feat: enforce tenant isolation and shared question bank
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Npgsql;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Auth;
|
||||
using Tiku.Application.Growth;
|
||||
using Tiku.Application.Storage;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
using Tiku.Domain.Content;
|
||||
using Tiku.Domain.Tenancy;
|
||||
using Tiku.IntegrationTests.Infrastructure;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
@@ -18,7 +22,9 @@ public sealed class ApiTestFactory(
|
||||
IWechatOAuthClient? wechatOAuthClient = null,
|
||||
IObjectStorageService? objectStorageService = null,
|
||||
IReferralQrcodeGenerator? referralQrcodeGenerator = null,
|
||||
IPaymentProviderGateway? paymentProviderGateway = null) : WebApplicationFactory<Program>
|
||||
IPaymentProviderGateway? paymentProviderGateway = null,
|
||||
IDomainOwnershipVerifier? domainOwnershipVerifier = null,
|
||||
IDomainGatewayProvisioner? domainGatewayProvisioner = null) : WebApplicationFactory<Program>
|
||||
{
|
||||
private readonly PostgresTestDatabase database = PostgresTestDatabase.Create();
|
||||
|
||||
@@ -29,6 +35,7 @@ public sealed class ApiTestFactory(
|
||||
foreach (var descriptor in services
|
||||
.Where(descriptor =>
|
||||
descriptor.ServiceType == typeof(NpgsqlDataSource) ||
|
||||
descriptor.ServiceType == typeof(TenantIsolationSaveChangesInterceptor) ||
|
||||
descriptor.ServiceType == typeof(DbContextOptions<TikuDbContext>) ||
|
||||
descriptor.ServiceType.FullName?.Contains(nameof(TikuDbContext), StringComparison.Ordinal) == true)
|
||||
.ToArray())
|
||||
@@ -37,11 +44,13 @@ public sealed class ApiTestFactory(
|
||||
}
|
||||
|
||||
services.AddSingleton(_ => NpgsqlDataSource.Create(database.ConnectionString));
|
||||
services.AddDbContextPool<TikuDbContext>((serviceProvider, options) =>
|
||||
services.AddScoped<TenantIsolationSaveChangesInterceptor>();
|
||||
services.AddDbContext<TikuDbContext>((serviceProvider, options) =>
|
||||
{
|
||||
var dataSource = serviceProvider.GetRequiredService<NpgsqlDataSource>();
|
||||
options.UseNpgsql(dataSource, npgsql =>
|
||||
npgsql.MigrationsAssembly(typeof(TikuDbContext).Assembly.FullName));
|
||||
options.AddInterceptors(serviceProvider.GetRequiredService<TenantIsolationSaveChangesInterceptor>());
|
||||
});
|
||||
|
||||
if (wechatOAuthClient is not null)
|
||||
@@ -63,17 +72,47 @@ public sealed class ApiTestFactory(
|
||||
{
|
||||
services.AddSingleton(paymentProviderGateway);
|
||||
}
|
||||
|
||||
if (domainOwnershipVerifier is not null)
|
||||
{
|
||||
services.RemoveAll<IDomainOwnershipVerifier>();
|
||||
services.AddSingleton(domainOwnershipVerifier);
|
||||
}
|
||||
|
||||
if (domainGatewayProvisioner is not null)
|
||||
{
|
||||
services.RemoveAll<IDomainGatewayProvisioner>();
|
||||
services.AddSingleton(domainGatewayProvisioner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SeedAsync(params object[] entities)
|
||||
{
|
||||
using var scope = Services.CreateScope();
|
||||
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
|
||||
.InitializeSystem(null, "Integration test fixture seeding");
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
||||
dbContext.AddRange(entities);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public IServiceScope CreateSystemScope(string reason = "Integration test verification")
|
||||
{
|
||||
var scope = Services.CreateScope();
|
||||
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
|
||||
.InitializeSystem(null, reason);
|
||||
return scope;
|
||||
}
|
||||
|
||||
public IServiceScope CreateTenantScope(Guid tenantId, string? tenantCode = null)
|
||||
{
|
||||
var scope = Services.CreateScope();
|
||||
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
|
||||
.Initialize(tenantId, tenantCode, TenantResolutionSource.TenantCode);
|
||||
return scope;
|
||||
}
|
||||
|
||||
public async Task SeedQuestionWithVersionAsync(Question question, QuestionVersion version)
|
||||
{
|
||||
question.CurrentVersionId = null;
|
||||
@@ -81,6 +120,8 @@ public sealed class ApiTestFactory(
|
||||
await SeedAsync(version);
|
||||
|
||||
using var scope = Services.CreateScope();
|
||||
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
|
||||
.InitializeSystem(question.TenantId, "Integration test question version linking");
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
||||
var persistedQuestion = await dbContext.Questions.SingleAsync(item =>
|
||||
item.TenantId == question.TenantId && item.Id == question.Id);
|
||||
@@ -88,6 +129,24 @@ public sealed class ApiTestFactory(
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<TenantQuestionReference> SeedQuestionReferenceAsync(
|
||||
Guid tenantId,
|
||||
Guid questionOwnerTenantId,
|
||||
Guid questionId,
|
||||
QuestionSource source = QuestionSource.Tenant)
|
||||
{
|
||||
var reference = new TenantQuestionReference
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = tenantId,
|
||||
QuestionOwnerTenantId = questionOwnerTenantId,
|
||||
QuestionId = questionId,
|
||||
Source = source
|
||||
};
|
||||
await SeedAsync(reference);
|
||||
return reference;
|
||||
}
|
||||
|
||||
public async Task<Guid> SeedActiveSessionAsync(
|
||||
Guid userId,
|
||||
Guid? tenantId = null,
|
||||
@@ -117,6 +176,8 @@ public sealed class ApiTestFactory(
|
||||
});
|
||||
|
||||
using var scope = Services.CreateScope();
|
||||
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
|
||||
.InitializeSystem(resolvedTenantId, "Integration test session lookup");
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
||||
return await dbContext.AuthSessions
|
||||
.Where(session => session.UserId == userId)
|
||||
|
||||
Reference in New Issue
Block a user