atagen

malice

public
Star0
Watch0
bcf2234c33f6ac03e7f81b83504fe8d3842d7911
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/test/push-model.test.tsTypescript132 lines5.2 KB
1
import assert from 'node:assert/strict';
2
import { test } from 'node:test';
3
import {
4
  bytesToUrlBase64,
5
  devicePusherRequest,
6
  parsePush,
7
  parseResolved,
8
  parseStoredPush,
9
  roomFromTag,
10
  roomTarget,
11
  shouldSilence,
12
  stalePushers,
13
  syncAction,
14
  urlBase64ToBytes
15
} from '../src/lib/push-model.ts';
16
17
const ORIGIN = 'https://chat.example';
18
19
const FULL = {
20
  web_push: 8030,
21
  notification: {
22
    title: 'Ada in General',
23
    body: 'hello',
24
    navigate: `${ORIGIN}/?room=%21r%3Ax`,
25
    tag: 'room:!r:x',
26
    silent: false,
27
    app_badge: '4'
28
  },
29
  malice: { room_id: '!r:x', event_id: '$e', encrypted: true, mention: true, unread: 7 }
30
};
31
32
test('parsePush reads the full contract', () => {
33
  const d = parsePush(JSON.stringify(FULL), ORIGIN);
34
  assert.equal(d.title, 'Ada in General');
35
  assert.equal(d.body, 'hello');
36
  assert.equal(d.tag, 'room:!r:x');
37
  assert.equal(d.navigate, `${ORIGIN}/?room=%21r%3Ax`);
38
  assert.equal(d.badge, 4);
39
  assert.equal(d.roomId, '!r:x');
40
  assert.equal(d.eventId, '$e');
41
  assert.equal(d.mention, true);
42
  assert.equal(d.encrypted, true);
43
});
44
45
test('parsePush falls back for garbage, empty and missing data', () => {
46
  for (const raw of [null, '', 'not json', '[]', '"str"', '{}', 42]) {
47
    const d = parsePush(raw, ORIGIN);
48
    assert.equal(d.title, 'New message');
49
    assert.equal(d.body, '');
50
    assert.equal(d.tag, 'malice');
51
    assert.equal(d.roomId, null);
52
    assert.equal(d.badge, null);
53
  }
54
});
55
56
test('parsePush derives the tag and badge from the malice block', () => {
57
  const d = parsePush({ malice: { room_id: '!a:b', unread: 3 } }, ORIGIN);
58
  assert.equal(d.tag, 'room:!a:b');
59
  assert.equal(d.badge, 3);
60
  assert.equal(parsePush({ notification: { app_badge: '0' } }, ORIGIN).badge, 0);
61
  assert.equal(parsePush({ notification: { app_badge: '-2' } }, ORIGIN).badge, null);
62
});
63
64
test('parsePush drops navigate urls from other origins', () => {
65
  const d = parsePush({ notification: { navigate: 'https://evil.example/?room=x' } }, ORIGIN);
66
  assert.equal(d.navigate, null);
67
  assert.equal(parsePush({ notification: { navigate: '/?room=x' } }, ORIGIN).navigate, `${ORIGIN}/?room=x`);
68
});
69
70
test('roomTarget prefers navigate then the room then the root', () => {
71
  assert.equal(roomTarget('!r:x', `${ORIGIN}/?room=y`), `${ORIGIN}/?room=y`);
72
  assert.equal(roomTarget('!r:x', null), '/?room=!r%3Ax');
73
  assert.equal(roomTarget(null, null), '/');
74
  assert.equal(roomFromTag('room:!r:x'), '!r:x');
75
  assert.equal(roomFromTag('malice'), null);
76
});
77
78
test('shouldSilence keeps the banner but mutes it for a focused window or a silent payload', () => {
79
  const loud = parsePush(FULL, ORIGIN);
80
  assert.equal(shouldSilence(loud, false), false);
81
  assert.equal(shouldSilence(loud, true), true);
82
  assert.equal(shouldSilence({ ...loud, silent: true }, false), true);
83
});
84
85
test('parseResolved only replaces when the content differs', () => {
86
  const d = parsePush(FULL, ORIGIN);
87
  assert.deepEqual(parseResolved({ title: 'Ada', body: 'real text' }, d), { title: 'Ada', body: 'real text' });
88
  assert.deepEqual(parseResolved({ body: 'real text' }, d), { title: d.title, body: 'real text' });
89
  assert.equal(parseResolved({ title: d.title, body: d.body }, d), null);
90
  assert.equal(parseResolved(null, d), null);
91
  assert.equal(parseResolved({}, d), null);
92
});
93
94
test('syncAction repairs a lost or rotated subscription', () => {
95
  const stored = { endpoint: 'https://push/a', pushkey: 'k' };
96
  assert.equal(syncAction(false, null, stored), 'none');
97
  assert.equal(syncAction(true, null, stored), 'subscribe');
98
  assert.equal(syncAction(true, 'https://push/b', stored), 'register');
99
  assert.equal(syncAction(true, 'https://push/a', null), 'register');
100
  assert.equal(syncAction(true, 'https://push/a', stored), 'none');
101
});
102
103
test('base64url round trips without padding', () => {
104
  const bytes = new Uint8Array([251, 255, 190, 1, 2]);
105
  const text = bytesToUrlBase64(bytes);
106
  assert.equal(text, '-_--AQI');
107
  assert.deepEqual([...urlBase64ToBytes(text)], [...bytes]);
108
});
109
110
test('parseStoredPush tolerates junk', () => {
111
  assert.equal(parseStoredPush(null), null);
112
  assert.equal(parseStoredPush('{'), null);
113
  assert.deepEqual(parseStoredPush('{"endpoint":"e","pushkey":"k"}'), { endpoint: 'e', pushkey: 'k' });
114
  assert.deepEqual(parseStoredPush('{"endpoint":"e"}'), { endpoint: 'e', pushkey: null });
115
});
116
117
test('devicePusherRequest targets the same-origin gateway', () => {
118
  const r = devicePusherRequest({ endpoint: 'https://push/a', p256dh: 'pk', auth: 'au' }, ORIGIN, 'en', 'b1');
119
  assert.equal(r.op, 'set_pusher');
120
  assert.equal(r.app_id, 'dev.malice.device');
121
  assert.equal(r.pushkey, 'pk');
122
  assert.equal(r.url, `${ORIGIN}/_matrix/push/v1/notify`);
123
  assert.equal(r.format, 'Full');
124
  assert.deepEqual(r.data, { endpoint: 'https://push/a', auth: 'au', browser: 'b1' });
125
});
126
127
test('stalePushers only picks this browser own old endpoints', () => {
128
  const mk = (pushkey: string, endpoint: string, browser: string, app_id = 'dev.malice.device') => ({ app_id, pushkey, data: { endpoint, browser } });
129
  const reply = { pushers: [mk('a', 'old', 'b1'), mk('b', 'cur', 'b1'), mk('c', 'old', 'b2'), mk('d', 'old', 'b1', 'other.app')] };
130
  assert.deepEqual(stalePushers(reply, 'b1', 'cur'), ['a']);
131
  assert.deepEqual(stalePushers(null, 'b1', 'cur'), []);
132
});