errorRegression.test.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import config from '../src/config/index.js';
  4. import { Decrypt, Encrypt, decryptUrlMiddle } from '../src/util/crypto/index.js';
  5. import { stringUtils } from '../src/util/stringClass.js';
  6. import { GetReaderBooksChapter, GetReaderBooksChapterContent } from '../src/api/yjbdc/readerController.js';
  7. import { buildTextbookWordsQuery } from '../src/model/miaoguo.js';
  8. import { buildCambridgeCategoryUnits } from '../src/api/miaoguo/miaoguoController.js';
  9. test('ReplaceAllString 将正则特殊字符按普通文本替换', () => {
  10. assert.equal(stringUtils.ReplaceAllString('a+b+c', '+', '___'), 'a___b___c');
  11. assert.equal(stringUtils.ReplaceAllString('a.b.c', '.', '-'), 'a-b-c');
  12. });
  13. test('Decrypt 正常往返并将无效密文标记为 400', () => {
  14. const encrypted = Encrypt('Ping?UserID=1', config.urlSecrets.aes_key, config.urlSecrets.aes_iv);
  15. assert.equal(Decrypt(encrypted, config.urlSecrets.aes_key, config.urlSecrets.aes_iv), 'Ping?UserID=1');
  16. assert.throws(
  17. () => Decrypt('not encrypted', config.urlSecrets.aes_key, config.urlSecrets.aes_iv),
  18. error => error.name === 'InvalidEncryptedDataError' && error.status === 400
  19. );
  20. assert.throws(
  21. () => Decrypt('AAAAAAAAAAAAAAAAAAAAAA==', config.urlSecrets.aes_key, config.urlSecrets.aes_iv),
  22. error => error.name === 'InvalidEncryptedDataError' && error.status === 400
  23. );
  24. });
  25. test('URL 解密中间件忽略 apiData 下的普通静态资源路径', async () => {
  26. const previousRequire = config.urlSecrets.require;
  27. config.urlSecrets.require = true;
  28. const ctx = { request: { url: '/apiData/web/School/0221_02.jpeg' } };
  29. let nextCalled = false;
  30. try {
  31. await decryptUrlMiddle()(ctx, async () => { nextCalled = true; });
  32. } finally {
  33. config.urlSecrets.require = previousRequire;
  34. }
  35. assert.equal(nextCalled, true);
  36. assert.equal(ctx.request.url, '/apiData/web/School/0221_02.jpeg');
  37. });
  38. test('URL 解密中间件只重写有效的加密 API 地址', async () => {
  39. const previousRequire = config.urlSecrets.require;
  40. config.urlSecrets.require = true;
  41. const encrypted = Encrypt('Ping?UserID=1', config.urlSecrets.aes_key, config.urlSecrets.aes_iv);
  42. const ctx = { request: { url: `/apiData/${encodeURIComponent(encrypted)}` } };
  43. try {
  44. await decryptUrlMiddle()(ctx, async () => {});
  45. } finally {
  46. config.urlSecrets.require = previousRequire;
  47. }
  48. assert.equal(ctx.request.url, '/api/Ping?UserID=1');
  49. });
  50. test('阅读器章节接口拒绝缺失或不在书单内的 Title', async () => {
  51. for (const controller of [GetReaderBooksChapter, GetReaderBooksChapterContent]) {
  52. const ctx = { query: { Title: '' } };
  53. await controller(ctx);
  54. assert.equal(ctx.status, 400);
  55. assert.deepEqual(ctx.body, { errcode: 400, errStr: '无效的书名' });
  56. }
  57. });
  58. test('阅读器正文接口校验章节号并正确换算回到过去变成猫的章节', async () => {
  59. const invalidCtx = { query: { Title: 'Strange-Life-of-a-Cat', Chapter: 'not-a-number' } };
  60. await GetReaderBooksChapterContent(invalidCtx);
  61. assert.equal(invalidCtx.status, 400);
  62. assert.deepEqual(invalidCtx.body, { errcode: 400, errStr: '无效的章节号' });
  63. const validCtx = { query: { Title: 'Strange-Life-of-a-Cat', Chapter: '1' } };
  64. await GetReaderBooksChapterContent(validCtx);
  65. assert.equal(validCtx.body.errcode, 10000);
  66. assert.ok(validCtx.body.result.length > 0);
  67. });
  68. test('教材释义查询不生成空的 IN 条件并参数化单词', () => {
  69. assert.equal(buildTextbookWordsQuery([]), null);
  70. assert.equal(buildTextbookWordsQuery(''), null);
  71. const textbookQuery = buildTextbookWordsQuery(['abandon', "student's"]);
  72. assert.deepEqual(textbookQuery.params, ['abandon', "student's"]);
  73. assert.match(textbookQuery.sql, /Word in \(\?,\?\)/);
  74. assert.equal(textbookQuery.sql.includes('abandon'), false);
  75. const legacyQuery = buildTextbookWordsQuery('"ability","about"');
  76. assert.deepEqual(legacyQuery.params, ['ability', 'about']);
  77. });
  78. test('剑桥分类词汇按每十个单词生成课程并保留尾组', () => {
  79. const words = Array.from({ length: 23 }, (_, index) => ({ Word: 'word' + (index + 1) }));
  80. const testFunction = [{ N: 'read' }];
  81. const units = buildCambridgeCategoryUnits(words, testFunction);
  82. assert.deepEqual(units.map(item => item.ID), [1, 2, 3]);
  83. assert.deepEqual(units.map(item => item.UnitName), ['Lesson 1', 'Lesson 2', 'Lesson 3']);
  84. assert.deepEqual(units.map(item => item.Length), [10, 10, 3]);
  85. assert.equal(units[2].ListStr, 'word21、word22、word23');
  86. assert.equal(units[0].TestFunction, testFunction);
  87. assert.deepEqual(buildCambridgeCategoryUnits([], testFunction), []);
  88. });