using System.Net; using System.Text.Json; using Tiku.Domain.Catalog; using Tiku.Domain.Common; using Tiku.Domain.Content; using Tiku.Domain.QuestionBanks; using Tiku.Domain.Tenancy; namespace Tiku.IntegrationTests.Api; public sealed class QuestionBankEndpointTests { [Fact] public async Task Question_banks_return_active_tenant_banks_only() { var tenantId = Guid.NewGuid(); var otherTenantId = Guid.NewGuid(); await using var factory = new ApiTestFactory(); await factory.SeedAsync( PlatformTenant(), Tenant(tenantId, "master"), Tenant(otherTenantId, "other"), new QuestionBank { Id = Guid.NewGuid(), TenantId = tenantId, Name = "A 题库", Status = QuestionBankStatus.Active }, new QuestionBank { Id = Guid.NewGuid(), TenantId = tenantId, Name = "归档题库", Status = QuestionBankStatus.Archived }, new QuestionBank { Id = Guid.NewGuid(), TenantId = otherTenantId, Name = "其他租户题库", Status = QuestionBankStatus.Active }); using var client = factory.CreateClient(); using var response = await client.GetAsync("/api/public/catalog/question-banks?tenantCode=master"); var items = await ReadItemsAsync(response); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("A 题库", Assert.Single(items).GetProperty("name").GetString()); } [Fact] public async Task Questions_support_filters_and_current_version_projection() { var tenantId = Guid.NewGuid(); var bankId = Guid.NewGuid(); var subjectId = Guid.NewGuid(); var categoryId = Guid.NewGuid(); var collectionId = Guid.NewGuid(); var includedQuestionId = Guid.NewGuid(); var excludedQuestionId = Guid.NewGuid(); var versionId = Guid.NewGuid(); await using var factory = new ApiTestFactory(); await factory.SeedAsync( PlatformTenant(), Tenant(tenantId, "master"), new Subject { Id = subjectId, TenantId = tenantId, Name = "测试科目" }, new Category { Id = categoryId, TenantId = tenantId, SubjectId = subjectId, Name = "测试分类" }, new QuestionBank { Id = bankId, TenantId = tenantId, Name = "题库" }, new QuestionCollection { Id = collectionId, TenantId = tenantId, Name = "题集", Status = ContentStatus.Active }, new Question { Id = excludedQuestionId, TenantId = tenantId, QuestionBankId = bankId, SubjectId = subjectId, Type = "choice", Status = QuestionStatus.Archived }); await factory.SeedQuestionWithVersionAsync( new Question { Id = includedQuestionId, TenantId = tenantId, QuestionBankId = bankId, SubjectId = subjectId, CategoryId = categoryId, Type = "choice", TypeLabel = "单选题", Difficulty = 2, Status = QuestionStatus.Published }, new QuestionVersion { Id = versionId, TenantId = tenantId, QuestionId = includedQuestionId, VersionNo = 2, Content = "题干关键词" }); var questionReference = await factory.SeedQuestionReferenceAsync( tenantId, tenantId, includedQuestionId); await factory.SeedAsync(new QuestionCollectionItem { Id = Guid.NewGuid(), TenantId = tenantId, CollectionId = collectionId, QuestionReferenceId = questionReference.Id, QuestionOwnerTenantId = tenantId, QuestionId = includedQuestionId }); using var client = factory.CreateClient(); using var response = await client.GetAsync( $"/api/public/catalog/questions?tenantCode=master&questionBankId={bankId}&subjectId={subjectId}&categoryId={categoryId}&collectionId={collectionId}&type=choice&keyword=关键词"); var items = await ReadItemsAsync(response); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var item = Assert.Single(items); Assert.Equal(includedQuestionId, item.GetProperty("id").GetGuid()); Assert.Equal(versionId, item.GetProperty("versionId").GetGuid()); Assert.Equal("题干关键词", item.GetProperty("content").GetString()); } [Fact] public async Task Question_detail_returns_not_found_for_archived_question() { var tenantId = Guid.NewGuid(); var questionId = Guid.NewGuid(); await using var factory = new ApiTestFactory(); await factory.SeedAsync( PlatformTenant(), Tenant(tenantId, "master"), new Question { Id = questionId, TenantId = tenantId, Type = "choice", Status = QuestionStatus.Archived }); using var client = factory.CreateClient(); using var response = await client.GetAsync($"/api/public/catalog/questions/{questionId}?tenantCode=master"); var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal("question_not_found", body.RootElement.GetProperty("code").GetString()); } [Fact] public async Task Question_versions_are_returned_newest_first() { var tenantId = Guid.NewGuid(); var questionId = Guid.NewGuid(); await using var factory = new ApiTestFactory(); await factory.SeedAsync( PlatformTenant(), Tenant(tenantId, "master"), new Question { Id = questionId, TenantId = tenantId, Type = "choice", Status = QuestionStatus.Published }, new QuestionVersion { Id = Guid.NewGuid(), TenantId = tenantId, QuestionId = questionId, VersionNo = 1, Content = "旧版本" }, new QuestionVersion { Id = Guid.NewGuid(), TenantId = tenantId, QuestionId = questionId, VersionNo = 2, Content = "新版本" }); using var client = factory.CreateClient(); using var response = await client.GetAsync($"/api/public/catalog/questions/{questionId}/versions?tenantCode=master"); var items = await ReadItemsAsync(response); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(["新版本", "旧版本"], items.Select(item => item.GetProperty("content").GetString()!).ToArray()); } 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 Tenant PlatformTenant() { return new Tenant { Id = Guid.NewGuid(), Slug = $"platform-{Guid.NewGuid():N}", Name = "Platform Question Bank", Status = TenantStatus.Active, Mode = TenantMode.PlatformOwned, Metadata = JsonDefaults.Object() }; } private static async Task ReadItemsAsync(HttpResponseMessage response) { var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); return body.RootElement .GetProperty("items") .EnumerateArray() .Select(item => item.Clone()) .ToArray(); } }