forked from wangziqi/gongxue-base
feat: support daily practice exports
This commit is contained in:
@@ -2814,6 +2814,41 @@ async function testTenantContentAdmin() {
|
||||
const exportedQuestionWithAnswer = paperExport.export?.questions?.find(item => item.id === question.item.id);
|
||||
assert.equal(exportedQuestionWithAnswer?.answerText, '4', 'paper export should include answers when requested');
|
||||
|
||||
const dailyPracticeExport = await request('/api/tenant-content/exports/questions', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
scopeType: 'collection',
|
||||
scopeId: collection.item.id,
|
||||
format: 'json',
|
||||
exportType: 'daily_practice',
|
||||
includeAnswers: true,
|
||||
includeExplanations: false,
|
||||
limit: 99,
|
||||
options: {
|
||||
title: '第 1 期每日一练',
|
||||
issue: '每日一练 第 1 期',
|
||||
date: '2026-06-29',
|
||||
theme: 'ink',
|
||||
cardFormat: '1:1',
|
||||
brand: {
|
||||
name: '恭学教育',
|
||||
english: 'GONGXUE EDU',
|
||||
slogan: '专注高职升本',
|
||||
ctaLine: '每日一练 · 稳步上岸',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(dailyPracticeExport.export?.exportType, 'daily_practice', 'daily practice export should use daily_practice type');
|
||||
assert.equal(dailyPracticeExport.export?.summary?.issue, '每日一练 第 1 期', 'daily practice export should expose issue metadata');
|
||||
assert.equal(dailyPracticeExport.export?.summary?.cardCount, 1, 'daily practice export should expose card count');
|
||||
assert.equal(dailyPracticeExport.export?.dailyPractice?.slots?.some(item => item.slot === 4 && item.kind === 'center'), true, 'daily practice export should include center slot metadata');
|
||||
assert.ok(
|
||||
dailyPracticeExport.export?.questions?.length <= 8,
|
||||
'daily practice export should cap question count for nine-grid material',
|
||||
);
|
||||
|
||||
const pdfExportJob = await request('/api/tenant-content/exports/questions', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
|
||||
@@ -15,9 +15,10 @@ const ids = {
|
||||
pdfJob: '21000000-0000-0000-0000-000000000901',
|
||||
docxJob: '21000000-0000-0000-0000-000000000902',
|
||||
noAnswerDocxJob: '21000000-0000-0000-0000-000000000903',
|
||||
dailyPdfJob: '21000000-0000-0000-0000-000000000904',
|
||||
};
|
||||
|
||||
const jobIds = [ids.pdfJob, ids.docxJob, ids.noAnswerDocxJob];
|
||||
const jobIds = [ids.pdfJob, ids.docxJob, ids.noAnswerDocxJob, ids.dailyPdfJob];
|
||||
|
||||
async function runWorkerOnce() {
|
||||
const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'exports'], {
|
||||
@@ -110,9 +111,17 @@ async function seed(pool) {
|
||||
'{"title":"导出测试无答案文档","watermarkText":"内部水印","publishToAssets":false,"assetVisibility":"private","limit":50}'::jsonb,
|
||||
'{"delivery":"content_asset_pending","maxQuestionLimit":50}'::jsonb,
|
||||
now() - interval '1 minute', now() - interval '1 minute'
|
||||
),
|
||||
(
|
||||
$7, $4, $5, 'daily_practice', 'pdf', 'collection', $6,
|
||||
'pending', true, false, false,
|
||||
0, '{"source":"export-worker-test"}'::jsonb,
|
||||
'{"title":"每日一练测试","issue":"每日一练 第 1 期","date":"2026-06-29","watermarkText":"每日一练水印","publishToAssets":true,"assetVisibility":"tenant","limit":99,"brand":{"name":"恭学教育","english":"GONGXUE EDU","slogan":"专注高职升本","ctaLine":"每日一练 · 稳步上岸"}}'::jsonb,
|
||||
'{"delivery":"content_asset_pending","maxQuestionLimit":8}'::jsonb,
|
||||
now() - interval '1 minute', now() - interval '1 minute'
|
||||
)
|
||||
`,
|
||||
[ids.pdfJob, ids.docxJob, ids.noAnswerDocxJob, tenantId, tenantAdminUserId, collectionId],
|
||||
[ids.pdfJob, ids.docxJob, ids.noAnswerDocxJob, tenantId, tenantAdminUserId, collectionId, ids.dailyPdfJob],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,7 +168,7 @@ async function main() {
|
||||
seeded = true;
|
||||
|
||||
const output = await runWorkerOnce();
|
||||
assert.ok(countFromWorkerOutput(output, 'completed') >= 3, 'worker should complete all binary export jobs');
|
||||
assert.ok(countFromWorkerOutput(output, 'completed') >= 4, 'worker should complete all binary export jobs');
|
||||
|
||||
const jobs = await pool.query(
|
||||
`
|
||||
@@ -170,7 +179,7 @@ async function main() {
|
||||
`,
|
||||
[tenantId, jobIds],
|
||||
);
|
||||
assert.equal(jobs.rowCount, 3, 'all export jobs should still exist');
|
||||
assert.equal(jobs.rowCount, 4, 'all export jobs should still exist');
|
||||
for (const job of jobs.rows) {
|
||||
assert.equal(job.status, 'completed', 'export job should be completed');
|
||||
assert.ok(job.asset_id, 'completed export job should reference a content asset');
|
||||
@@ -190,24 +199,30 @@ async function main() {
|
||||
`,
|
||||
[tenantId, jobIds],
|
||||
);
|
||||
assert.equal(assets.rowCount, 3, 'worker should create three export assets');
|
||||
assert.equal(assets.rowCount, 4, 'worker should create four export assets');
|
||||
const pdfAsset = assets.rows.find(asset => asset.metadata?.exportJobId === ids.pdfJob);
|
||||
const docxAsset = assets.rows.find(asset => asset.metadata?.exportJobId === ids.docxJob);
|
||||
const noAnswerDocxAsset = assets.rows.find(asset => asset.metadata?.exportJobId === ids.noAnswerDocxJob);
|
||||
const dailyPdfAsset = assets.rows.find(asset => asset.metadata?.exportJobId === ids.dailyPdfJob);
|
||||
assert.ok(pdfAsset, 'PDF export should create a PDF content asset');
|
||||
assert.ok(docxAsset, 'DOCX export should create a Word content asset');
|
||||
assert.ok(noAnswerDocxAsset, 'no-answer DOCX export should create a Word content asset');
|
||||
assert.ok(dailyPdfAsset, 'daily practice PDF export should create a PDF content asset');
|
||||
assert.equal(pdfAsset.mime_type, 'application/pdf', 'PDF asset should use application/pdf');
|
||||
assert.ok(docxAsset.mime_type?.includes('wordprocessingml'), 'DOCX asset should use Word MIME type');
|
||||
assert.equal(pdfAsset.upload_status, 'verified', 'PDF asset should be verified');
|
||||
assert.equal(docxAsset.upload_status, 'verified', 'DOCX asset should be verified');
|
||||
assert.equal(noAnswerDocxAsset.upload_status, 'verified', 'no-answer DOCX asset should be verified');
|
||||
assert.equal(dailyPdfAsset.upload_status, 'verified', 'daily practice PDF asset should be verified');
|
||||
assert.equal(pdfAsset.visibility, 'tenant', 'published PDF export should use requested asset visibility');
|
||||
assert.equal(docxAsset.visibility, 'private', 'non-published DOCX export should remain private');
|
||||
assert.equal(noAnswerDocxAsset.visibility, 'private', 'no-answer DOCX export should remain private');
|
||||
assert.equal(dailyPdfAsset.visibility, 'tenant', 'daily practice PDF export should use requested asset visibility');
|
||||
assert.equal(dailyPdfAsset.metadata?.exportType, 'daily_practice', 'daily practice asset should keep export type metadata');
|
||||
await assertGeneratedFile(pdfAsset);
|
||||
await assertGeneratedFile(docxAsset);
|
||||
await assertGeneratedFile(noAnswerDocxAsset);
|
||||
await assertGeneratedFile(dailyPdfAsset);
|
||||
|
||||
const answeredDocxText = await docxText(docxAsset);
|
||||
assert.match(answeredDocxText, /答案/, 'DOCX with answers should include answer labels');
|
||||
|
||||
Reference in New Issue
Block a user