mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 01:53:21 +08:00
Localize WhatsApp access-grant approvals via t() and push Host language from the UI on load and locale change. Co-authored-by: Cursor <cursoragent@cursor.com>
99 lines
4.2 KiB
JavaScript
99 lines
4.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createUpdateRpcHandler, installUpdateRpc, HOST_LANGUAGE_SET_ENDPOINT } from '../plugin-src/host/update-rpc.mjs';
|
|
import { createImHostPlugin } from '../plugin-src/host/index.mjs';
|
|
import { getImHostLanguage, setImHostLanguage } from '../src/channels/shared/i18n.mjs';
|
|
|
|
test('update RPC rejects arbitrary commands, paths, profiles, sources and invalid request identifiers', async () => {
|
|
const calls = [];
|
|
const service = Object.fromEntries(['status', 'check', 'install'].map((key) => [key, async (value) => calls.push([key, value])]));
|
|
const handle = createUpdateRpcHandler(service);
|
|
for (const [endpoint, payload] of [
|
|
['unknown', {}], ['update.check', { registry: 'https://evil.test/' }],
|
|
['update.status', { profile: '../other' }], ['update.install', null],
|
|
['update.install', { checkId: 'id', requestId: 'request', command: 'anything' }],
|
|
['update.install', { checkId: 'id', requestId: '; rm -rf /' }],
|
|
['update.install', { checkId: 'id', requestId: 'request', version: '9.0.0' }],
|
|
[HOST_LANGUAGE_SET_ENDPOINT, {}],
|
|
[HOST_LANGUAGE_SET_ENDPOINT, { language: 'en', extra: true }],
|
|
[HOST_LANGUAGE_SET_ENDPOINT, { language: '' }],
|
|
]) {
|
|
assert.equal((await handle(endpoint, payload)).error.code, 'bad-request');
|
|
}
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('host.language.set follows DSH locale ids and returns the normalized host language', async () => {
|
|
const handle = createUpdateRpcHandler({});
|
|
setImHostLanguage('zh');
|
|
try {
|
|
assert.deepEqual(await handle(HOST_LANGUAGE_SET_ENDPOINT, { language: 'en' }), {
|
|
ok: true, value: { language: 'en' },
|
|
});
|
|
assert.equal(getImHostLanguage(), 'en');
|
|
assert.deepEqual(await handle(HOST_LANGUAGE_SET_ENDPOINT, { language: 'zh-CN' }), {
|
|
ok: true, value: { language: 'zh' },
|
|
});
|
|
assert.equal(getImHostLanguage(), 'zh');
|
|
} finally {
|
|
setImHostLanguage('zh');
|
|
}
|
|
});
|
|
|
|
test('update RPC mounts on /dsh-im through webServer', () => {
|
|
const routes = [];
|
|
const service = { close: async () => {} };
|
|
const ctx = {
|
|
connection: {
|
|
requestRejection: () => undefined,
|
|
},
|
|
webServer: {
|
|
register: (route) => {
|
|
routes.push(route);
|
|
return () => {};
|
|
},
|
|
},
|
|
effect: (factory) => {
|
|
const dispose = factory();
|
|
return typeof dispose === 'function' ? dispose : () => {};
|
|
},
|
|
};
|
|
installUpdateRpc(ctx, { service, runtime: {} });
|
|
assert.equal(routes[0]?.path, '/dsh-im');
|
|
assert.equal(routes[0]?.kind, 'prefix');
|
|
});
|
|
|
|
test('update RPC returns only safe codes for unanticipated runtime errors', async () => {
|
|
const handle = createUpdateRpcHandler({ status: async () => {
|
|
throw new Error('token=secret /Users/private/profile');
|
|
} });
|
|
assert.deepEqual(await handle('update.status', {}), {
|
|
ok: false, error: { code: 'update-failed', message: 'update-failed' },
|
|
});
|
|
});
|
|
|
|
test('aborting a submitted browser request does not cancel the Host installation', async () => {
|
|
const abort = new AbortController();
|
|
let complete;
|
|
const pending = new Promise((resolve) => { complete = resolve; });
|
|
const handle = createUpdateRpcHandler({ install: async () => pending });
|
|
const result = handle('update.install', { checkId: 'confirmed', requestId: 'request' }, abort.signal);
|
|
abort.abort();
|
|
complete({ job: { state: 'installing' } });
|
|
assert.deepEqual(await result, { ok: true, value: { job: { state: 'installing' } } });
|
|
});
|
|
|
|
test('Host update initialization failure leaves all channel activations available', async () => {
|
|
const calls = [];
|
|
const channels = ['Feishu', 'Weixin', 'Dingtalk', 'Wecom', 'Qq', 'Slack', 'Telegram', 'Discord', 'Whatsapp', 'Office'];
|
|
const internals = Object.fromEntries(channels.map((channel) => [`apply${channel}`, async () => calls.push(channel)]));
|
|
internals.installUpdateRpc = () => { throw new Error('updater unavailable'); };
|
|
internals.installDeliveryRpc = () => {};
|
|
const errors = [];
|
|
await createImHostPlugin(internals).apply({
|
|
connection: { rpc: {} }, logger: { error: (...args) => errors.push(args) },
|
|
});
|
|
assert.deepEqual(calls, channels);
|
|
assert.equal(errors.length, 1);
|
|
assert.match(errors[0][0], /update management/);
|
|
});
|