verify-repair-manager.cjs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. 'use strict';
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. const path = require('node:path');
  5. const vm = require('node:vm');
  6. /** @param {string} source @param {string} name @returns {string} */
  7. function extractFunction(source, name) {
  8. const pattern = new RegExp('export (?:async )?function ' + name + '\\(');
  9. const match = pattern.exec(source);
  10. assert.ok(match, '缺少方法 ' + name);
  11. const end = source.indexOf('\n/**', match.index + match[0].length);
  12. return source.slice(match.index, end < 0 ? source.length : end).replace(/^export /, '');
  13. }
  14. /** @param {object} options @returns {object} */
  15. function createContext(options = {}) {
  16. const state = {
  17. selectedYear: 2026, selectedMonth: 8, userId: '626968159',
  18. userInfo: { userName: '冷志刚/Zhigang Leng', staffType: '内部', managerId: '640891109' },
  19. projectRows: [], ...options.state,
  20. };
  21. return {
  22. state,
  23. collectApprovalData: () => options.projectGroups || {},
  24. collectOtherApprovalData: () => ({
  25. otherRows: [{ dateStr: '20260813', activityName: 'Annual Leave', hours: 8 }],
  26. otherTotal: 8, ...options.otherGroup,
  27. }),
  28. getRepairMonthText: (year, month) => String(year) + String(month).padStart(2, '0'),
  29. fetchDeptManagerId: methods.fetchDeptManagerId,
  30. buildTimecardRepairRequest: methods.buildTimecardRepairRequest,
  31. getTexts: () => ({ repairBackendPending: '补填连接器未配置' }),
  32. unwrapConnectorResponse: (response) => response.data,
  33. };
  34. }
  35. const sourcePath = path.resolve(__dirname, '../src/timecard-repair.oyd.jsx');
  36. const schemaFile = process.argv[2];
  37. const schema = schemaFile ? JSON.parse(fs.readFileSync(schemaFile, 'utf8')).content : null;
  38. const compiled = process.argv[3] === 'compiled';
  39. const source = schema ? schema.actions.module[compiled ? 'compiled' : 'source'] : fs.readFileSync(sourcePath, 'utf8');
  40. const methods = vm.runInNewContext(
  41. (compiled ? source : ['buildTimecardRepairRequest', 'submitTimecardRepairRequest', 'fetchDeptManagerId']
  42. .map((name) => extractFunction(source, name)).join('\n'))
  43. + '\n({buildTimecardRepairRequest, submitTimecardRepairRequest, fetchDeptManagerId})',
  44. { exports: {}, REPAIR_DATA_SOURCES: { submit: 'startTimecardRepair' } },
  45. );
  46. /** @returns {Promise<void>} */
  47. async function main() {
  48. const cases = [
  49. ['内部员工缓存主管', { state: { deptManagerInfo: { managerId: '640891109', isFallback: false } } }, '640891109'],
  50. ['缓存缺失时从内部人员档案获取', {}, '640891109'],
  51. ['外部员工从有效项目获取', { state: {
  52. userInfo: { staffType: '外部' },
  53. projectRows: [{ managerId: '634735129' }, { managerId: 'project-manager' }],
  54. } }, 'project-manager'],
  55. ['内部员工未配置主管时保留业务兜底', { state: { userInfo: { staffType: '内部' } } }, '634735129'],
  56. ['外部员工无有效项目经理时保留业务兜底', { state: { userInfo: { staffType: '外部' } } }, '634735129'],
  57. ];
  58. for (const [name, options, expected] of cases) {
  59. const context = createContext(options);
  60. const request = await methods.buildTimecardRepairRequest.call(context);
  61. assert.equal(request.otherGroup.managerId, expected, name);
  62. assert.equal(request.otherGroup.otherTotal, 8, name);
  63. assert.equal(request.otherGroup.otherRows[0].hours, 8, name);
  64. assert.equal(request.sourceMonthText, '202608', name);
  65. assert.equal(request.submitterUserId, '626968159', name);
  66. console.log('PASS ' + name);
  67. }
  68. const projectGroups = { pm: { managerId: 'original-pm', billableTotal: 8, nonBillableTotal: 0 } };
  69. const projectOnly = createContext({ projectGroups, otherGroup: { otherRows: [], otherTotal: 0 } });
  70. projectOnly.fetchDeptManagerId = () => { throw new Error('纯项目补填无需获取其他工时主管'); };
  71. assert.equal((await methods.buildTimecardRepairRequest.call(projectOnly)).projectGroups, projectGroups);
  72. console.log('PASS 项目工时分组保持原样');
  73. const context = createContext();
  74. let submitted;
  75. context.getCallableDataSource = () => ({ load: async (payload) => {
  76. submitted = JSON.parse(payload.inputs).Body;
  77. return { data: { status: '审批中' } };
  78. } });
  79. const result = await methods.submitTimecardRepairRequest.call(context);
  80. assert.equal(submitted.otherGroup.managerId, '640891109');
  81. assert.equal(submitted.otherGroup.otherRows[0].activityName, 'Annual Leave');
  82. assert.equal(result.status, '审批中');
  83. console.log('PASS 连接器实际请求携带 Kevin ID');
  84. const failedContext = createContext();
  85. failedContext.fetchDeptManagerId = async () => { throw new Error('主管获取失败'); };
  86. failedContext.getCallableDataSource = () => ({ load: () => assert.fail('主管获取失败后不得提交') });
  87. await assert.rejects(methods.submitTimecardRepairRequest.call(failedContext), /主管获取失败/);
  88. console.log('PASS 主管获取失败阻止提交');
  89. }
  90. main().catch((error) => { console.error(error); process.exitCode = 1; });