'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 /, ''); } /** @param {object} options @returns {object} */ function createContext(options = {}) { const state = { selectedYear: 2026, selectedMonth: 8, userId: '626968159', userInfo: { userName: '冷志刚/Zhigang Leng', staffType: '内部', managerId: '640891109' }, projectRows: [], ...options.state, }; return { state, collectApprovalData: () => options.projectGroups || {}, collectOtherApprovalData: () => ({ otherRows: [{ dateStr: '20260813', activityName: 'Annual Leave', hours: 8 }], otherTotal: 8, ...options.otherGroup, }), getRepairMonthText: (year, month) => String(year) + String(month).padStart(2, '0'), fetchDeptManagerId: methods.fetchDeptManagerId, buildTimecardRepairRequest: methods.buildTimecardRepairRequest, getTexts: () => ({ repairBackendPending: '补填连接器未配置' }), unwrapConnectorResponse: (response) => response.data, }; } const sourcePath = path.resolve(__dirname, '../src/timecard-repair.oyd.jsx'); const schemaFile = process.argv[2]; const schema = schemaFile ? JSON.parse(fs.readFileSync(schemaFile, 'utf8')).content : null; const compiled = process.argv[3] === 'compiled'; const source = schema ? schema.actions.module[compiled ? 'compiled' : 'source'] : fs.readFileSync(sourcePath, 'utf8'); const methods = vm.runInNewContext( (compiled ? source : ['buildTimecardRepairRequest', 'submitTimecardRepairRequest', 'fetchDeptManagerId'] .map((name) => extractFunction(source, name)).join('\n')) + '\n({buildTimecardRepairRequest, submitTimecardRepairRequest, fetchDeptManagerId})', { exports: {}, REPAIR_DATA_SOURCES: { submit: 'startTimecardRepair' } }, ); /** @returns {Promise} */ async function main() { const cases = [ ['内部员工缓存主管', { state: { deptManagerInfo: { managerId: '640891109', isFallback: false } } }, '640891109'], ['缓存缺失时从内部人员档案获取', {}, '640891109'], ['外部员工从有效项目获取', { state: { userInfo: { staffType: '外部' }, projectRows: [{ managerId: '634735129' }, { managerId: 'project-manager' }], } }, 'project-manager'], ['内部员工未配置主管时保留业务兜底', { state: { userInfo: { staffType: '内部' } } }, '634735129'], ['外部员工无有效项目经理时保留业务兜底', { state: { userInfo: { staffType: '外部' } } }, '634735129'], ]; for (const [name, options, expected] of cases) { const context = createContext(options); const request = await methods.buildTimecardRepairRequest.call(context); assert.equal(request.otherGroup.managerId, expected, name); assert.equal(request.otherGroup.otherTotal, 8, name); assert.equal(request.otherGroup.otherRows[0].hours, 8, name); assert.equal(request.sourceMonthText, '202608', name); assert.equal(request.submitterUserId, '626968159', name); console.log('PASS ' + name); } const projectGroups = { pm: { managerId: 'original-pm', billableTotal: 8, nonBillableTotal: 0 } }; const projectOnly = createContext({ projectGroups, otherGroup: { otherRows: [], otherTotal: 0 } }); projectOnly.fetchDeptManagerId = () => { throw new Error('纯项目补填无需获取其他工时主管'); }; assert.equal((await methods.buildTimecardRepairRequest.call(projectOnly)).projectGroups, projectGroups); console.log('PASS 项目工时分组保持原样'); const context = createContext(); let submitted; context.getCallableDataSource = () => ({ load: async (payload) => { submitted = JSON.parse(payload.inputs).Body; return { data: { status: '审批中' } }; } }); const result = await methods.submitTimecardRepairRequest.call(context); assert.equal(submitted.otherGroup.managerId, '640891109'); assert.equal(submitted.otherGroup.otherRows[0].activityName, 'Annual Leave'); assert.equal(result.status, '审批中'); console.log('PASS 连接器实际请求携带 Kevin ID'); const failedContext = createContext(); failedContext.fetchDeptManagerId = async () => { throw new Error('主管获取失败'); }; failedContext.getCallableDataSource = () => ({ load: () => assert.fail('主管获取失败后不得提交') }); await assert.rejects(methods.submitTimecardRepairRequest.call(failedContext), /主管获取失败/); console.log('PASS 主管获取失败阻止提交'); } main().catch((error) => { console.error(error); process.exitCode = 1; });