2026-09-19 14:12:23 -04:00
|
|
|
import { test } from 'node:test';
|
|
|
|
|
import assert from 'node:assert/strict';
|
2026-10-17 20:52:12 -04:00
|
|
|
import {
|
|
|
|
|
buildReplyContent,
|
|
|
|
|
pickReplySession,
|
|
|
|
|
replyFailureText,
|
|
|
|
|
replySendUrl,
|
|
|
|
|
sendNotificationReply,
|
|
|
|
|
} from './swReply';
|
2026-09-19 14:12:23 -04:00
|
|
|
|
|
|
|
|
test('thread replies carry the m.thread relation, room replies do not', () => {
|
|
|
|
|
assert.deepEqual(buildReplyContent('hi'), { msgtype: 'm.text', body: 'hi', 'm.mentions': {} });
|
|
|
|
|
assert.deepEqual(buildReplyContent('hi', '$root')['m.relates_to'], {
|
|
|
|
|
rel_type: 'm.thread',
|
|
|
|
|
event_id: '$root',
|
|
|
|
|
is_falling_back: true,
|
|
|
|
|
'm.in_reply_to': { event_id: '$root' },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('send URL encodes the room id against the session base URL', () => {
|
|
|
|
|
assert.equal(
|
|
|
|
|
replySendUrl('https://matrix.example.org/', '!r:example.org', 'tx1'),
|
|
|
|
|
'https://matrix.example.org/_matrix/client/v3/rooms/!r%3Aexample.org/send/m.room.message/tx1',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-17 20:52:12 -04:00
|
|
|
test('sendNotificationReply checks encryption, then PUTs with the bearer token (#203, #299)', async () => {
|
2026-09-19 14:12:23 -04:00
|
|
|
const calls: { url: string; init: RequestInit }[] = [];
|
2026-10-17 20:52:12 -04:00
|
|
|
const fetchFor = (encStatus: number, sendOk = true) =>
|
|
|
|
|
(async (url: string, init: RequestInit = {}) => {
|
|
|
|
|
calls.push({ url, init });
|
|
|
|
|
if (url.includes('/state/m.room.encryption/')) {
|
|
|
|
|
return { ok: encStatus === 200, status: encStatus } as Response;
|
|
|
|
|
}
|
|
|
|
|
return { ok: sendOk, status: sendOk ? 200 : 500 } as Response;
|
|
|
|
|
}) as unknown as typeof fetch;
|
|
|
|
|
const session = { accessToken: 'tok', baseUrl: 'https://hs' };
|
|
|
|
|
|
|
|
|
|
assert.equal(await sendNotificationReply(session, '!r:hs', '$t', 'yo', fetchFor(404)), 'sent');
|
|
|
|
|
assert.match(calls[0].url, /\/rooms\/!r%3Ahs\/state\/m\.room\.encryption\/$/);
|
|
|
|
|
const put = calls[1];
|
|
|
|
|
assert.equal(put.init.method, 'PUT');
|
|
|
|
|
assert.equal((put.init.headers as Record<string, string>).Authorization, 'Bearer tok');
|
2026-09-19 14:12:23 -04:00
|
|
|
assert.match(
|
2026-10-17 20:52:12 -04:00
|
|
|
put.url,
|
2026-09-19 14:12:23 -04:00
|
|
|
/^https:\/\/hs\/_matrix\/client\/v3\/rooms\/!r%3Ahs\/send\/m\.room\.message\/swreply-/,
|
|
|
|
|
);
|
2026-10-17 20:52:12 -04:00
|
|
|
assert.equal(JSON.parse(put.init.body as string)['m.relates_to'].event_id, '$t');
|
2026-09-19 14:12:23 -04:00
|
|
|
|
2026-10-17 20:52:12 -04:00
|
|
|
// Encryption turned on since the notification was shown: nothing is sent.
|
|
|
|
|
calls.length = 0;
|
|
|
|
|
assert.equal(
|
|
|
|
|
await sendNotificationReply(session, '!r:hs', undefined, 'yo', fetchFor(200)),
|
|
|
|
|
'encrypted',
|
|
|
|
|
);
|
|
|
|
|
assert.equal(calls.length, 1);
|
|
|
|
|
// Can't tell (server error): fail safe, nothing is sent.
|
|
|
|
|
calls.length = 0;
|
|
|
|
|
assert.equal(
|
|
|
|
|
await sendNotificationReply(session, '!r:hs', undefined, 'yo', fetchFor(500)),
|
|
|
|
|
'failed',
|
|
|
|
|
);
|
|
|
|
|
assert.equal(calls.length, 1);
|
2026-09-19 14:12:23 -04:00
|
|
|
assert.equal(
|
2026-10-17 20:52:12 -04:00
|
|
|
await sendNotificationReply(session, '!r:hs', undefined, 'yo', fetchFor(404, false)),
|
|
|
|
|
'failed',
|
2026-09-19 14:12:23 -04:00
|
|
|
);
|
2026-10-17 20:52:12 -04:00
|
|
|
assert.equal(
|
|
|
|
|
await sendNotificationReply(undefined, '!r:hs', undefined, 'yo', fetchFor(404)),
|
|
|
|
|
'no-session',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('pickReplySession: the notification account only (#299)', () => {
|
|
|
|
|
const a = { accessToken: 'a', baseUrl: 'https://hs', userId: '@a:hs' };
|
|
|
|
|
const b = { accessToken: 'b', baseUrl: 'https://hs', userId: '@b:hs' };
|
|
|
|
|
assert.equal(pickReplySession([a, b], '@a:hs'), a);
|
|
|
|
|
assert.equal(pickReplySession([b], '@a:hs'), undefined, 'logged out of @a here');
|
|
|
|
|
assert.equal(pickReplySession([a], undefined), a, 'old notification, one session');
|
|
|
|
|
assert.equal(pickReplySession([a, b], undefined), undefined, 'old notification, ambiguous');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('replyFailureText says why', () => {
|
|
|
|
|
assert.match(replyFailureText('encrypted', 'hi'), /now encrypted/);
|
|
|
|
|
assert.match(replyFailureText('no-session', 'hi'), /no longer signed in/);
|
|
|
|
|
assert.match(replyFailureText('failed', 'hi'), /retry/);
|
2026-09-19 14:12:23 -04:00
|
|
|
});
|