109 lines
4.8 KiB
JavaScript
109 lines
4.8 KiB
JavaScript
// Exercises the actual shared UTS logic as JS, not a duplicate implementation.
|
|
// This does not replace Kotlin/Swift native compilation or device tests.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const vm = require('node:vm');
|
|
const hx = process.env.HBUILDERX_HOME;
|
|
assert.ok(hx, 'Set HBUILDERX_HOME to the HBuilderX installation directory');
|
|
const ts = require(path.join(hx, 'plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js'));
|
|
const source = fs.readFileSync(path.join(__dirname, '../uni_modules/tb-douyin-pay/utssdk/common.uts'), 'utf8');
|
|
const compiled = ts.transpileModule(source, {
|
|
compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS },
|
|
}).outputText;
|
|
const info = { appid: 'app', partnerid: 'merchant', prepayid: 'prepay', package: 'Sign=DYPay',
|
|
noncestr: 'nonce', timestamp: '1780000000', sign: 'signed+value/==' };
|
|
function load(stringify = JSON.stringify) {
|
|
const timers = new Map();
|
|
let timerId = 0;
|
|
const exports = {};
|
|
const ctx = { exports, JSON: { stringify, parseObject(raw) {
|
|
const value = JSON.parse(raw);
|
|
return value == null ? null : { getString: key => typeof value[key] === 'string' ? value[key] : null };
|
|
} }, setTimeout(fn) { timers.set(++timerId, fn); return timerId; },
|
|
clearTimeout(id) { timers.delete(id); } };
|
|
vm.runInNewContext(compiled, ctx);
|
|
assert.equal(exports.configure('app'), true);
|
|
return { ...exports, timers };
|
|
}
|
|
|
|
test('shared bridge keeps signed fields unchanged and handles default/explicit loading', () => {
|
|
for (const showLoading of [undefined, null, true, false]) {
|
|
const bridge = load();
|
|
const results = [];
|
|
bridge.invoke({ payInfo: info, showLoading }, result => results.push(result), (payload, loading, done) => {
|
|
assert.deepEqual(JSON.parse(payload), info);
|
|
assert.equal(loading, showLoading ?? true);
|
|
done('{"resultCode":"0","errorMsg":""}');
|
|
});
|
|
assert.equal(results.length, 1);
|
|
assert.equal(results[0].resultCode, '0');
|
|
assert.equal(results[0].needsQuery, true);
|
|
assert.equal(bridge.timers.size, 0);
|
|
}
|
|
});
|
|
|
|
test('missing/null/empty payment fields never reach native code or take a lock', () => {
|
|
const invalid = [null, undefined, {}, { ...info, appid: 'wrong' },
|
|
{ ...info, package: 'wrong' }, { ...info, timestamp: '1780000000000' },
|
|
{ ...info, noncestr: 'n'.repeat(33) }];
|
|
for (const key of Object.keys(info)) {
|
|
for (const value of [null, undefined, '', ' ']) invalid.push({ ...info, [key]: value });
|
|
}
|
|
for (const payInfo of invalid) {
|
|
const bridge = load();
|
|
const results = [];
|
|
bridge.invoke({ payInfo }, r => results.push(r), () => assert.fail('Invalid native call'));
|
|
assert.equal(results.length, 1);
|
|
assert.equal(results[0].resultCode, '-1');
|
|
assert.equal(bridge.timers.size, 0);
|
|
assert.equal(bridge.configure('app'), true);
|
|
}
|
|
});
|
|
|
|
test('null or failed serialization returns an error before the lock and native call', () => {
|
|
for (const stringify of [() => null, () => undefined, () => '', () => { throw Error('serialize'); }]) {
|
|
const bridge = load(stringify);
|
|
bridge.invoke({ payInfo: info }, r => assert.equal(r.resultCode, '-1'), () => assert.fail('Invalid payload'));
|
|
assert.equal(bridge.timers.size, 0);
|
|
assert.equal(bridge.configure('app'), true);
|
|
}
|
|
});
|
|
|
|
test('reentrant calls, duplicate callbacks and late callbacks after reset are isolated', () => {
|
|
const bridge = load();
|
|
const results = [];
|
|
let oldDone;
|
|
bridge.invoke({ payInfo: info }, r => results.push(r), (_, __, done) => { oldDone = done; });
|
|
bridge.invoke({ payInfo: info }, r => assert.equal(r.resultCode, '103'), () => assert.fail('Duplicate pay'));
|
|
assert.equal(bridge.configure('other'), false);
|
|
bridge.resetPending();
|
|
let newDone;
|
|
bridge.invoke({ payInfo: info }, r => results.push(r), (_, __, done) => { newDone = done; });
|
|
oldDone('{"resultCode":"0"}');
|
|
assert.equal(results.length, 0);
|
|
newDone('{"resultCode":"1"}');
|
|
newDone('{"resultCode":"0"}');
|
|
assert.equal(results.length, 1);
|
|
assert.equal(results[0].resultCode, '1');
|
|
for (const timer of bridge.timers.values()) timer();
|
|
assert.equal(results.length, 1);
|
|
});
|
|
|
|
test('timeout, malformed result and native exception remain query-required and unlock', () => {
|
|
for (const mode of ['timeout', 'malformed', 'throw']) {
|
|
const bridge = load();
|
|
const results = [];
|
|
bridge.invoke({ payInfo: info }, r => results.push(r), (_, __, done) => {
|
|
if (mode === 'throw') throw Error('native');
|
|
if (mode === 'malformed') done('{');
|
|
});
|
|
if (mode === 'timeout') for (const timer of bridge.timers.values()) timer();
|
|
assert.equal(results.length, 1);
|
|
assert.notEqual(results[0].resultCode, '0');
|
|
assert.equal(results[0].needsQuery, true);
|
|
assert.equal(bridge.configure('app'), true);
|
|
}
|
|
});
|