| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- 'use strict';
- const assert = require('node:assert/strict');
- const fs = require('node:fs');
- const path = require('node:path');
- const vm = require('node:vm');
- const babel = require('/Users/malk/.nvm/versions/node/v22.12.0/lib/node_modules/openyida/node_modules/@babel/standalone');
- const helperNames = ['closingNativeMonthText', 'closingNativeTimestamp', 'closingNativeRecordData',
- 'closingNativeRecordMonth', 'closingNativePage', 'summarizeNativeMonthClosing', 'queryNativeMonthClosingStatus'];
- const APP_TYPE = 'APP_ZQ3I7XO2RSHDJ4QDEVNB';
- /** @param {string} source @returns {Map<string,string>} Exact exported function bodies. */
- function functions(source) {
- const ast = babel.transform(source, { ast: true, code: false, parserOpts: { sourceType: 'module', plugins: ['jsx'] } }).ast;
- return new Map(ast.program.body.filter(node => node.type === 'ExportNamedDeclaration'
- && node.declaration && node.declaration.type === 'FunctionDeclaration')
- .map(node => [node.declaration.id.name, source.slice(node.start, node.end)]));
- }
- /** @param {string} source @returns {Object} Isolated frontend methods. */
- function methods(source) {
- const exported = functions(source);
- const names = helperNames.concat(['loadMonthClosingStatus', 'isMonthLocked', 'isRepairMonthBlocked', 'isCurrentMonthBlocked'])
- .filter(name => exported.has(name));
- const code = names.map(name => exported.get(name).replace(/^export /, '')).join('\n');
- return vm.runInNewContext(code + '\n({' + names.join(',') + '})', { APP_TYPE });
- }
- /** @param {Object} data @returns {*} Plain JSON for cross-context assertions. */
- function plain(data) { return JSON.parse(JSON.stringify(data)); }
- /** @param {string} id @param {string} status @param {string} action @param {number} time @param {string} approved @returns {Object} Fixture. */
- function record(id, status = 'COMPLETED', action = '封账', time = 1790000000000, approved = 'agree') {
- return { processInstanceId: id, instanceStatus: status, approvedResult: approved, gmtModified: time,
- data: { dateField_mt6wzb17: 1785513600000, radioField_mt6wzb18: action } };
- }
- /** @param {Object} m @param {Array<*>} replies @returns {Object} Native API stub and call log. */
- function context(m, replies) {
- const calls = [];
- const obj = Object.assign({}, m, { getCurrentBusinessMonthText: () => '202610', calls,
- utils: { yida: { getProcessInstances: async params => {
- calls.push(params);
- const reply = replies[Math.min(calls.length - 1, replies.length - 1)];
- if (reply instanceof Error) throw reply;
- return reply;
- } } }, getCallableDataSource: () => { throw new Error('不得调用旧封账连接器'); } });
- return obj;
- }
- /** @param {string} filename @returns {Promise<void>} Exercise state, paging and failure contract. */
- async function verify(filename) {
- const source = fs.readFileSync(filename, 'utf8');
- const m = methods(source);
- const count = record('closed');
- const ctx = context(m, [{ success: true, content: { data: [count], totalCount: 1 } }]);
- const closed = plain(await ctx.loadMonthClosingStatus(2026, 8));
- assert.deepEqual(closed, { monthText: '202608', closed: true, currentStatus: '封账',
- approvedClosingCount: 1, approvedReopeningCount: 0, runningClosingCount: 0 });
- const params = ctx.calls[0];
- assert.equal(params.pageSize, 100);
- assert.equal(params.formUuid, 'FORM-275BDAFE67FE4226B6BCF2BC6EEFFD4B4215');
- assert.deepEqual(JSON.parse(params.searchFieldJson)[0].value, [1785513600000, 1788191999999]);
- const empty = plain(await context(m, [{ data: [], totalCount: 0 }]).loadMonthClosingStatus('2026', '10'));
- assert.equal(empty.closed, false);
- assert.equal(empty.monthText, '202610');
- const running = ctx.summarizeNativeMonthClosing('202608', [record('run', 'RUNNING', '启封')]);
- assert.equal(running.runningClosingCount, 1);
- assert.equal(running.closed, false);
- const reopened = ctx.summarizeNativeMonthClosing('202608', [count, record('open', 'COMPLETED', '启封', count.gmtModified + 1)]);
- assert.equal(reopened.closed, false);
- assert.equal(reopened.approvedClosingCount, 1);
- assert.equal(reopened.approvedReopeningCount, 1);
- const closedAgain = ctx.summarizeNativeMonthClosing('202608', [count, record('open', 'COMPLETED', '启封', count.gmtModified + 1),
- record('closed-again', 'COMPLETED', '封账', count.gmtModified + 2)]);
- assert.equal(closedAgain.closed, true);
- const ignored = ctx.summarizeNativeMonthClosing('202608', [record('rejected', 'COMPLETED', '封账', undefined, 'disagree'),
- record('terminated', 'TERMINATED'), record('error', 'ERROR')]);
- assert.equal(ignored.approvedClosingCount, 0);
- assert.equal(ignored.closed, false);
- const otherMonth = record('other'); otherMonth.data.dateField_mt6wzb17 = 1788192000000;
- assert.equal(ctx.summarizeNativeMonthClosing('202608', [otherMonth]).approvedClosingCount, 0);
- assert.equal(ctx.closingNativeTimestamp('1790000000'), 1790000000000);
- assert.equal(ctx.closingNativeTimestamp('2026-08-01T00:00:00+08:00'), 1785513600000);
- assert.equal(ctx.closingNativeRecordMonth('20260801'), '202608');
- const priority = record('priority'); priority.modifiedTimeGMT = count.gmtModified - 1;
- assert.equal(ctx.summarizeNativeMonthClosing('202608', [record('open-first', 'COMPLETED', '启封', count.gmtModified), priority]).closed, false);
- const tied = ctx.summarizeNativeMonthClosing('202608', [count, record('tie-open', 'COMPLETED', '启封', count.gmtModified)]);
- assert.equal(tied.closed, true, '与后端一致,时间相等不覆盖先出现动作');
- const firstPage = Array.from({ length: 100 }, (_, index) => record('p-' + index));
- const paged = context(m, [{ data: firstPage, totalCount: 101 }, { data: [record('last')], totalCount: 101 }]);
- assert.equal((await paged.loadMonthClosingStatus(2026, 8)).approvedClosingCount, 101);
- assert.equal(paged.calls.length, 2);
- for (const reply of [null, {}, { success: false, data: [], totalCount: 0 }, { errorCode: 'NO_PERMISSION' },
- { data: [] }, { data: [], totalCount: null }, { data: [], totalCount: -1 }, new Error('timeout')]) {
- await assert.rejects(context(m, [reply]).loadMonthClosingStatus(2026, 8));
- }
- await assert.rejects(context(m, [{ data: firstPage, totalCount: 101 }, { data: [], totalCount: 101 }]).loadMonthClosingStatus(2026, 8));
- await assert.rejects(context(m, [{ data: firstPage, totalCount: 101 }, { data: [count], totalCount: 102 }]).loadMonthClosingStatus(2026, 8));
- await assert.rejects(context(m, [{ data: [count, count], totalCount: 2 }]).loadMonthClosingStatus(2026, 8));
- await assert.rejects(context(m, [{ data: [{ processInstanceId: 'hidden', data: {} }], totalCount: 1 }]).loadMonthClosingStatus(2026, 8));
- await assert.rejects(context(m, [{ data: [], totalCount: 0 }]).loadMonthClosingStatus(2026, 13));
- const failed = context(m, [new Error('timeout'), { data: [], totalCount: 0 }]);
- await assert.rejects(failed.loadMonthClosingStatus(2026, 10));
- assert.equal((await failed.loadMonthClosingStatus(2026, 10)).closed, false, '失败不阻断后续串行队列');
- const openCurrent = Object.assign({}, empty, { monthText: '202610' });
- if (m.isMonthLocked) { assert.equal(ctx.isMonthLocked(closed), true); assert.equal(ctx.isMonthLocked(running), true); assert.equal(ctx.isMonthLocked(empty), false); }
- if (m.isCurrentMonthBlocked) { assert.equal(ctx.isCurrentMonthBlocked(openCurrent), false); assert.equal(ctx.isCurrentMonthBlocked(closed), true); }
- if (m.isRepairMonthBlocked) {
- assert.equal(ctx.isRepairMonthBlocked(closed), false);
- assert.equal(ctx.isRepairMonthBlocked(Object.assign({}, running, { monthText: '202608' })), false);
- assert.equal(ctx.isRepairMonthBlocked(openCurrent), filename.includes('change') ? false : true);
- }
- process.stdout.write('PASS 原生封账契约、状态归约、分页、异常保护、原准入规则:' + path.basename(filename) + '\n');
- }
- /** @returns {Promise<void>} Verify requested sources. */
- async function main() {
- const files = process.argv.slice(2);
- if (!files.length) files.push(...['timecard-repair-lifecycle.oyd.jsx', 'timecard-repair.oyd.jsx', 'timecard-change.oyd.jsx']
- .map(name => path.resolve(__dirname, '../src', name)));
- for (const file of files) await verify(file);
- }
- main().catch(error => { console.error(error); process.exitCode = 1; });
|