import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const batchPagePath = path.join(adminRoot, 'src/pages/nfc/batch/index.tsx'); const batchLocalePath = path.join(adminRoot, 'src/pages/nfc/batch/locale/index.ts'); test('1. nfc/batch 页面必须循环请求 admin/nfc/device/add', () => { assert.ok(fs.existsSync(batchPagePath), '批量制卡页面必须存在'); const content = fs.readFileSync(batchPagePath, 'utf-8'); assert.ok( content.includes('admin/nfc/device/add'), '必须请求 POST /api/admin/nfc/device/add' ); assert.ok( content.includes("from '@arco-design/web-react'"), '必须从 @arco-design/web-react 引入组件' ); assert.ok( content.includes('code === 1000') || content.includes('code ===1000'), '必须按 Cool-Admin code === 1000 判断成功' ); assert.ok(content.includes('Message.error'), '失败必须使用 Arco Message.error'); assert.ok(content.includes('unbound'), '入库 payload 必须带 status: unbound'); assert.ok(content.includes('batchNo'), '入库 payload 必须带 batchNo'); assert.ok( !content.includes('Mock.js') && !content.includes("from 'mockjs'"), '禁止用 Mock.js 假数据冒充成功' ); assert.ok(!/\bany\b/.test(content), '禁止使用 any'); }); test('2. nfc/batch 必须使用 Form + Table + copy', () => { const content = fs.readFileSync(batchPagePath, 'utf-8'); assert.ok(content.includes('Form'), '必须使用 Arco Form'); assert.ok(content.includes('Table'), '必须使用 Arco Table'); assert.ok( content.includes("from 'copy-to-clipboard'") || content.includes('copy-to-clipboard'), '必须使用 copy-to-clipboard 复制短链' ); assert.ok( content.includes('portal?uid=') || content.includes('portal?uid='), '短链必须包含 portal?uid=' ); assert.ok(content.includes('lightColors'), '颜色必须引用 lightColors token'); assert.ok(content.includes('NFCMaterial'), '必须使用 NFCMaterial 类型'); assert.ok(content.includes("'wood'"), '必须支持材质 wood'); assert.ok(content.includes("'metal'"), '必须支持材质 metal'); assert.ok(content.includes("'pvc'"), '必须支持材质 pvc'); assert.ok(!/\bPagination\b/.test(content), '禁止手写自定义分页器'); }); test('3. nfc/batch 必须有 count 上限或校验文案', () => { const pageContent = fs.readFileSync(batchPagePath, 'utf-8'); const localeContent = fs.readFileSync(batchLocalePath, 'utf-8'); const hasCountCap = pageContent.includes('200') || pageContent.includes('max={200}') || localeContent.includes('nfcBatch.validation.count'); assert.ok(hasCountCap, '必须限制 count 上限 200 或提供校验文案'); assert.ok(pageContent.includes('count'), '表单必须包含 count'); }); test('4. nfc/batch locale 必须包含 menu.nfc.batch', () => { assert.ok(fs.existsSync(batchLocalePath), '批量制卡语言包必须存在'); const content = fs.readFileSync(batchLocalePath, 'utf-8'); assert.ok(content.includes("'menu.nfc.batch'"), '页面语言包必须包含 menu.nfc.batch'); assert.ok(content.includes('nfcBatch.request.fail'), '必须包含失败提示文案'); assert.ok(!/\bany\b/.test(content), 'locale 禁止使用 any'); assert.ok( !content.includes('Mock.js') && !content.includes("from 'mockjs'"), 'locale 禁止 Mock.js' ); }); test('5. 无可用 zip 打包下载时不得假成功 / 假 zip;须禁用打包按钮并提示暂不可打包', () => { const pageContent = fs.readFileSync(batchPagePath, 'utf-8'); const localeContent = fs.readFileSync(batchLocalePath, 'utf-8'); assert.ok( localeContent.includes("'nfcBatch.pack.unavailable'"), 'locale 必须包含 nfcBatch.pack.unavailable' ); assert.ok( localeContent.includes("'nfcBatch.form.packDownload'"), 'locale 必须包含打包下载按钮文案' ); assert.ok( /暂不可打包/.test(localeContent), '中文须说明暂不可打包' ); assert.ok( pageContent.includes("t['nfcBatch.pack.unavailable']") || pageContent.includes('nfcBatch.pack.unavailable'), '页面必须引用 pack.unavailable 文案' ); assert.ok( pageContent.includes("t['nfcBatch.form.packDownload']") || pageContent.includes('nfcBatch.form.packDownload'), '页面必须展示打包下载按钮文案' ); // 禁止客户端造假 zip / 把复制短链当成打包成功 assert.ok( !/JSZip|new Blob\s*\(|application\/zip|createObjectURL/.test(pageContent), '禁止前端生成假 zip' ); assert.ok( !/Message\.success\s*\(\s*t\[['"]nfcBatch\.pack\.(success|download)/.test( pageContent ), '禁止 Message.success 假装打包成功' ); const copySuccessAsPack = /handlePack[\s\S]{0,240}Message\.success\s*\(\s*t\[['"]nfcBatch\.copy\.success['"]\]/.test( pageContent ) || /packDownload[\s\S]{0,240}Message\.success\s*\(\s*t\[['"]nfcBatch\.copy\.success['"]\]/.test( pageContent ); assert.ok(!copySuccessAsPack, '禁止把复制短链成功当成打包成功'); const packDisabled = /packDownload[\s\S]{0,200}disabled|disabled[\s\S]{0,200}packDownload|handlePackUnavailable[\s\S]{0,200}disabled|disabled[\s\S]{0,120}handlePackUnavailable/i.test( pageContent ) || /Button[\s\S]{0,320}nfcBatch\.form\.packDownload[\s\S]{0,120}disabled|disabled[\s\S]{0,200}nfcBatch\.form\.packDownload/i.test( pageContent ); const packUnavailableHint = pageContent.includes("t['nfcBatch.pack.unavailable']") || pageContent.includes('nfcBatch.pack.unavailable'); assert.ok( packDisabled && packUnavailableHint, '无可用打包接口时必须禁用打包下载按钮且点击/悬停提示暂不可打包' ); // 真实入库成功仍须 code===1000 才 Message.success assert.ok( /code\s*===\s*1000[\s\S]{0,400}Message\.success\s*\(\s*t\[['"]nfcBatch\.request\.success['"]\]/.test( pageContent ) || (pageContent.includes('code === 1000') && pageContent.includes("t['nfcBatch.request.success']")), '真实制卡写入仍只在 code===1000 时提示入库成功' ); });