forked from xiongyuxing/tiku-backend.net
270 lines
9.8 KiB
C#
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®ionId={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();
|
|
}
|
|
}
|