Files
cinny/src/app/features/room/widgets/widgetUtils.test.ts
T

109 lines
4.1 KiB
TypeScript
Raw Normal View History

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { MatrixCapabilities, Capability } from 'matrix-widget-api';
import {
validateWidgetUrl,
isWidgetUrlSafe,
filterWidgetCapabilities,
generateWidgetId,
classifyWidgetCapabilities,
loadWidgetConsent,
} from './widgetUtils';
const APP = 'https://chat.lotusguild.org';
test('validateWidgetUrl accepts a cross-origin https url', () => {
assert.equal(validateWidgetUrl('https://pad.example.org/p/room', APP), undefined);
});
test('validateWidgetUrl rejects empty / invalid / http / same-origin', () => {
assert.equal(validateWidgetUrl(' ', APP), 'empty');
assert.equal(validateWidgetUrl('not a url', APP), 'invalid');
assert.equal(validateWidgetUrl('http://example.org', APP), 'not-https');
assert.equal(validateWidgetUrl('https://chat.lotusguild.org/evil', APP), 'same-origin');
});
test('isWidgetUrlSafe rejects same-origin + garbage, accepts cross-origin', () => {
assert.equal(isWidgetUrlSafe('https://chat.lotusguild.org/x', APP), false);
assert.equal(isWidgetUrlSafe('https://other.example/x', APP), true);
assert.equal(isWidgetUrlSafe('garbage', APP), false);
});
test('filterWidgetCapabilities keeps only the benign allowlist', () => {
const requested = new Set<Capability>([
MatrixCapabilities.AlwaysOnScreen,
'm.send.event:m.room.message',
'org.matrix.msc2762.receive.state_event:m.room.member',
MatrixCapabilities.Screenshots,
]);
const allowed = filterWidgetCapabilities(requested);
assert.ok(allowed.has(MatrixCapabilities.AlwaysOnScreen));
assert.ok(allowed.has(MatrixCapabilities.Screenshots));
assert.equal(allowed.has('m.send.event:m.room.message'), false);
assert.equal(allowed.size, 2);
});
test('generateWidgetId is prefixed and unique across calls', () => {
const a = generateWidgetId();
const b = generateWidgetId();
assert.match(a, /^lotus_/);
assert.notEqual(a, b);
});
test('classifyWidgetCapabilities: display caps are automatic, event caps are asked', () => {
const { auto, ask } = classifyWidgetCapabilities([
MatrixCapabilities.AlwaysOnScreen,
'org.matrix.msc2762.receive.event:m.room.message#m.text',
'org.matrix.msc2762.send.event:m.reaction',
'org.matrix.msc2762.receive.state_event:m.room.topic',
]);
assert.deepEqual([...auto], [MatrixCapabilities.AlwaysOnScreen]);
assert.deepEqual(
ask.map((r) => [r.label, r.sends]),
[
['See messages of type “m.text” in this room', false],
['Send reactions in this room', true],
['See the room topic in this room', false],
],
);
});
test('classifyWidgetCapabilities never offers protected state, other rooms, to-device or account data', () => {
const { auto, ask } = classifyWidgetCapabilities([
'org.matrix.msc2762.send.state_event:m.room.power_levels',
'org.matrix.msc2762.send.state_event:m.room.join_rules',
'org.matrix.msc2762.send.state_event:m.room.encryption',
'org.matrix.msc2762.send.state_event:m.room.member#@a:x',
'org.matrix.msc2762.timeline:*',
'org.matrix.msc2762.timeline:!other:x',
'org.matrix.msc3819.send.to_device:m.custom',
'org.matrix.msc3819.receive.to_device:m.custom',
'org.matrix.msc4157.send.delayed_event',
'org.matrix.msc4039.upload_file',
'org.matrix.msc3973.user_directory_search',
'org.matrix.msc2762.receive.room_account_data:m.fully_read',
]);
assert.equal(auto.size, 0);
assert.deepEqual(ask, []);
});
test('classifyWidgetCapabilities: reading protected state is fine, changing unprotected state is asked', () => {
const { ask } = classifyWidgetCapabilities([
'org.matrix.msc2762.receive.state_event:m.room.power_levels',
'org.matrix.msc2762.send.state_event:m.room.topic',
'org.matrix.msc2762.send.state_event:com.example.board#main',
]);
assert.deepEqual(
ask.map((r) => r.label),
[
'See room permissions in this room',
'Change the room topic',
'Send “com.example.board” state (key “main”) in this room',
],
);
});
test('loadWidgetConsent tolerates missing storage', () => {
assert.equal(loadWidgetConsent('!r:x', 'w', 'https://w.example'), undefined);
});