| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- 'use strict';
- const assert = require('node:assert/strict');
- const fs = require('node:fs');
- const path = require('node:path');
- const vm = require('node:vm');
- /** @param {string} source @param {string} name @returns {string} */
- function extractFunction(source, name) {
- const pattern = new RegExp('export (?:async )?function ' + name + '\\(');
- const match = pattern.exec(source);
- assert.ok(match, '缺少方法 ' + name);
- const end = source.indexOf('\n/**', match.index + match[0].length);
- return source.slice(match.index, end < 0 ? source.length : end).replace(/^export /, '');
- }
- const defaultSourcePath = path.resolve(__dirname, '../src/timecard-repair-lifecycle.oyd.jsx');
- const sourcePath = process.argv[2] ? path.resolve(process.argv[2]) : defaultSourcePath;
- const source = fs.readFileSync(sourcePath, 'utf8');
- let holdTimer = false;
- let pendingTimer = null;
- const methods = vm.runInNewContext(
- ['getRepairContextErrorMessage', 'isRepairContextRetryableError', 'waitForRepairContextRetry', 'loadTimecardRepairStatus']
- .map((name) => extractFunction(source, name)).join('\n')
- + '\n({getRepairContextErrorMessage, isRepairContextRetryableError, waitForRepairContextRetry, loadTimecardRepairStatus})',
- {
- REPAIR_DATA_SOURCES: { status: 'getTimecardRepairStatus' },
- setTimeout: (callback) => {
- if (!holdTimer) {
- callback();
- return { immediate: true };
- }
- pendingTimer = { callback, cleared: false };
- return pendingTimer;
- },
- },
- );
- /** @param {*} response @returns {object} */
- function createContext(response) {
- return {
- getTexts: () => ({ repairContextTimeout: '补填工时加载超时,请稍后重试', errUnknown: '未知错误' }),
- getRepairContextErrorMessage: methods.getRepairContextErrorMessage,
- isRepairContextRetryableError: methods.isRepairContextRetryableError,
- waitForRepairContextRetry: methods.waitForRepairContextRetry,
- getRepairMonthText: (year, month) => String(year) + String(month).padStart(2, '0'),
- getCallableDataSource: () => ({ load: async () => response }),
- unwrapConnectorResponse: (value) => value,
- };
- }
- /** @returns {Promise<void>} */
- async function main() {
- const errorContext = createContext(null);
- assert.equal(methods.getRepairContextErrorMessage.call(errorContext, new Error('Read timed out')),
- '补填工时加载超时,请稍后重试');
- assert.equal(methods.getRepairContextErrorMessage.call(errorContext, { errorCode: 'ETIMEDOUT', code: 'ETIMEDOUT' }),
- '补填工时加载超时,请稍后重试');
- assert.equal(methods.getRepairContextErrorMessage.call(errorContext, new Error('接口返回 500')), '接口返回 500');
- assert.equal(methods.getRepairContextErrorMessage.call(errorContext, {}), '未知错误');
- const response = { originalRecords: [{ id: 'original' }], repairRecords: [{ id: 'repair' }], records: [] };
- const result = await methods.loadTimecardRepairStatus.call(createContext(response), 'user-1', 2026, 9);
- assert.equal(result.configured, true);
- assert.equal(result.originalRecords[0].id, 'original');
- assert.equal(result.repairRecords[0].id, 'repair');
- assert.equal(Object.hasOwn(result, 'closed'), false);
- let retryCount = 0;
- const retryContext = createContext(response);
- retryContext.getCallableDataSource = () => ({
- load: async () => {
- retryCount += 1;
- if (retryCount === 1) throw new Error('Read timed out');
- return response;
- },
- });
- const retryResult = await methods.loadTimecardRepairStatus.call(retryContext, 'user-1', 2026, 9);
- assert.equal(retryCount, 2);
- assert.equal(retryResult.originalRecords[0].id, 'original');
- let pendingCount = 0;
- const pendingContext = createContext(response);
- pendingContext.getCallableDataSource = () => ({
- load: async () => {
- pendingCount += 1;
- return pendingCount < 3 ? { ready: false, retryAfterMillis: 500 } : response;
- },
- });
- const pendingResult = await methods.loadTimecardRepairStatus.call(pendingContext, 'user-1', 2026, 9);
- assert.equal(pendingCount, 3);
- assert.equal(pendingResult.repairRecords[0].id, 'repair');
- let emptyCount = 0;
- const emptyContext = createContext(response);
- emptyContext.getCallableDataSource = () => ({
- load: async () => {
- emptyCount += 1;
- return emptyCount === 1 ? null : response;
- },
- });
- const emptyRetryResult = await methods.loadTimecardRepairStatus.call(emptyContext, 'user-1', 2026, 9);
- assert.equal(emptyCount, 2);
- assert.equal(emptyRetryResult.originalRecords[0].id, 'original');
- let exhaustedCount = 0;
- const exhaustedContext = createContext(null);
- exhaustedContext.getCallableDataSource = () => ({
- load: async () => {
- exhaustedCount += 1;
- return { originalRecords: [] };
- },
- });
- await assert.rejects(
- methods.loadTimecardRepairStatus.call(exhaustedContext, 'user-1', 2026, 9),
- /补填工时加载超时,请稍后重试/,
- );
- assert.equal(exhaustedCount, 45);
- let businessErrorCount = 0;
- const businessErrorContext = createContext(null);
- businessErrorContext.getCallableDataSource = () => ({
- load: async () => {
- businessErrorCount += 1;
- throw new Error('无权限访问');
- },
- });
- await assert.rejects(
- methods.loadTimecardRepairStatus.call(businessErrorContext, 'user-1', 2026, 9),
- /无权限访问/,
- );
- assert.equal(businessErrorCount, 1);
- let releasedOnUnmount = false;
- const waitContext = {
- _tcDisposed: false,
- _tcRepairContextPollTimers: [],
- };
- holdTimer = true;
- methods.waitForRepairContextRetry.call(waitContext, 2000).then(() => { releasedOnUnmount = true; });
- assert.equal(waitContext._tcRepairContextPollTimers.length, 1);
- const entry = waitContext._tcRepairContextPollTimers[0];
- assert.equal(entry.timerId, pendingTimer);
- pendingTimer.cleared = true;
- entry.resolve();
- await Promise.resolve();
- assert.equal(pendingTimer.cleared, true);
- assert.equal(releasedOnUnmount, true);
- holdTimer = false;
- console.log('PASS 补填上下文 pending 轮询、空响应重试、总超时和业务错误识别');
- }
- main().catch((error) => { console.error(error); process.exitCode = 1; });
|