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([ 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); });