Files
tiku-backend.net/Tiku.IntegrationTests/Api/CatalogEndpointTests.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

505 lines
18 KiB
C#

using System.Net;
using System.Text.Json;
using Tiku.Domain.Catalog;
using Tiku.Domain.Commerce;
using Tiku.Domain.Learning;
using Tiku.Domain.Operations;
using Tiku.Domain.Tenancy;
namespace Tiku.IntegrationTests.Api;
public sealed class CatalogEndpointTests
{
[Fact]
public async Task Regions_are_filtered_by_active_tenant()
{
var tenantId = Guid.NewGuid();
var otherTenantId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
Tenant(otherTenantId, "other"),
new Region
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Name = "浙江",
SortOrder = 2,
IsActive = true
},
new Region
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Name = "北京",
SortOrder = 1,
IsActive = true
},
new Region
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Name = "停用地区",
SortOrder = 0,
IsActive = false
},
new Region
{
Id = Guid.NewGuid(),
TenantId = otherTenantId,
Name = "其他租户地区",
IsActive = true
});
using var client = factory.CreateClient();
using var response = await client.GetAsync("/api/public/catalog/regions?tenantCode=master");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(["北京", "浙江"], items.Select(item => item.GetProperty("name").GetString()!).ToArray());
}
[Fact]
public async Task Subjects_support_common_filters()
{
var tenantId = Guid.NewGuid();
var regionId = Guid.NewGuid();
var schoolId = Guid.NewGuid();
var majorId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new Region { Id = regionId, TenantId = tenantId, Name = "浙江" },
new School { Id = schoolId, TenantId = tenantId, RegionId = regionId, Name = "测试院校" },
new Major { Id = majorId, TenantId = tenantId, RegionId = regionId, SchoolId = schoolId, Name = "视觉传达" },
new Subject
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
SchoolId = schoolId,
MajorId = majorId,
Name = "专业理论",
Type = SubjectType.Professional,
SortOrder = 1,
IsActive = true
},
new Subject
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
SchoolId = schoolId,
MajorId = majorId,
Name = "公共英语",
Type = SubjectType.Cultural,
SortOrder = 2,
IsActive = true
},
new Subject
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Name = "停用科目",
Type = SubjectType.Professional,
IsActive = false
});
using var client = factory.CreateClient();
using var response = await client.GetAsync(
$"/api/public/catalog/subjects?tenantCode=master&regionId={regionId}&schoolId={schoolId}&majorId={majorId}&type=professional&keyword=理论");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var item = Assert.Single(items);
Assert.Equal("专业理论", item.GetProperty("name").GetString());
}
[Fact]
public async Task Module_nodes_support_root_filter()
{
var tenantId = Guid.NewGuid();
var parentId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new ModuleNode
{
Id = parentId,
TenantId = tenantId,
Name = "根节点",
Type = ModuleNodeType.Category,
IsActive = true
},
new ModuleNode
{
Id = Guid.NewGuid(),
TenantId = tenantId,
ParentId = parentId,
Name = "子节点",
Type = ModuleNodeType.Chapter,
IsActive = true
});
using var client = factory.CreateClient();
using var response = await client.GetAsync("/api/public/catalog/module-nodes?tenantCode=master&parentId=root");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var item = Assert.Single(items);
Assert.Equal("根节点", item.GetProperty("name").GetString());
}
[Fact]
public async Task Catalog_requires_known_tenant_for_anonymous_requests()
{
await using var factory = new ApiTestFactory();
using var client = factory.CreateClient();
using var response = await client.GetAsync("/api/public/catalog/regions");
var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
Assert.Equal("tenant_not_found", body.RootElement.GetProperty("code").GetString());
}
[Fact]
public async Task Banners_are_filtered_by_tenant_region_and_active_status()
{
var tenantId = Guid.NewGuid();
var otherTenantId = Guid.NewGuid();
var regionId = Guid.NewGuid();
var otherRegionId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
Tenant(otherTenantId, "other"),
new Region { Id = regionId, TenantId = tenantId, Name = "浙江" },
new Region { Id = otherRegionId, TenantId = tenantId, Name = "江苏" },
new Banner
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "第二张",
SortOrder = 2,
IsActive = true
},
new Banner
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "第一张",
SortOrder = 1,
IsActive = true
},
new Banner
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "停用横幅",
IsActive = false
},
new Banner
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = otherRegionId,
Title = "其他地区横幅",
IsActive = true
},
new Banner
{
Id = Guid.NewGuid(),
TenantId = otherTenantId,
Title = "其他租户横幅",
IsActive = true
});
using var client = factory.CreateClient();
using var response =
await client.GetAsync($"/api/public/catalog/banners?tenantCode=master&regionId={regionId}");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(["第一张", "第二张"], items.Select(item => item.GetProperty("title").GetString()!).ToArray());
}
[Fact]
public async Task Faqs_and_announcements_only_return_active_current_tenant_items()
{
var tenantId = Guid.NewGuid();
var otherTenantId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
Tenant(otherTenantId, "other"),
new Faq
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Question = "如何购买?",
Answer = "在线购买",
IsActive = true
},
new Faq
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Question = "停用 FAQ",
IsActive = false
},
new Faq
{
Id = Guid.NewGuid(),
TenantId = otherTenantId,
Question = "其他租户 FAQ",
IsActive = true
},
new Announcement
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Content = "暑期活动",
IsActive = true
},
new Announcement
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Content = "停用公告",
IsActive = false
},
new Announcement
{
Id = Guid.NewGuid(),
TenantId = otherTenantId,
Content = "其他租户公告",
IsActive = true
});
using var client = factory.CreateClient();
using var faqResponse = await client.GetAsync("/api/public/catalog/faqs?tenantCode=master");
using var announcementResponse = await client.GetAsync("/api/public/catalog/announcements?tenantCode=master");
var faqs = await ReadItemsAsync(faqResponse);
var announcements = await ReadItemsAsync(announcementResponse);
Assert.Equal(HttpStatusCode.OK, faqResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, announcementResponse.StatusCode);
Assert.Equal("如何购买?", Assert.Single(faqs).GetProperty("question").GetString());
Assert.Equal("暑期活动", Assert.Single(announcements).GetProperty("content").GetString());
}
[Fact]
public async Task Exam_dates_support_region_school_filters_and_days_left()
{
var tenantId = Guid.NewGuid();
var regionId = Guid.NewGuid();
var schoolId = Guid.NewGuid();
var otherRegionId = Guid.NewGuid();
var otherSchoolId = Guid.NewGuid();
var futureExamAt = new DateTimeOffset(DateTimeOffset.UtcNow.Date.AddDays(10), TimeSpan.Zero);
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new Region { Id = regionId, TenantId = tenantId, Name = "浙江" },
new Region { Id = otherRegionId, TenantId = tenantId, Name = "其他地区" },
new School { Id = schoolId, TenantId = tenantId, RegionId = regionId, Name = "测试院校" },
new School { Id = otherSchoolId, TenantId = tenantId, RegionId = otherRegionId, Name = "其他院校" },
new ExamDate
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
SchoolId = schoolId,
ExamName = "校考",
ExamAt = futureExamAt,
IsActive = true
},
new ExamDate
{
Id = Guid.NewGuid(),
TenantId = tenantId,
ExamName = "通用考试",
ExamAt = futureExamAt.AddDays(1),
IsActive = true
},
new ExamDate
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = otherRegionId,
SchoolId = otherSchoolId,
ExamName = "其他地区学校考试",
ExamAt = futureExamAt.AddDays(2),
IsActive = true
},
new ExamDate
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
SchoolId = schoolId,
ExamName = "停用考试",
IsActive = false
});
using var client = factory.CreateClient();
using var response = await client.GetAsync(
$"/api/public/catalog/exam-dates?tenantCode=master&regionId={regionId}&schoolId={schoolId}");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(["校考", "通用考试"], items.Select(item => item.GetProperty("examName").GetString()!).ToArray());
Assert.Equal(10, items[0].GetProperty("daysLeft").GetInt32());
}
[Fact]
public async Task Products_filter_active_region_type_keyword_and_limit()
{
var tenantId = Guid.NewGuid();
var regionId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new Region { Id = regionId, TenantId = tenantId, Name = "浙江" },
new Product
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "联考资料 B",
Type = ProductType.Material,
SortOrder = 2,
LegacyPriceText = "¥20",
IsActive = true
},
new Product
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "联考资料 A",
Type = ProductType.Material,
SortOrder = 1,
LegacyPriceText = "¥10",
IsActive = true
},
new Product
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "联考课程",
Type = ProductType.Course,
IsActive = true
},
new Product
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Title = "停用资料",
Type = ProductType.Material,
IsActive = false
});
using var client = factory.CreateClient();
using var response = await client.GetAsync(
$"/api/public/catalog/products?tenantCode=master&regionId={regionId}&type=material&keyword=联考&limit=1");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var item = Assert.Single(items);
Assert.Equal("联考资料 A", item.GetProperty("title").GetString());
Assert.Equal("¥10", item.GetProperty("priceLabel").GetString());
Assert.True(item.GetProperty("isActive").GetBoolean());
}
[Fact]
public async Task Svip_plans_filter_active_region_and_include_global_plans()
{
var tenantId = Guid.NewGuid();
var regionId = Guid.NewGuid();
var otherRegionId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new Region { Id = regionId, TenantId = tenantId, Name = "浙江" },
new Region { Id = otherRegionId, TenantId = tenantId, Name = "其他地区" },
new SvipPlan
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Name = "地区月卡",
PriceCents = 3000,
Days = 30,
SortOrder = 2,
IsActive = true
},
new SvipPlan
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Name = "通用周卡",
PriceCents = 900,
Days = 7,
SortOrder = 1,
IsActive = true
},
new SvipPlan
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = otherRegionId,
Name = "其他地区卡",
PriceCents = 1000,
Days = 7,
IsActive = true
},
new SvipPlan
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
Name = "停用卡",
PriceCents = 1,
Days = 1,
IsActive = false
});
using var client = factory.CreateClient();
using var response =
await client.GetAsync($"/api/public/catalog/svip-plans?tenantCode=master&regionId={regionId}");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(["通用周卡", "地区月卡"], items.Select(item => item.GetProperty("name").GetString()!).ToArray());
Assert.Equal(9m, items[0].GetProperty("price").GetDecimal());
}
private static Tenant Tenant(Guid id, string slug)
{
return new Tenant
{
Id = id,
Slug = slug,
Name = slug,
Status = TenantStatus.Active
};
}
private static async Task<JsonElement[]> ReadItemsAsync(HttpResponseMessage response)
{
var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
return body.RootElement
.GetProperty("items")
.EnumerateArray()
.Select(item => item.Clone())
.ToArray();
}
}