dsh-im-ops/test/channels/shared/access-grant.test.mjs
oliver 45f430fb28 Sync bot chat language with DSH system locale.
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>
2026-09-14 22:38:16 +08:00

285 lines
9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict';
import test from 'node:test';
import {
approverPhonesForPending,
emptyAccessGrant,
enqueueAccessPending,
ensureOwnerGlobalAdmin,
evaluateAccessGrant,
findPendingByNotifyMessageId,
formatPendingNotifyBody,
migrateAccessPolicyToGrant,
normalizeAccessPhone,
parseApprovalIntent,
parsePendingIdFromNotifyText,
resolveAccessPending,
upsertAccessContact,
resolveAccessAgentPreset,
validateAccessGrant,
} from '../../../src/channels/shared/access-grant.mjs';
const GROUP_A = '120363111111111111@g.us';
const GROUP_B = '120363222222222222@g.us';
function grant(overrides = {}) {
return validateAccessGrant({
version: 1,
globalAdmins: ['8618111111111'],
directMembers: [],
groups: {},
pending: [],
contacts: [],
...overrides,
});
}
test('ACCESS_GRANT_GROUP_JID_PATTERN accepts legacy hyphen group JIDs', () => {
const legacy = '123456789-120363000000000000@g.us';
assert.doesNotThrow(() => validateAccessGrant({
...emptyAccessGrant(),
globalAdmins: ['8618111111111'],
groups: {
[legacy]: { admins: [], members: [] },
},
}));
});
test('normalizeAccessPhone accepts E.164, JIDs, and rejects LID servers', () => {
assert.equal(normalizeAccessPhone('+86 181-4238-7786'), '8618142387786');
assert.equal(normalizeAccessPhone('8618142387786@s.whatsapp.net'), '8618142387786');
assert.equal(normalizeAccessPhone('12345:12@c.us'), '12345');
assert.equal(normalizeAccessPhone('123456789012345@lid'), null);
assert.equal(normalizeAccessPhone('12'), null);
});
test('ensureOwnerGlobalAdmin seeds linked account and refuses empty admins on validate', () => {
const seeded = ensureOwnerGlobalAdmin(null, '8618111111111@s.whatsapp.net');
assert.deepEqual(seeded.globalAdmins, ['8618111111111']);
assert.throws(
() => validateAccessGrant({ ...emptyAccessGrant(), globalAdmins: [] }),
/全局管理员/,
);
});
test('migrateAccessPolicyToGrant copies allowlist phones into directMembers only', () => {
const migrated = migrateAccessPolicyToGrant({
direct: { allowlist: { users: [{ id: '8618222222222@s.whatsapp.net', canExecuteCommands: true }] } },
group: { allowlist: { users: [{ id: '+86 183-3333-3333', canExecuteCommands: false }] } },
}, null, '8618111111111');
assert.deepEqual(migrated.globalAdmins, ['8618111111111']);
assert.deepEqual(
migrated.directMembers.map((m) => m.phone).sort(),
['8618222222222', '8618333333333'],
);
assert.equal(Object.keys(migrated.groups).length, 0);
});
test('evaluateAccessGrant keeps DM and group scopes isolated', () => {
const doc = grant({
directMembers: [{ phone: '8618222222222', canExecuteCommands: true }],
groups: {
[GROUP_A]: {
admins: ['8618333333333'],
members: [{ phone: '8618444444444', canExecuteCommands: false }],
},
},
});
assert.equal(evaluateAccessGrant(doc, {
scene: 'direct', phone: '8618111111111',
}).reason, 'global-admin');
assert.equal(evaluateAccessGrant(doc, {
scene: 'direct', phone: '8618222222222',
}).allowed, true);
assert.equal(evaluateAccessGrant(doc, {
scene: 'direct', phone: '8618333333333',
}).allowed, false);
assert.equal(evaluateAccessGrant(doc, {
scene: 'group', phone: '8618444444444', groupJid: GROUP_A,
}).reason, 'group-member');
assert.equal(evaluateAccessGrant(doc, {
scene: 'group', phone: '8618444444444', groupJid: GROUP_B,
}).allowed, false);
assert.equal(evaluateAccessGrant(doc, {
scene: 'group', phone: '8618222222222', groupJid: GROUP_A,
}).allowed, false);
assert.equal(evaluateAccessGrant(doc, {
scene: 'group',
phone: '8618444444444',
groupJid: GROUP_A,
isCommand: true,
}).reason, 'command-not-allowed');
});
test('pending notify falls back to global admins when group has no admins', () => {
const doc = grant({
groups: {
[GROUP_A]: { admins: [], members: [] },
},
});
assert.deepEqual(
approverPhonesForPending(doc, { kind: 'group', groupJid: GROUP_A }),
['8618111111111'],
);
assert.deepEqual(
approverPhonesForPending(grant({
groups: { [GROUP_A]: { admins: ['8618555555555'], members: [] } },
}), { kind: 'group', groupJid: GROUP_A }),
['8618555555555'],
);
});
test('enqueue + approve places member into the matching grant bucket', () => {
let doc = grant();
const { grant: withPending, pending, created } = enqueueAccessPending(doc, {
kind: 'group',
groupJid: GROUP_A,
phone: '8618666666666',
pushName: 'Alice',
requestText: 'hello',
}, { idFactory: () => 'p_test_approve_1' });
assert.equal(created, true);
assert.equal(pending.id, 'p_test_approve_1');
doc = withPending;
const reused = enqueueAccessPending(doc, {
kind: 'group',
groupJid: GROUP_A,
phone: '8618666666666',
});
assert.equal(reused.created, false);
const { grant: approved } = resolveAccessPending(doc, {
pendingId: pending.id,
action: 'approve',
resolvedByPhone: '8618111111111',
});
assert.equal(approved.groups[GROUP_A].members[0].phone, '8618666666666');
assert.equal(approved.directMembers.length, 0);
assert.equal(approved.pending[0].status, 'approved');
});
test('contacts upsert merges lid and phone sightings', () => {
let doc = grant();
doc = upsertAccessContact(doc, {
lid: '999@lid',
pushName: 'Bob',
scene: 'group',
groupJid: GROUP_A,
});
doc = upsertAccessContact(doc, {
phone: '8618777777777',
lid: '999@lid',
pushName: 'Bobby',
scene: 'direct',
});
assert.equal(doc.contacts.length, 1);
assert.equal(doc.contacts[0].phone, '8618777777777');
assert.deepEqual(doc.contacts[0].lids, ['999@lid']);
assert.deepEqual(doc.contacts[0].scenes.slice().sort(), ['direct', 'group']);
});
test('quote approval helpers parse intent, pending id, and notify refs', () => {
assert.equal(parseApprovalIntent('同意 顺便说一声'), 'approve');
assert.equal(parseApprovalIntent('拒绝'), 'deny');
assert.equal(parseApprovalIntent('maybe later'), null);
const body = formatPendingNotifyBody({
id: 'p_abc123',
kind: 'direct',
phone: '8618888888888',
pushName: 'Carol',
requestText: 'hi',
});
assert.match(body, /请求编号:p_abc123/);
assert.equal(parsePendingIdFromNotifyText(body), 'p_abc123');
const enBody = formatPendingNotifyBody({
id: 'p_abc123',
kind: 'direct',
phone: '8618888888888',
pushName: 'Carol',
requestText: 'hi',
}, (text, params) => {
const map = {
'[dsh-im-ops] 访问申请': '[dsh-im-ops] Access request',
'私聊': 'Direct chat',
'场景:{scene}': 'Scene: {scene}',
'申请人:{who}': 'Requester: {who}',
'请求编号:{id}': 'Request: {id}',
'原文:{text}': 'Original message: {text}',
'请引用本条消息回复「同意」或「拒绝」。': 'Quote this message…',
'未知': 'unknown',
};
const translated = map[text] ?? text;
if (params == null) return translated;
return translated.replace(/\{(\w+)\}/g, (match, name) =>
Object.hasOwn(params, name) ? String(params[name]) : match);
});
assert.match(enBody, /Request: p_abc123/);
assert.equal(parsePendingIdFromNotifyText(enBody), 'p_abc123');
const withRef = {
...grant(),
pending: [{
id: 'p_abc123',
kind: 'direct',
phone: '8618888888888',
createdAt: new Date().toISOString(),
status: 'pending',
unresolved: false,
notifyRefs: [{ adminPhone: '8618111111111', providerMessageId: 'MSG1' }],
}],
};
assert.equal(findPendingByNotifyMessageId(withRef, 'MSG1')?.id, 'p_abc123');
});
test('resolveAccessAgentPreset prefers direct-member and group overrides over global', () => {
const doc = grant({
directMembers: [
{ phone: '8618222222222', canExecuteCommands: true, agentPreset: 'ops-user' },
{ phone: '8618333333333', canExecuteCommands: true },
],
groups: {
[GROUP_A]: {
title: 'A',
agentPreset: 'ops-group',
admins: [],
members: [{ phone: '8618444444444', canExecuteCommands: true }],
},
},
});
assert.equal(resolveAccessAgentPreset(doc, {
kind: 'direct',
phone: '8618222222222',
}), 'ops-user');
assert.equal(resolveAccessAgentPreset(doc, {
kind: 'direct',
phone: '8618333333333',
}), null);
assert.equal(resolveAccessAgentPreset(doc, {
kind: 'group',
groupJid: GROUP_A,
}), 'ops-group');
assert.equal(resolveAccessAgentPreset(doc, {
kind: 'group',
groupJid: GROUP_B,
}), null);
});
test('validateAccessGrant keeps optional agentPreset on members and groups', () => {
const doc = grant({
directMembers: [{ phone: '8618222222222', canExecuteCommands: false, agentPreset: 'desk' }],
groups: {
[GROUP_A]: {
agentPreset: 'night',
admins: ['8618111111111'],
members: [],
},
},
});
assert.equal(doc.directMembers[0].agentPreset, 'desk');
assert.equal(doc.groups[GROUP_A].agentPreset, 'night');
assert.throws(() => grant({
directMembers: [{ phone: '8618222222222', canExecuteCommands: true, agentPreset: 'BAD Preset' }],
}), /Agent Preset/);
});