import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; const MP_ROOT = path.resolve('m:/2026项目/CRMmp/mpuniapp'); const calendarUtsPath = path.join(MP_ROOT, 'common/calendar.uts'); const typesUtsPath = path.join(MP_ROOT, 'common/types.uts'); const localeUtsPath = path.join(MP_ROOT, 'common/locale.uts'); const portalIndexUvuePath = path.join(MP_ROOT, 'pages/portal/index.uvue'); const usePortalUtsPath = path.join(MP_ROOT, 'pages/portal/usePortal.uts'); test('1. common/calendar.uts 文件与核心导出函数契约检查', () => { assert.ok(fs.existsSync(calendarUtsPath), '必须存在 common/calendar.uts'); const content = fs.readFileSync(calendarUtsPath, 'utf-8'); assert.ok(content.includes('export function generateGoogleCalendarUrl'), '必须导出 generateGoogleCalendarUrl'); assert.ok(content.includes('export function generateOutlookCalendarUrl'), '必须导出 generateOutlookCalendarUrl'); assert.ok(content.includes('export function getMeetingIcsDownloadUrl'), '必须导出 getMeetingIcsDownloadUrl'); assert.ok(content.includes('export function buildCalendarInviteBundle'), '必须导出 buildCalendarInviteBundle'); }); test('2. common/types.uts 中 BentoBlockType 与预约配置契约对齐', () => { assert.ok(fs.existsSync(typesUtsPath), '必须存在 common/types.uts'); const content = fs.readFileSync(typesUtsPath, 'utf-8'); assert.ok(content.includes("'appointment'"), "BentoBlockType 必须包含 'appointment'"); assert.ok(content.includes('meetingUrl?: string'), 'CardPortalDetail / CardProfile 必须包含 meetingUrl 属性'); assert.ok(content.includes('MeetingInviteOptions'), '必须导出 MeetingInviteOptions 接口'); }); test('3. common/locale.uts 必须覆盖 7 种国际主流语言的商务预约词条', () => { assert.ok(fs.existsSync(localeUtsPath), '必须存在 common/locale.uts'); const content = fs.readFileSync(localeUtsPath, 'utf-8'); const requiredKeys = [ 'portal.appointment', 'portal.appointment_desc', 'portal.book_now', 'portal.google_cal', 'portal.outlook_cal', 'portal.ics_download', 'portal.copied_meeting_link', ]; for (const key of requiredKeys) { assert.ok(content.includes(`'${key}'`), `必须在 OmniMessageKey 中定义 ${key}`); } const locales = ['zh-CN', 'en-US', 'ar-SA', 'es-ES', 'de-DE', 'fr-FR', 'ja-JP']; for (const loc of locales) { assert.ok(content.includes(`'${loc}':`), `必须配置语言 ${loc}`); assert.ok(content.includes("'portal.appointment':"), `${loc} 必须包含 portal.appointment`); assert.ok(content.includes("'portal.book_now':"), `${loc} 必须包含 portal.book_now`); } }); test('4. pages/portal/index.uvue 中集成商务日程预约卡片与底部抽屉', () => { assert.ok(fs.existsSync(portalIndexUvuePath), '必须存在 index.uvue'); const content = fs.readFileSync(portalIndexUvuePath, 'utf-8'); // 必须包含预约卡片与唤起方法 assert.ok(content.includes('appointment-card') || content.includes('appointment-box'), '必须包含预约卡片组件/元素'); assert.ok(content.includes('openAppointmentSheet'), '必须包含打开预约抽屉的方法 openAppointmentSheet'); assert.ok(content.includes('appointment-sheet') || content.includes('appointment-drawer'), '必须包含预约底部抽屉'); // 抽屉内必须提供三种日历同步渠道 assert.ok(content.includes('handleCalendarSync'), '必须包含日历同步处理 handleCalendarSync'); assert.ok(content.includes("'google'"), '必须支持 Google Calendar'); assert.ok(content.includes("'outlook'"), '必须支持 Outlook Calendar'); assert.ok(content.includes("'ics'"), '必须支持 ICS 日历文件'); // 零 Emoji 审查(严禁在代码中写死非业务 emoji 图标) const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u; assert.equal(emojiRegex.test(content), false, 'index.uvue 严禁含有装饰性 Emoji'); }); test('5. pages/portal/usePortal.uts 中必须上报 appointment 雷达轨迹', () => { assert.ok(fs.existsSync(usePortalUtsPath), '必须存在 usePortal.uts'); const content = fs.readFileSync(usePortalUtsPath, 'utf-8'); assert.ok( content.includes("touchType: 'appointment'") || content.includes('trackAppointmentAction'), '预约动作必须向雷达上报 touchType appointment 推进商机' ); }); test('6. 日历链接生成与时间戳算法规范断言', () => { const content = fs.readFileSync(calendarUtsPath, 'utf-8'); // Google Calendar 规范检查 assert.ok( content.includes('calendar.google.com/calendar/render'), 'Google Calendar 必须指向官方 render 终端' ); assert.ok( content.includes('action=TEMPLATE'), 'Google Calendar 必须使用 TEMPLATE 动作' ); // Outlook 规范检查 assert.ok( content.includes('outlook.live.com/calendar/0/deeplink/compose'), 'Outlook Calendar 必须使用官方 compose 深度链接' ); assert.ok( content.includes('rru=addevent'), 'Outlook Calendar 必须指定 rru=addevent' ); // 后端 RFC 5545 ICS 接口规范检查 assert.ok( content.includes('/open/card/portal/meeting-ics'), 'ICS 下载必须复用后端 /open/card/portal/meeting-ics 接口' ); assert.ok( content.includes('summary='), 'ICS 请求参数必须包含 summary' ); assert.ok( content.includes('startTime='), 'ICS 请求参数必须包含 startTime' ); assert.ok( content.includes('endTime='), 'ICS 请求参数必须包含 endTime' ); // UTC RFC 5545 时间戳对齐 assert.ok( content.includes('getUTCFullYear') && content.includes('getUTCMonth'), '时间戳格式化必须采用 UTC 时区计算避免跨国时区偏差' ); });