legacy-learning-pull.test.mjs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import vm from 'node:vm';
  4. import { readFileSync } from 'node:fs';
  5. const gatewaySource = readFileSync(new URL('../deploy-admin-functions.mjs', import.meta.url), 'utf8');
  6. const source = gatewaySource.match(/const LEGACY_LEARNING_PULLS=new Map\(\);[\s\S]*?\nconst APP_LIVE_SCOPES=/)?.[0]
  7. .replace(/\nconst APP_LIVE_SCOPES=[\s\S]*$/, '');
  8. assert.ok(source, '未找到旧系统学习记录拉取实现');
  9. function contextFor(rows) {
  10. let sequence = 0;
  11. let stored = [];
  12. const writes = [];
  13. const context = {
  14. console,
  15. URLSearchParams,
  16. AbortSignal,
  17. Map,
  18. Date,
  19. JSON,
  20. String,
  21. Math,
  22. DEFAULT_COMPANY_ID: 'company',
  23. number(value, fallback = 0) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : fallback; },
  24. firstValue(...values) { return values.find((value) => value !== undefined && value !== null && value !== ''); },
  25. parseArray(value) { try { const parsed = typeof value === 'string' ? JSON.parse(value) : value; return Array.isArray(parsed) ? parsed : []; } catch { return []; } },
  26. randomObjectId() { sequence += 1; return `object${String(sequence).padStart(14, '0')}`; },
  27. async appLegacyConfig() { return { url: 'https://legacy.example/api', apiId: 'id', apiKey: 'key' }; },
  28. async fetch(url) {
  29. assert.match(String(url), /myfield2=UserId%3D42/);
  30. return { ok: true, async text() { return JSON.stringify({ retcode: 0, result: JSON.stringify(rows), page: { itemCount: rows.length, pageCount: 1 } }); } };
  31. },
  32. Psql: {
  33. async query(sql) {
  34. if (sql.includes('SELECT c."generalId"')) return stored;
  35. if (sql.includes('SELECT CAST("id" AS text)')) return stored.map((row) => ({ id: row.itemId }));
  36. throw new Error(`unexpected sql: ${sql}`);
  37. },
  38. },
  39. async mobileAtomicWrite(_input, _current, operations) {
  40. writes.push(operations);
  41. for (let index = 0; index < operations.length; index += 2) {
  42. const addon = operations[index].data;
  43. const common = operations[index + 1].data;
  44. stored = stored.filter((item) => Number(item.generalId) !== Number(common.generalId));
  45. stored.push({ ...common, ...addon, itemId: addon.id });
  46. }
  47. return operations.map((operation) => operation.data);
  48. },
  49. };
  50. vm.runInNewContext(`${source};this.normalize=legacyLearningRecord;this.signature=legacyLearningSignature;this.sync=performLegacyLearningSync`, context);
  51. return { context, writes };
  52. }
  53. test('旧系统学习记录按学员过滤,并规范化北京时间与单词明细', () => {
  54. const { context } = contextFor([]);
  55. assert.equal(context.normalize({ GeneralID: 1, UserID: 7 }, 42), null);
  56. const row = context.normalize({
  57. GeneralID: 9001,
  58. UserID: 42,
  59. CreateTime: '2026-09-25 08:10:00',
  60. Title: '灵灵',
  61. dqrq: '2026-09-25',
  62. xxqs: '[ { "GeneralID": 1 } ]',
  63. learned: 1,
  64. }, 42);
  65. assert.equal(row.generalId, 9001);
  66. assert.equal(row.createdAt, '2026-09-25T00:10:00.000Z');
  67. assert.equal(row.addon.dqrq, '20260925');
  68. assert.equal(row.addon.xxqs, '[{"GeneralID":1}]');
  69. });
  70. test('旧系统学习记录只在变化时原子写入,重复轮询不制造版本变化', async () => {
  71. const rows = [{
  72. GeneralID: 9001,
  73. UserID: 42,
  74. CreateTime: '2026-09-25 08:10:00',
  75. Title: '灵灵',
  76. Inputer: '老师',
  77. dqrq: '20260925',
  78. xxqs: '[{"GeneralID":1}]',
  79. learned: 1,
  80. }];
  81. const { context, writes } = contextFor(rows);
  82. const first = await context.sync(42, { id: 'actor' });
  83. const second = await context.sync(42, { id: 'actor' });
  84. assert.equal(first.pulled, 1);
  85. assert.equal(first.written, 1);
  86. assert.equal(second.written, 0);
  87. assert.equal(writes.length, 1);
  88. assert.equal(writes[0].length, 2);
  89. assert.equal(writes[0][0].match.id, 9001);
  90. assert.equal(writes[0][1].match.generalId, 9001);
  91. });
  92. test('移动网关实时版本逻辑不引用运营网关的局部工具函数', () => {
  93. const liveSource = gatewaySource.match(/const APP_LIVE_SCOPES=[\s\S]*?async function appLearningOverview/)?.[0];
  94. assert.ok(liveSource, '未找到移动网关实时版本实现');
  95. assert.doesNotMatch(liveSource, /\btext\(/);
  96. assert.doesNotMatch(liveSource, /\bdateIso\(/);
  97. assert.doesNotMatch(liveSource, /\bsyncConfig\(/);
  98. });