import test from 'node:test'; import assert from 'node:assert/strict'; import config from '../src/config/index.js'; import { Decrypt, Encrypt, decryptUrlMiddle } from '../src/util/crypto/index.js'; import { stringUtils } from '../src/util/stringClass.js'; import { GetReaderBooksChapter, GetReaderBooksChapterContent } from '../src/api/yjbdc/readerController.js'; import { buildTextbookWordsQuery } from '../src/model/miaoguo.js'; import { buildCambridgeCategoryUnits } from '../src/api/miaoguo/miaoguoController.js'; test('ReplaceAllString 将正则特殊字符按普通文本替换', () => { assert.equal(stringUtils.ReplaceAllString('a+b+c', '+', '___'), 'a___b___c'); assert.equal(stringUtils.ReplaceAllString('a.b.c', '.', '-'), 'a-b-c'); }); test('Decrypt 正常往返并将无效密文标记为 400', () => { const encrypted = Encrypt('Ping?UserID=1', config.urlSecrets.aes_key, config.urlSecrets.aes_iv); assert.equal(Decrypt(encrypted, config.urlSecrets.aes_key, config.urlSecrets.aes_iv), 'Ping?UserID=1'); assert.throws( () => Decrypt('not encrypted', config.urlSecrets.aes_key, config.urlSecrets.aes_iv), error => error.name === 'InvalidEncryptedDataError' && error.status === 400 ); assert.throws( () => Decrypt('AAAAAAAAAAAAAAAAAAAAAA==', config.urlSecrets.aes_key, config.urlSecrets.aes_iv), error => error.name === 'InvalidEncryptedDataError' && error.status === 400 ); }); test('URL 解密中间件忽略 apiData 下的普通静态资源路径', async () => { const previousRequire = config.urlSecrets.require; config.urlSecrets.require = true; const ctx = { request: { url: '/apiData/web/School/0221_02.jpeg' } }; let nextCalled = false; try { await decryptUrlMiddle()(ctx, async () => { nextCalled = true; }); } finally { config.urlSecrets.require = previousRequire; } assert.equal(nextCalled, true); assert.equal(ctx.request.url, '/apiData/web/School/0221_02.jpeg'); }); test('URL 解密中间件只重写有效的加密 API 地址', async () => { const previousRequire = config.urlSecrets.require; config.urlSecrets.require = true; const encrypted = Encrypt('Ping?UserID=1', config.urlSecrets.aes_key, config.urlSecrets.aes_iv); const ctx = { request: { url: `/apiData/${encodeURIComponent(encrypted)}` } }; try { await decryptUrlMiddle()(ctx, async () => {}); } finally { config.urlSecrets.require = previousRequire; } assert.equal(ctx.request.url, '/api/Ping?UserID=1'); }); test('阅读器章节接口拒绝缺失或不在书单内的 Title', async () => { for (const controller of [GetReaderBooksChapter, GetReaderBooksChapterContent]) { const ctx = { query: { Title: '' } }; await controller(ctx); assert.equal(ctx.status, 400); assert.deepEqual(ctx.body, { errcode: 400, errStr: '无效的书名' }); } }); test('阅读器正文接口校验章节号并正确换算回到过去变成猫的章节', async () => { const invalidCtx = { query: { Title: 'Strange-Life-of-a-Cat', Chapter: 'not-a-number' } }; await GetReaderBooksChapterContent(invalidCtx); assert.equal(invalidCtx.status, 400); assert.deepEqual(invalidCtx.body, { errcode: 400, errStr: '无效的章节号' }); const validCtx = { query: { Title: 'Strange-Life-of-a-Cat', Chapter: '1' } }; await GetReaderBooksChapterContent(validCtx); assert.equal(validCtx.body.errcode, 10000); assert.ok(validCtx.body.result.length > 0); }); test('教材释义查询不生成空的 IN 条件并参数化单词', () => { assert.equal(buildTextbookWordsQuery([]), null); assert.equal(buildTextbookWordsQuery(''), null); const textbookQuery = buildTextbookWordsQuery(['abandon', "student's"]); assert.deepEqual(textbookQuery.params, ['abandon', "student's"]); assert.match(textbookQuery.sql, /Word in \(\?,\?\)/); assert.equal(textbookQuery.sql.includes('abandon'), false); const legacyQuery = buildTextbookWordsQuery('"ability","about"'); assert.deepEqual(legacyQuery.params, ['ability', 'about']); }); test('剑桥分类词汇按每十个单词生成课程并保留尾组', () => { const words = Array.from({ length: 23 }, (_, index) => ({ Word: 'word' + (index + 1) })); const testFunction = [{ N: 'read' }]; const units = buildCambridgeCategoryUnits(words, testFunction); assert.deepEqual(units.map(item => item.ID), [1, 2, 3]); assert.deepEqual(units.map(item => item.UnitName), ['Lesson 1', 'Lesson 2', 'Lesson 3']); assert.deepEqual(units.map(item => item.Length), [10, 10, 3]); assert.equal(units[2].ListStr, 'word21、word22、word23'); assert.equal(units[0].TestFunction, testFunction); assert.deepEqual(buildCambridgeCategoryUnits([], testFunction), []); });