feat: enforce tenant isolation and shared question bank

This commit is contained in:
2026-07-27 16:59:12 +08:00
parent 28e9a9fa41
commit db4c7b4496
137 changed files with 6402 additions and 112274 deletions

View File

@@ -1,6 +1,8 @@
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Tenancy;
using Tiku.Domain.Common;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;
@@ -9,6 +11,59 @@ namespace Tiku.IntegrationTests.Api;
public sealed class TenantPublicEndpointTests
{
[Fact]
public async Task Runtime_bootstrap_uses_published_config_etag_and_invalidates_after_publish()
{
await using var factory = new ApiTestFactory();
var tenantId = Guid.NewGuid();
await SeedTenantAsync(factory, tenantId);
await factory.SeedAsync(new TenantFrontendConfig
{
TenantId = tenantId,
ConfigVersion = 1,
PublishedBranding = JsonSerializer.SerializeToElement(new { name = "published" }),
DraftBranding = JsonSerializer.SerializeToElement(new { name = "published" })
});
using var client = factory.CreateClient();
using var firstRequest = new HttpRequestMessage(HttpMethod.Get, "/api/runtime/bootstrap");
firstRequest.Headers.Host = "student.example.test";
var first = await client.SendAsync(firstRequest);
var firstBody = await first.Content.ReadFromJsonAsync<JsonElement>();
var firstEtag = first.Headers.ETag?.Tag;
using var notModifiedRequest = new HttpRequestMessage(HttpMethod.Get, "/api/runtime/bootstrap");
notModifiedRequest.Headers.Host = "student.example.test";
notModifiedRequest.Headers.TryAddWithoutValidation("If-None-Match", firstEtag);
var notModified = await client.SendAsync(notModifiedRequest);
using (var scope = factory.CreateTenantScope(tenantId, "master"))
{
var service = scope.ServiceProvider.GetRequiredService<ITenantFrontendConfigService>();
await service.SaveDraftAsync(
tenantId,
new TenantFrontendConfigDraft(
JsonSerializer.SerializeToElement(new { name = "draft" }),
JsonDefaults.Object(),
JsonDefaults.Object(),
JsonDefaults.Array(),
JsonDefaults.Array()));
await service.PublishAsync(tenantId, 1);
}
using var publishedRequest = new HttpRequestMessage(HttpMethod.Get, "/api/runtime/bootstrap");
publishedRequest.Headers.Host = "student.example.test";
var published = await client.SendAsync(publishedRequest);
var publishedBody = await published.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
Assert.Equal("published", firstBody.GetProperty("branding").GetProperty("name").GetString());
Assert.Equal(HttpStatusCode.NotModified, notModified.StatusCode);
Assert.Equal(HttpStatusCode.OK, published.StatusCode);
Assert.NotEqual(firstEtag, published.Headers.ETag?.Tag);
Assert.Equal("draft", publishedBody.GetProperty("branding").GetProperty("name").GetString());
}
[Fact]
public async Task Resolve_returns_public_tenant_configuration_by_tenant_code()
{