feat: add ai recommendation exports

This commit is contained in:
Codex
2026-06-30 19:04:30 +08:00
parent ba737d41f5
commit 686b3609cf
13 changed files with 380 additions and 12 deletions

View File

@@ -3206,6 +3206,30 @@ async function testAiSchoolRecommendationAfterSvip() {
});
assert.equal(detail.item?.id, generated.item.id, 'AI report detail should load own report');
const markdownExport = await request('/api/ai/school-recommendations/export', {
query: { reportId: generated.item.id, format: 'markdown' },
});
assert.equal(markdownExport.item?.format, 'markdown', 'AI report export should support markdown');
assert.ok(markdownExport.item?.fileName?.endsWith('.md'), 'AI markdown export should return a markdown filename');
assert.ok(markdownExport.item?.sha256, 'AI report export should include content hash');
const markdownReport = Buffer.from(markdownExport.item?.contentBase64 || '', 'base64').toString('utf8');
assert.ok(markdownReport.includes('AI 择校推荐报告'), 'AI markdown export should include report title');
assert.ok(markdownReport.includes('烟测学院'), 'AI markdown export should include recommended school');
assert.ok(markdownReport.includes('免责声明'), 'AI markdown export should include disclaimers');
assert.ok(!markdownReport.includes('secret'), 'AI markdown export should not leak provider secrets');
const htmlExport = await request('/api/ai/school-recommendations/export', {
query: { reportId: generated.item.id, format: 'html' },
});
assert.equal(htmlExport.item?.format, 'html', 'AI report export should support html');
assert.ok(Buffer.from(htmlExport.item?.contentBase64 || '', 'base64').toString('utf8').includes('<!doctype html>'), 'AI html export should return HTML content');
const invalidExportFormat = await request('/api/ai/school-recommendations/export', {
query: { reportId: generated.item.id, format: 'pdf' },
expectStatus: 400,
});
assert.equal(invalidExportFormat.code, 'AI_REPORT_EXPORT_FORMAT_INVALID', 'AI report export should reject unsupported formats');
const otherStudentDenied = await request('/api/ai/school-recommendations/detail', {
userId: SECOND_STUDENT_USER_ID,
query: { reportId: generated.item.id },
@@ -3213,6 +3237,13 @@ async function testAiSchoolRecommendationAfterSvip() {
});
assert.equal(otherStudentDenied.code, 'AI_REPORT_NOT_FOUND', 'students must not read other students AI reports');
const otherStudentExportDenied = await request('/api/ai/school-recommendations/export', {
userId: SECOND_STUDENT_USER_ID,
query: { reportId: generated.item.id, format: 'markdown' },
expectStatus: 404,
});
assert.equal(otherStudentExportDenied.code, 'AI_REPORT_NOT_FOUND', 'students must not export other students AI reports');
const partnerTenantDenied = await request('/api/ai/school-recommendations/generate', {
tenantId: PARTNER_TENANT_ID,
method: 'POST',