Files
tiku-backend.net/Tiku.IntegrationTests/Api/ContentNavigationEndpointTests.cs

270 lines
9.8 KiB
C#

using System.Net;
using System.Text.Json;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
namespace Tiku.IntegrationTests.Api;
public sealed class ContentNavigationEndpointTests
{
[Fact]
public async Task Content_entries_hide_hidden_items_by_default()
{
var tenantId = Guid.NewGuid();
var regionId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new ContentEntry
{
Id = Guid.NewGuid(),
TenantId = tenantId,
RegionId = regionId,
EntryKey = "practice",
Name = "公开练习",
EntryType = ContentEntryType.QuestionPractice,
Visibility = ContentVisibility.Public,
SortOrder = 2
},
new ContentEntry
{
Id = Guid.NewGuid(),
TenantId = tenantId,
EntryKey = "global",
Name = "全局入口",
EntryType = ContentEntryType.QuestionPractice,
Visibility = ContentVisibility.Public,
SortOrder = 1
},
new ContentEntry
{
Id = Guid.NewGuid(),
TenantId = tenantId,
EntryKey = "hidden",
Name = "隐藏入口",
EntryType = ContentEntryType.QuestionPractice,
Visibility = ContentVisibility.Hidden
});
using var client = factory.CreateClient();
using var response = await client.GetAsync($"/api/catalog/content-entries?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());
}
[Fact]
public async Task Content_nodes_support_required_entry_and_root_filter()
{
var tenantId = Guid.NewGuid();
var entryId = Guid.NewGuid();
var rootNodeId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new ContentEntry
{
Id = entryId,
TenantId = tenantId,
EntryKey = "practice",
Name = "练习入口"
},
new ContentNode
{
Id = rootNodeId,
TenantId = tenantId,
EntryId = entryId,
Name = "根内容节点",
NodeKey = "root",
SortOrder = 1,
IsLeaf = false
},
new ContentNode
{
Id = Guid.NewGuid(),
TenantId = tenantId,
EntryId = entryId,
ParentId = rootNodeId,
Name = "子内容节点",
NodeKey = "child",
SortOrder = 2,
IsLeaf = true
});
using var client = factory.CreateClient();
using var missingEntryResponse = await client.GetAsync("/api/catalog/content-nodes?tenantCode=master");
using var response = await client.GetAsync($"/api/catalog/content-nodes?tenantCode=master&entryId={entryId}&parentId=root");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.BadRequest, missingEntryResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var item = Assert.Single(items);
Assert.Equal("根内容节点", item.GetProperty("name").GetString());
}
[Fact]
public async Task Collections_and_blueprints_return_active_items_only()
{
var tenantId = Guid.NewGuid();
var entryId = Guid.NewGuid();
var nodeId = Guid.NewGuid();
var collectionId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new ContentEntry { Id = entryId, TenantId = tenantId, EntryKey = "practice", Name = "练习入口" },
new ContentNode { Id = nodeId, TenantId = tenantId, EntryId = entryId, Name = "章节" },
new QuestionCollection
{
Id = collectionId,
TenantId = tenantId,
EntryId = entryId,
NodeId = nodeId,
Name = "章节题集",
CollectionType = QuestionCollectionType.Chapter,
Status = ContentStatus.Active,
SortOrder = 1
},
new QuestionCollection
{
Id = Guid.NewGuid(),
TenantId = tenantId,
EntryId = entryId,
NodeId = nodeId,
Name = "草稿题集",
Status = ContentStatus.Draft
},
new PracticeBlueprint
{
Id = Guid.NewGuid(),
TenantId = tenantId,
EntryId = entryId,
NodeId = nodeId,
CollectionId = collectionId,
Name = "章节练习",
Mode = PracticeMode.Sequential,
Status = ContentStatus.Active
},
new PracticeBlueprint
{
Id = Guid.NewGuid(),
TenantId = tenantId,
EntryId = entryId,
NodeId = nodeId,
CollectionId = collectionId,
Name = "归档练习",
Mode = PracticeMode.Sequential,
Status = ContentStatus.Archived
});
using var client = factory.CreateClient();
using var collectionsResponse = await client.GetAsync(
$"/api/catalog/question-collections?tenantCode=master&entryId={entryId}&nodeId={nodeId}&collectionType=chapter");
using var blueprintsResponse = await client.GetAsync(
$"/api/catalog/practice-blueprints?tenantCode=master&entryId={entryId}&nodeId={nodeId}&collectionId={collectionId}&mode=sequential");
var collections = await ReadItemsAsync(collectionsResponse);
var blueprints = await ReadItemsAsync(blueprintsResponse);
Assert.Equal(HttpStatusCode.OK, collectionsResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, blueprintsResponse.StatusCode);
Assert.Equal("章节题集", Assert.Single(collections).GetProperty("name").GetString());
Assert.Equal("章节练习", Assert.Single(blueprints).GetProperty("name").GetString());
}
[Fact]
public async Task Collection_questions_return_published_questions_only()
{
var tenantId = Guid.NewGuid();
var collectionId = Guid.NewGuid();
var publishedQuestionId = Guid.NewGuid();
var archivedQuestionId = Guid.NewGuid();
var versionId = Guid.NewGuid();
await using var factory = new ApiTestFactory();
await factory.SeedAsync(
Tenant(tenantId, "master"),
new QuestionCollection
{
Id = collectionId,
TenantId = tenantId,
Name = "真题套卷",
Status = ContentStatus.Active
},
new Question
{
Id = publishedQuestionId,
TenantId = tenantId,
PrimaryCollectionId = collectionId,
Type = "choice",
TypeLabel = "单选题",
Status = QuestionStatus.Published,
CurrentVersionId = versionId
},
new QuestionVersion
{
Id = versionId,
TenantId = tenantId,
QuestionId = publishedQuestionId,
Content = "题干"
},
new Question
{
Id = archivedQuestionId,
TenantId = tenantId,
PrimaryCollectionId = collectionId,
Type = "choice",
Status = QuestionStatus.Archived
},
new QuestionCollectionItem
{
Id = Guid.NewGuid(),
TenantId = tenantId,
CollectionId = collectionId,
QuestionId = publishedQuestionId,
SortOrder = 1,
Score = 2
},
new QuestionCollectionItem
{
Id = Guid.NewGuid(),
TenantId = tenantId,
CollectionId = collectionId,
QuestionId = archivedQuestionId,
SortOrder = 2
});
using var client = factory.CreateClient();
using var response = await client.GetAsync($"/api/catalog/question-collections/questions?tenantCode=master&collectionId={collectionId}");
var items = await ReadItemsAsync(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var item = Assert.Single(items);
Assert.Equal(publishedQuestionId, item.GetProperty("id").GetGuid());
Assert.Equal("题干", item.GetProperty("content").GetString());
}
private static Tenant Tenant(Guid id, string slug)
{
return new Tenant
{
Id = id,
Slug = slug,
Name = slug,
Status = TenantStatus.Active,
Metadata = JsonDefaults.Object()
};
}
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();
}
}