forked from wangziqi/gongxue-base
feat: add public question bank adoption
This commit is contained in:
@@ -15,6 +15,7 @@ const TENANT_SALES_USER_ID = '00000000-0000-0000-0000-000000000104';
|
||||
const TENANT_AGENT_USER_ID = '00000000-0000-0000-0000-000000000105';
|
||||
const TENANT_TEACHER_USER_ID = '00000000-0000-0000-0000-000000000106';
|
||||
const SECOND_STUDENT_USER_ID = '00000000-0000-0000-0000-000000000107';
|
||||
const PARTNER_TENANT_ADMIN_USER_ID = '00000000-0000-0000-0000-000000000907';
|
||||
const AUTH_USER_ID = '00000000-0000-0000-0000-00000000a101';
|
||||
const AUTH_TENANT_ADMIN_USER_ID = '00000000-0000-0000-0000-00000000a102';
|
||||
const AUTH_PLATFORM_ADMIN_USER_ID = '00000000-0000-0000-0000-00000000a999';
|
||||
@@ -48,6 +49,8 @@ const ids = {
|
||||
tenantClassOther: '00000000-0000-0000-0000-000000000852',
|
||||
examDate: '00000000-0000-0000-0000-000000000861',
|
||||
tenantExamDate: '00000000-0000-0000-0000-000000000862',
|
||||
questionBank: '00000000-0000-0000-0000-000000000400',
|
||||
publicQuestionBankGrant: '00000000-0000-0000-0000-000000000906',
|
||||
};
|
||||
|
||||
const paymentFixture = (() => {
|
||||
@@ -2404,6 +2407,115 @@ async function testTenantContentAssetsAndImports() {
|
||||
assert.equal(partnerImports.code, 'TENANT_CONTENT_EDITOR_REQUIRED', 'import jobs must be tenant isolated');
|
||||
}
|
||||
|
||||
async function testPublicQuestionBankAdoption() {
|
||||
const studentDenied = await request('/api/tenant-content/public-question-banks', {
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(studentDenied.code, 'TENANT_CONTENT_EDITOR_REQUIRED', 'student should not browse adoptable public banks');
|
||||
|
||||
const platformBanks = await request('/api/platform-admin/question-banks', {
|
||||
userId: false,
|
||||
headers: { 'x-platform-admin-key': 'local-platform-admin-key' },
|
||||
query: { q: '烟测公共题库' },
|
||||
});
|
||||
assert.ok(platformBanks.items?.some(item => item.id === ids.questionBank && item.sourceScope === 'platform'), 'platform admin should list platform public banks');
|
||||
|
||||
const grant = await request('/api/platform-admin/question-bank-grants', {
|
||||
userId: false,
|
||||
headers: { 'x-platform-admin-key': 'local-platform-admin-key' },
|
||||
method: 'PUT',
|
||||
body: {
|
||||
id: ids.publicQuestionBankGrant,
|
||||
sourceQuestionBankId: ids.questionBank,
|
||||
grantScope: 'plans',
|
||||
allowedPlanCodes: ['starter_yearly', 'pro_yearly'],
|
||||
status: 'active',
|
||||
metadata: { source: 'integration-test' },
|
||||
},
|
||||
});
|
||||
assert.equal(grant.item?.sourceQuestionBankId, ids.questionBank, 'platform admin should upsert public bank grant');
|
||||
|
||||
const grants = await request('/api/platform-admin/question-bank-grants', {
|
||||
userId: false,
|
||||
headers: { 'x-platform-admin-key': 'local-platform-admin-key' },
|
||||
query: { questionBankId: ids.questionBank },
|
||||
});
|
||||
assert.ok(grants.items?.some(item => item.id === ids.publicQuestionBankGrant), 'platform admin should list public bank grants');
|
||||
|
||||
const partnerBanks = await request('/api/tenant-content/public-question-banks', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
});
|
||||
const adoptable = partnerBanks.items?.find(item => item.grantId === ids.publicQuestionBankGrant);
|
||||
assert.ok(adoptable, 'partner tenant should see public bank granted by SaaS plan');
|
||||
assert.equal(adoptable.adoptedId, null, 'public bank should start as not adopted after smoke seed');
|
||||
|
||||
const adopted = await request('/api/tenant-content/public-question-banks/adopt', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
grantId: ids.publicQuestionBankGrant,
|
||||
entryName: '合作商采纳烟测公共题库',
|
||||
collectionName: '合作商公共题库题目',
|
||||
copyLimit: 3,
|
||||
metadata: { source: 'integration-test' },
|
||||
},
|
||||
});
|
||||
assert.equal(adopted.item?.sourceQuestionBankId, ids.questionBank, 'adoption should bind source public question bank');
|
||||
assert.equal(adopted.item?.copiedQuestionCount, 3, 'adoption should copy a snapshot of published questions');
|
||||
assert.ok(adopted.item?.targetEntryId, 'adoption should create tenant content entry');
|
||||
assert.ok(adopted.item?.targetCollectionId, 'adoption should create tenant question collection');
|
||||
|
||||
const repeated = await request('/api/tenant-content/public-question-banks/adopt', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: { grantId: ids.publicQuestionBankGrant },
|
||||
expectStatus: 409,
|
||||
});
|
||||
assert.equal(repeated.code, 'QUESTION_BANK_ALREADY_ADOPTED', 'tenant should not adopt the same public bank twice');
|
||||
|
||||
const adoptedList = await request('/api/tenant-content/public-question-banks', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
});
|
||||
const adoptedItem = adoptedList.items?.find(item => item.grantId === ids.publicQuestionBankGrant);
|
||||
assert.equal(adoptedItem?.adoptionStatus, 'active', 'adoptable list should expose adoption status');
|
||||
|
||||
const entries = await request('/api/catalog/content-entries', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: false,
|
||||
query: { entryType: 'question_practice' },
|
||||
});
|
||||
assert.ok(entries.items?.some(item => item.id === adopted.item.targetEntryId), 'adopted public bank should appear in tenant catalog entries');
|
||||
|
||||
const collections = await request('/api/catalog/question-collections', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: false,
|
||||
query: { entryId: adopted.item.targetEntryId },
|
||||
});
|
||||
assert.ok(collections.items?.some(item => item.id === adopted.item.targetCollectionId && item.questionCount === 3), 'adopted public bank should expose a tenant collection');
|
||||
|
||||
const session = await request('/api/learning/practice-sessions', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
collectionId: adopted.item.targetCollectionId,
|
||||
mode: 'sequential',
|
||||
questionLimit: 2,
|
||||
},
|
||||
});
|
||||
assert.equal(session.item?.questionCount, 2, 'adopted public bank collection should be usable for practice sessions');
|
||||
|
||||
const mainTenantNotAdopted = await request('/api/tenant-content/public-question-banks', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
query: { onlyNotAdopted: 'true' },
|
||||
});
|
||||
assert.ok(!mainTenantNotAdopted.items?.some(item => item.grantId === ids.publicQuestionBankGrant), 'platform-owned main tenant should not see plan grant without matching SaaS subscription');
|
||||
}
|
||||
|
||||
async function testTenantAdminOps() {
|
||||
const fakeWechat = await startFakeWechatServer();
|
||||
const denied = await request('/api/tenant-admin/branding', {
|
||||
@@ -3567,6 +3679,7 @@ async function main() {
|
||||
await check('tenant isolation', testTenantIsolation);
|
||||
await check('tenant content admin', testTenantContentAdmin);
|
||||
await check('tenant content assets and imports', testTenantContentAssetsAndImports);
|
||||
await check('public question bank adoption', testPublicQuestionBankAdoption);
|
||||
await check('tenant admin operations', testTenantAdminOps);
|
||||
await check('tenant member permissions and audit', testTenantMemberPermissionsAndAudit);
|
||||
await check('tenant class and student scopes', testTenantClassStudentScopes);
|
||||
|
||||
Reference in New Issue
Block a user