forked from xiongyuxing/tiku-backend.net
505 lines
18 KiB
C#
505 lines
18 KiB
C#
using System.Net;
|
|
using System.Text.Json;
|
|
using Tiku.Domain.Catalog;
|
|
using Tiku.Domain.Commerce;
|
|
using Tiku.Domain.Common;
|
|
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/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/catalog/subjects?tenantCode=master®ionId={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/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/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/catalog/banners?tenantCode=master®ionId={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/catalog/faqs?tenantCode=master");
|
|
using var announcementResponse = await client.GetAsync("/api/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/catalog/exam-dates?tenantCode=master®ionId={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/catalog/products?tenantCode=master®ionId={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/catalog/svip-plans?tenantCode=master®ionId={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();
|
|
}
|
|
}
|