修复匹配详情中“用户自填未经核验”等来源说明,清理未参与项的橙色注意提示,修正贷款统计口径文案;补充公积金完整可选路径提取逻辑,确保明确无额外时长要求的公积金分支可正确识别并通过。
212 lines
14 KiB
JavaScript
212 lines
14 KiB
JavaScript
// Execute the actual UTS services and page scripts in memory. No app build, network or storage.
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const vm = require('node:vm');
|
|
const assert = require('node:assert/strict');
|
|
const ts = require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
|
|
class Json {
|
|
constructor(values = {}) { Object.assign(this, values); }
|
|
getString(k) { return typeof this[k] === 'string' ? this[k] : null; }
|
|
getNumber(k) { return typeof this[k] === 'number' ? this[k] : null; }
|
|
getBoolean(k) { return typeof this[k] === 'boolean' ? this[k] : null; }
|
|
getArray(k) { return Array.isArray(this[k]) ? this[k] : null; }
|
|
}
|
|
const root = path.join(__dirname, '..');
|
|
const modules = {};
|
|
const calls = [];
|
|
const activePages = [];
|
|
let response = new Json({ assessmentId: '1', revisionId: '2', submissionStatus: 'accepted' });
|
|
const session = new Json({ token: 'synthetic-zero-balance-owner' });
|
|
const api = {
|
|
getSession: () => session,
|
|
requestJson: async (...args) => { calls.push(args); return response; }
|
|
};
|
|
function compile(source, filename, extras = {}) {
|
|
const exports = {};
|
|
const compiled = ts.transpileModule(source, { reportDiagnostics: true, compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } });
|
|
assert.equal((compiled.diagnostics || []).filter(d => d.category === ts.DiagnosticCategory.Error).length, 0, 'UTS transpiles: ' + filename);
|
|
vm.runInNewContext(compiled.outputText, {
|
|
exports, UTSJSONObject: Json, Error, Number, Date, Math, Promise,
|
|
require: p => p.endsWith('.uvue') ? {} : p === '@/services/api.uts' || p === './api.uts' ? api :
|
|
p === '@/services/layout.uts' ? { usePageLayout: () => ({ pageStyle: () => '', contentStyle: () => '', bottomStyle: () => '' }) } :
|
|
p === '@/services/documents.uts' ? { fetchOwnedDocuments: async () => [], releaseDocumentTemp: () => {} } :
|
|
load(p.startsWith('@/') ? p.slice(2) : path.posix.normalize(path.posix.dirname(filename) + '/' + p)),
|
|
...extras
|
|
}, { filename });
|
|
return exports;
|
|
}
|
|
function load(filename) {
|
|
if (modules[filename]) return modules[filename];
|
|
const result = compile(fs.readFileSync(path.join(root, filename), 'utf8'), filename);
|
|
modules[filename] = result;
|
|
return result;
|
|
}
|
|
const service = load('services/assessment.uts');
|
|
const records = load('packages/finance/services/records.uts');
|
|
const flow = load('services/assessment-flow.uts');
|
|
function form(overrides = {}) {
|
|
return Object.assign(service.emptyAssessmentForm(), {
|
|
name: '合成用户', idCard: '110101199001010000', phone: '19900000009', city: '广东省', maritalStatus: '未婚',
|
|
occupation: '企业职员', company: '合成单位', monthlyIncome: '1', socialSecurity: '未缴纳', housingFund: '未缴纳',
|
|
house: '无', car: '无', creditLoanCount: '0', creditCardCount: '0', loanCountScope: 'all_loans'
|
|
}, overrides);
|
|
}
|
|
function save(value) { service.setAssessmentInput('personal', 2, value, [], [], false, false); }
|
|
function snapshot(value) { return JSON.stringify(value); }
|
|
function detail(formValues) {
|
|
return new Json({ assessmentId: '1', revisionId: '2', revisionNo: 1, type: 'personal', status: 'succeeded', moneyUnit: 'yuan', form: new Json(formValues) });
|
|
}
|
|
function page(kind) {
|
|
const filename = `packages/finance/${kind}/${kind}.uvue`;
|
|
const hooks = {};
|
|
const events = { toasts: [], navigation: [] };
|
|
const exposure = kind === 'assessment' ? '{form,step,restore,next,validateStep,confirmLoanCountScope:typeof confirmLoanCountScope === "undefined" ? null : confirmLoanCountScope}' : '{payload,sections,submit,loadInput}';
|
|
const source = fs.readFileSync(path.join(root, filename), 'utf8').match(/<script setup lang="uts">([\s\S]*?)<\/script>/)[1];
|
|
const result = compile(source + '\nexports.page=' + exposure, filename, {
|
|
ref: value => ({ value }), computed: fn => ({ get value() { return fn(); } }),
|
|
onLoad: fn => { hooks.load = fn; }, onShow: fn => { hooks.show = fn; },
|
|
onHide: fn => { hooks.hide = fn; }, onUnload: fn => { hooks.unload = fn; }, onBackPress: () => {},
|
|
getCurrentPages: () => [], setInterval: () => 1, clearInterval: () => {},
|
|
uni: { showToast: value => events.toasts.push(value.title), navigateTo: value => events.navigation.push(value.url),
|
|
redirectTo: value => events.navigation.push(value.url), reLaunch: value => events.navigation.push(value.url) }
|
|
});
|
|
if (kind === 'assessment') { hooks.load({}); hooks.show(); }
|
|
else hooks.show();
|
|
activePages.push(hooks);
|
|
return { ...result.page, events };
|
|
}
|
|
let checks = 0, failures = 0;
|
|
async function test(name, callback) {
|
|
activePages.splice(0).forEach(hooks => { hooks.hide?.(); });
|
|
flow.clearAssessmentFlow(); calls.length = 0;
|
|
try { await callback(); checks++; console.log('PASS ' + name); }
|
|
catch (error) { failures++; console.error('FAIL ' + name + '\n' + error.stack); }
|
|
}
|
|
(async () => {
|
|
await test('zero counts make only empty related amounts not applicable without changing input', () => {
|
|
const value = form(); const before = snapshot(value); save(value);
|
|
const progress = service.assessmentProgress('personal');
|
|
assert.equal(progress.assetsDone, true, 'empty balances for explicitly absent loans/cards are complete');
|
|
assert.equal(progress.percent, 100);
|
|
assert.equal(snapshot(value), before);
|
|
for (const key of ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit'])
|
|
assert.equal(service.getAssessmentInput('personal')[key], '');
|
|
});
|
|
await test('missing counts and positive counts never infer zero balances', () => {
|
|
for (const value of [form({ creditLoanCount: '', creditCardCount: '' }), form({ creditLoanCount: '1', creditCardCount: '1' })]) {
|
|
save(value); const progress = service.assessmentProgress('personal');
|
|
assert.equal(progress.assetsDone, false); assert.ok(progress.percent < 100);
|
|
}
|
|
});
|
|
await test('all accepted zero-count spellings share the same non-applicable behavior', () => {
|
|
for (const count of ['0', '00', '000', '0000']) {
|
|
const value = form({ creditLoanCount: count, creditCardCount: count }); save(value);
|
|
assert.equal(service.assessmentProgress('personal').assetsDone, true);
|
|
assert.equal(service.assessmentProgress('personal').percent, 100);
|
|
assert.equal(service.getAssessmentInput('personal').creditLoanCount, count);
|
|
}
|
|
});
|
|
await test('legacy credit-loan zero never receives the new loan scope during restoration', () => {
|
|
const saved = new Json({ ...form(), loanCountScope: '' });
|
|
const restored = service.formFromInput(saved); save(restored);
|
|
assert.equal(restored.creditLoanCount, '0'); assert.equal(restored.loanCountScope, '');
|
|
assert.equal(service.assessmentProgress('personal').assetsDone, false);
|
|
assert.ok(service.assetValidationMessage(restored).includes('重新确认'));
|
|
});
|
|
await test('zero count and positive related amounts are rejected without overwriting', () => {
|
|
for (const [key, expected] of [['totalDebtBalance', '贷款笔数为 0 时,欠款总余额和贷款月还款金额不能大于 0'],
|
|
['monthlyLoanPayment', '贷款笔数为 0 时,欠款总余额和贷款月还款金额不能大于 0'],
|
|
['creditCardTotalLimit', '信用卡张数为 0 时,总额度和已使用总额度不能大于 0'],
|
|
['creditCardUsedLimit', '信用卡张数为 0 时,总额度和已使用总额度不能大于 0']]) {
|
|
const value = form({ [key]: '0.000001' }); const before = snapshot(value);
|
|
assert.equal(service.assetValidationMessage(value), expected); save(value);
|
|
assert.equal(service.assessmentProgress('personal').assetsDone, false);
|
|
assert.equal(snapshot(value), before); assert.equal(service.getAssessmentInput('personal')[key], '0.000001');
|
|
}
|
|
});
|
|
await test('invalid amounts cannot grant a zero-count exemption to sibling empty amounts', () => {
|
|
for (const key of ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']) {
|
|
const value = form({ [key]: 'bad' }); save(value);
|
|
assert.ok(service.assetValidationMessage(value).length > 0);
|
|
assert.equal(service.assessmentProgress('personal').assetsDone, false);
|
|
assert.equal(service.getAssessmentInput('personal')[key], 'bad');
|
|
}
|
|
});
|
|
await test('entered zero money stays entered and legacy loan scope is not inferred from zero values', () => {
|
|
const value = form({ totalDebtBalance: '0.000000', monthlyLoanPayment: '0.00', creditCardTotalLimit: '0', creditCardUsedLimit: '0.000' });
|
|
const before = snapshot(value); save(value); assert.equal(service.assessmentProgress('personal').assetsDone, true);
|
|
assert.equal(service.assetValidationMessage(value), ''); assert.equal(snapshot(value), before);
|
|
const old = service.formFromInput(new Json({ ...value, loanCountScope: '' }));
|
|
assert.equal(old.loanCountScope, ''); assert.ok(service.assetValidationMessage(old).length > 0);
|
|
});
|
|
await test('new submission carries all-loan scope while empty monetary fields remain null', async () => {
|
|
const value = new Json({ ...form(), type: 'personal', documents: [], authorizations: [] });
|
|
const before = snapshot(value); await records.createAssessment(value);
|
|
const body = calls.at(-1)[2]; assert.equal(body.form.loanCountScope, 'all_loans');
|
|
assert.equal(body.form.creditLoanCount, 0); assert.equal(body.form.creditCardCount, 0);
|
|
for (const key of ['totalDebtBalance', 'monthlyLoanPayment', 'creditCardTotalLimit', 'creditCardUsedLimit']) assert.equal(body.form[key], null);
|
|
assert.equal(snapshot(value), before);
|
|
});
|
|
await test('serialization preserves entered amounts and never fabricates scope for legacy callers', async () => {
|
|
const value = new Json({ ...form({ loanCountScope: '', creditLoanCount: '2', creditCardCount: '1', totalDebtBalance: '1.234567', monthlyLoanPayment: '0', creditCardTotalLimit: '2', creditCardUsedLimit: '0.123456' }), type: 'personal' });
|
|
const before = snapshot(value); await records.createAssessment(value);
|
|
const body = calls.at(-1)[2]; assert.equal(body.form.loanCountScope, undefined);
|
|
assert.equal(body.form.totalDebtBalance, '12345.67'); assert.equal(body.form.monthlyLoanPayment, '0.00');
|
|
assert.equal(body.form.creditCardTotalLimit, '20000.00'); assert.equal(body.form.creditCardUsedLimit, '1234.56');
|
|
assert.equal(snapshot(value), before);
|
|
});
|
|
await test('edit restoration preserves old and new scope and every existing amount', async () => {
|
|
for (const scope of [undefined, 'all_loans']) {
|
|
response = detail({ creditLoanCount: 0, loanCountScope: scope, creditCardCount: 0, totalDebtBalance: '12000.00', monthlyLoanPayment: null, creditCardTotalLimit: '0.00', creditCardUsedLimit: null });
|
|
const restored = await records.fetchAssessmentEdit('1');
|
|
assert.equal(restored.loanCountScope || '', scope || ''); assert.equal(restored.creditLoanCount, '0');
|
|
assert.equal(restored.totalDebtBalance, '1.2'); assert.equal(restored.monthlyLoanPayment, '');
|
|
assert.equal(restored.creditCardTotalLimit, '0'); assert.equal(restored.creditCardUsedLimit, '');
|
|
}
|
|
});
|
|
await test('archive labels retain historical credit-loan meaning and name new scope correctly', async () => {
|
|
for (const [scope, label] of [[undefined, '信用贷款笔数(旧口径)'], ['all_loans', '贷款笔数']]) {
|
|
response = detail({ creditLoanCount: 0, loanCountScope: scope });
|
|
const archived = await records.fetchAssessmentDetail('1', '2');
|
|
assert.ok(archived.fields.some(item => item.label === label && item.value === '0'));
|
|
}
|
|
});
|
|
await test('historical count requires an explicit page confirmation without clearing any values', () => {
|
|
const current = page('assessment');
|
|
const old = form({ loanCountScope: '', totalDebtBalance: '1.2', monthlyLoanPayment: '0.03' });
|
|
save(old); current.restore('personal'); current.step.value = 2;
|
|
const before = snapshot(current.form.value); current.next();
|
|
assert.equal(current.step.value, 2); assert.ok(current.events.toasts.at(-1).includes('重新确认'));
|
|
assert.equal(typeof current.confirmLoanCountScope, 'function'); current.confirmLoanCountScope();
|
|
const after = JSON.parse(snapshot(current.form.value)); after.loanCountScope = '';
|
|
assert.equal(JSON.stringify(after), before); assert.equal(current.form.value.loanCountScope, 'all_loans');
|
|
current.next(); assert.equal(current.step.value, 2, 'positive money conflict remains after confirming zero loans');
|
|
});
|
|
await test('page confirmation enables blank balances for old zero counts and remembers scope', () => {
|
|
const current = page('assessment'); save(form({ loanCountScope: '' })); current.restore('personal'); current.step.value = 2;
|
|
current.next(); assert.equal(current.step.value, 2);
|
|
current.confirmLoanCountScope(); current.next(); assert.equal(current.step.value, 3);
|
|
assert.equal(service.getAssessmentInput('personal').loanCountScope, 'all_loans');
|
|
assert.equal(service.getAssessmentInput('personal').totalDebtBalance, '');
|
|
});
|
|
await test('confirmation page refuses legacy scope and displays submitted scope without reinterpreting', async () => {
|
|
save(form({ loanCountScope: '' })); let current = page('confirm');
|
|
const before = calls.length; await current.submit(); assert.equal(calls.length, before);
|
|
assert.ok(current.events.toasts.at(-1).includes('重新确认'));
|
|
assert.equal(service.getAssessmentInput('personal').step, 2);
|
|
save(form()); current = page('confirm');
|
|
assert.ok(current.sections.value[2].rows.some(row => row.label === '贷款笔数'));
|
|
assert.ok(current.sections.value[2].rows.some(row => row.value.includes('房贷') && row.value.includes('车贷')));
|
|
});
|
|
await test('confirmation distinguishes exempt empty amounts from entered zero amounts', () => {
|
|
save(form()); let current = page('confirm'); let rows = current.sections.value[2].rows;
|
|
assert.equal(rows.find(row => row.label === '欠款总余额').value, '不适用(无贷款)');
|
|
assert.equal(rows.find(row => row.label === '总额度').value, '不适用(无信用卡)');
|
|
save(form({ totalDebtBalance: '0.000000', creditCardTotalLimit: '0.00' })); current = page('confirm'); rows = current.sections.value[2].rows;
|
|
assert.equal(rows.find(row => row.label === '欠款总余额').value, '0.000000 万元');
|
|
assert.equal(rows.find(row => row.label === '总额度').value, '0.00 万元');
|
|
});
|
|
console.log((failures ? 'FAIL' : 'PASS') + ': ' + checks + ' passed, ' + failures + ' failed zero-balance UTS/page scenarios; modelCalls=0 businessWrites=0 networkCalls=0');
|
|
if (failures) process.exitCode = 1;
|
|
})().catch(error => { console.error(error.stack); process.exitCode = 1; });
|