mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 05:20:46 +08:00
124 lines
4.3 KiB
JavaScript
124 lines
4.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import React from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
|
|
import { LoopbackRecoveryNotice } from '../plugin-src/client/index.js';
|
|
import {
|
|
createLoopbackAwareRpcCall,
|
|
createLoopbackAwareRpcCalls,
|
|
createLoopbackRecovery,
|
|
LOOPBACK_RECOVERY_ERROR_CODE,
|
|
LOOPBACK_RECOVERY_ERROR_MESSAGE,
|
|
replacePageLocation,
|
|
} from '../plugin-src/client/loopback-recovery.js';
|
|
|
|
const WEIXIN_FORBIDDEN = new Error(
|
|
'transport failure for /weixin/connection.status: HTTP 403',
|
|
);
|
|
|
|
test('creates a localhost recovery target for the known loopback transport 403', () => {
|
|
const recovery = createLoopbackRecovery(WEIXIN_FORBIDDEN, {
|
|
href: 'http://127.0.0.1:3080/settings?tab=plugins#im',
|
|
});
|
|
|
|
assert.deepEqual(recovery, {
|
|
url: 'http://localhost:3080/settings?tab=plugins#im',
|
|
origin: 'http://localhost:3080',
|
|
});
|
|
assert.deepEqual(createLoopbackRecovery(WEIXIN_FORBIDDEN, {
|
|
href: 'http://127.8.9.10:4111/',
|
|
}), {
|
|
url: 'http://localhost:4111/',
|
|
origin: 'http://localhost:4111',
|
|
});
|
|
});
|
|
|
|
test('does not reinterpret unrelated 403s, protocols, or hosts', () => {
|
|
const cases = [
|
|
[new Error('transport failure for /weixin/connection.status: HTTP 404'), 'http://127.0.0.1:3080/'],
|
|
[new Error('request failed: HTTP 403'), 'http://127.0.0.1:3080/'],
|
|
[WEIXIN_FORBIDDEN, 'http://localhost:3080/'],
|
|
[WEIXIN_FORBIDDEN, 'http://192.168.1.20:3080/'],
|
|
[WEIXIN_FORBIDDEN, 'https://127.0.0.1:3080/'],
|
|
[WEIXIN_FORBIDDEN, 'not a URL'],
|
|
];
|
|
|
|
for (const [error, href] of cases) {
|
|
assert.equal(createLoopbackRecovery(error, { href }), null);
|
|
}
|
|
});
|
|
|
|
test('RPC wrapper reports the recovery and presents a safe actionable error', async () => {
|
|
const original = new Error(
|
|
'transport failure for /whatsapp/connection.status: HTTP 403',
|
|
);
|
|
let observed;
|
|
const rpcCall = createLoopbackAwareRpcCall(async () => {
|
|
throw original;
|
|
}, {
|
|
location: { href: 'http://127.0.0.1:3080/settings' },
|
|
onRecovery: (recovery) => { observed = recovery; },
|
|
});
|
|
|
|
await assert.rejects(rpcCall('connection.status', {}), (error) => {
|
|
assert.equal(error.code, LOOPBACK_RECOVERY_ERROR_CODE);
|
|
assert.equal(error.message, LOOPBACK_RECOVERY_ERROR_MESSAGE);
|
|
assert.equal(error.cause, original);
|
|
assert.equal(error.recoveryUrl, 'http://localhost:3080/settings');
|
|
return true;
|
|
});
|
|
assert.deepEqual(observed, {
|
|
url: 'http://localhost:3080/settings',
|
|
origin: 'http://localhost:3080',
|
|
});
|
|
});
|
|
|
|
test('RPC wrappers preserve successful results, unrelated errors, and missing optional calls', async () => {
|
|
const result = { ok: true, value: { connected: true } };
|
|
const successful = createLoopbackAwareRpcCall(async (...args) => ({ result, args }), {
|
|
location: { href: 'http://127.0.0.1:3080/' },
|
|
});
|
|
assert.deepEqual(await successful('connection.status', { botId: 'bot-1' }), {
|
|
result,
|
|
args: ['connection.status', { botId: 'bot-1' }],
|
|
});
|
|
|
|
const original = new Error('transport failure for /weixin/connection.status: HTTP 500');
|
|
const failing = createLoopbackAwareRpcCall(async () => { throw original; }, {
|
|
location: { href: 'http://127.0.0.1:3080/' },
|
|
});
|
|
await assert.rejects(failing(), (error) => error === original);
|
|
|
|
const calls = createLoopbackAwareRpcCalls({
|
|
weixinRpcCall: async () => result,
|
|
optionalRpcCall: undefined,
|
|
}, {
|
|
location: { href: 'http://127.0.0.1:3080/' },
|
|
});
|
|
assert.equal(calls.optionalRpcCall, undefined);
|
|
assert.equal(await calls.weixinRpcCall(), result);
|
|
});
|
|
|
|
test('recovery notice exposes one localized navigation action', () => {
|
|
const recovery = {
|
|
url: 'http://localhost:3080/settings?tab=plugins#im',
|
|
origin: 'http://localhost:3080',
|
|
};
|
|
const element = LoopbackRecoveryNotice({ recovery, onNavigate() {} });
|
|
const markup = renderToStaticMarkup(element);
|
|
|
|
assert.match(markup, /role="alert"/);
|
|
assert.match(markup, /请改用 localhost 重新打开/);
|
|
assert.match(markup, /http:\/\/localhost:3080/);
|
|
assert.match(markup, /<button[^>]*>使用 localhost 重新打开<\/button>/);
|
|
});
|
|
|
|
test('recovery navigation replaces the current history entry', () => {
|
|
let replacedWith;
|
|
replacePageLocation('http://localhost:3080/settings', {
|
|
replace(url) { replacedWith = url; },
|
|
});
|
|
assert.equal(replacedWith, 'http://localhost:3080/settings');
|
|
});
|