using System.Net; using System.Net.Http.Json; using System.Text.Json; using Tiku.Domain.Common; using Tiku.Domain.Operations; using Tiku.Domain.Tenancy; namespace Tiku.IntegrationTests.Api; public sealed class TenantPublicEndpointTests { [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(); 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" }) }); } }