forked from xiongyuxing/tiku-backend.net
221 lines
9.1 KiB
C#
221 lines
9.1 KiB
C#
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;
|
|
|
|
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()
|
|
{
|
|
await using var factory = new ApiTestFactory();
|
|
var tenantId = Guid.NewGuid();
|
|
await SeedTenantAsync(factory, tenantId);
|
|
using var client = factory.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/tenant/resolve?tenantCode=master");
|
|
var document = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal(tenantId.ToString(), document.RootElement.GetProperty("tenant").GetProperty("id").GetString());
|
|
Assert.Equal("升本刷题通", document.RootElement.GetProperty("branding").GetProperty("brandName").GetString());
|
|
Assert.True(document.RootElement.GetProperty("features").GetProperty("enableVocabulary").GetBoolean());
|
|
Assert.Equal("http://127.0.0.1:5173", document.RootElement.GetProperty("publicConfig").GetProperty("appUrl").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_returns_public_tenant_configuration_by_host()
|
|
{
|
|
await using var factory = new ApiTestFactory();
|
|
var tenantId = Guid.NewGuid();
|
|
await SeedTenantAsync(factory, tenantId);
|
|
using var client = factory.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/tenant/resolve?host=student.example.test");
|
|
var document = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal("student.example.test", document.RootElement.GetProperty("tenant").GetProperty("host").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_prefers_published_theme_config_over_branding_theme()
|
|
{
|
|
await using var factory = new ApiTestFactory();
|
|
var tenantId = Guid.NewGuid();
|
|
await SeedTenantAsync(factory, tenantId);
|
|
await factory.SeedAsync(new TenantThemeConfig
|
|
{
|
|
TenantId = tenantId,
|
|
Status = TenantThemeConfigStatus.Published,
|
|
ActiveTheme = JsonSerializer.SerializeToElement(new
|
|
{
|
|
color = "red"
|
|
}),
|
|
ActivePublicAssets = JsonSerializer.SerializeToElement(new
|
|
{
|
|
hero = "/hero.png"
|
|
})
|
|
});
|
|
using var client = factory.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/tenant/current-public?tenantCode=master");
|
|
var document = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal("red", document.RootElement.GetProperty("branding").GetProperty("theme").GetProperty("color").GetString());
|
|
Assert.Equal("/hero.png", document.RootElement.GetProperty("branding").GetProperty("publicAssets").GetProperty("hero").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_returns_not_found_for_unknown_tenant()
|
|
{
|
|
await using var factory = new ApiTestFactory();
|
|
using var client = factory.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/tenant/resolve?tenantCode=missing");
|
|
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Health_returns_ok()
|
|
{
|
|
await using var factory = new ApiTestFactory();
|
|
using var client = factory.CreateClient();
|
|
|
|
var response = await client.GetAsync("/api/health");
|
|
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal("ok", body.GetProperty("status").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Cors_preflight_allows_configured_development_origin()
|
|
{
|
|
await using var factory = new ApiTestFactory();
|
|
using var client = factory.CreateClient();
|
|
using var request = new HttpRequestMessage(HttpMethod.Options, "/api/health");
|
|
request.Headers.Add("Origin", "http://localhost:5173");
|
|
request.Headers.Add("Access-Control-Request-Method", "GET");
|
|
|
|
var response = await client.SendAsync(request);
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
|
Assert.Equal("http://localhost:5173", response.Headers.GetValues("Access-Control-Allow-Origin").Single());
|
|
}
|
|
|
|
private static Task SeedTenantAsync(ApiTestFactory factory, Guid tenantId)
|
|
{
|
|
return factory.SeedAsync(
|
|
new Tenant
|
|
{
|
|
Id = tenantId,
|
|
Slug = "master",
|
|
Name = "升本刷题通",
|
|
Status = TenantStatus.Active,
|
|
Mode = TenantMode.PlatformOwned
|
|
},
|
|
new TenantDomain
|
|
{
|
|
TenantId = tenantId,
|
|
Host = "student.example.test",
|
|
DomainType = TenantDomainType.Custom,
|
|
Status = TenantDomainStatus.Active,
|
|
IsPrimary = true
|
|
},
|
|
new TenantBranding
|
|
{
|
|
TenantId = tenantId,
|
|
BrandName = "升本刷题通",
|
|
ShortName = "刷题通",
|
|
Slogan = "多租户专升本题库 SaaS",
|
|
LogoUrl = "https://example.test/logo.png",
|
|
FaviconUrl = "https://example.test/favicon.ico",
|
|
ServiceWechat = "service-wechat",
|
|
ServiceAccountName = "服务号",
|
|
Theme = JsonSerializer.SerializeToElement(new
|
|
{
|
|
color = "blue"
|
|
}),
|
|
PublicAssets = JsonDefaults.Object()
|
|
},
|
|
new TenantSettings
|
|
{
|
|
TenantId = tenantId,
|
|
FeatureFlags = JsonSerializer.SerializeToElement(new
|
|
{
|
|
enableVocabulary = true
|
|
}),
|
|
AdminFeatureFlags = JsonSerializer.SerializeToElement(new
|
|
{
|
|
enableTenantManagement = true
|
|
}),
|
|
PublicConfig = JsonSerializer.SerializeToElement(new
|
|
{
|
|
appUrl = "http://127.0.0.1:5173"
|
|
})
|
|
});
|
|
}
|
|
}
|