atagen

malice

public
Star0
Watch0
e72926bcca9a49b5dce29970ae99748c32cac9f5
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/test/push-model.test.ts

235ce394f90datagen2026-10-26 06:54 UTC1
import assert from 'node:assert/strict';
235ce394f90datagen2026-10-26 06:54 UTC2
import { test } from 'node:test';
235ce394f90datagen2026-10-26 06:54 UTC3
import {
235ce394f90datagen2026-10-26 06:54 UTC4
  bytesToUrlBase64,
235ce394f90datagen2026-10-26 06:54 UTC5
  devicePusherRequest,
235ce394f90datagen2026-10-26 06:54 UTC6
  parsePush,
235ce394f90datagen2026-10-26 06:54 UTC7
  parseResolved,
235ce394f90datagen2026-10-26 06:54 UTC8
  parseStoredPush,
235ce394f90datagen2026-10-26 06:54 UTC9
  roomFromTag,
235ce394f90datagen2026-10-26 06:54 UTC10
  roomTarget,
235ce394f90datagen2026-10-26 06:54 UTC11
  shouldSilence,
235ce394f90datagen2026-10-26 06:54 UTC12
  stalePushers,
235ce394f90datagen2026-10-26 06:54 UTC13
  syncAction,
235ce394f90datagen2026-10-26 06:54 UTC14
  urlBase64ToBytes
235ce394f90datagen2026-10-26 06:54 UTC15
} from '../src/lib/push-model.ts';
235ce394f90datagen2026-10-26 06:54 UTC16
235ce394f90datagen2026-10-26 06:54 UTC17
const ORIGIN = 'https://chat.example';
235ce394f90datagen2026-10-26 06:54 UTC18
235ce394f90datagen2026-10-26 06:54 UTC19
const FULL = {
235ce394f90datagen2026-10-26 06:54 UTC20
  web_push: 8030,
235ce394f90datagen2026-10-26 06:54 UTC21
  notification: {
235ce394f90datagen2026-10-26 06:54 UTC22
    title: 'Ada in General',
235ce394f90datagen2026-10-26 06:54 UTC23
    body: 'hello',
235ce394f90datagen2026-10-26 06:54 UTC24
    navigate: `${ORIGIN}/?room=%21r%3Ax`,
235ce394f90datagen2026-10-26 06:54 UTC25
    tag: 'room:!r:x',
235ce394f90datagen2026-10-26 06:54 UTC26
    silent: false,
235ce394f90datagen2026-10-26 06:54 UTC27
    app_badge: '4'
235ce394f90datagen2026-10-26 06:54 UTC28
  },
235ce394f90datagen2026-10-26 06:54 UTC29
  malice: { room_id: '!r:x', event_id: '$e', encrypted: true, mention: true, unread: 7 }
235ce394f90datagen2026-10-26 06:54 UTC30
};
235ce394f90datagen2026-10-26 06:54 UTC31
235ce394f90datagen2026-10-26 06:54 UTC32
test('parsePush reads the full contract', () => {
235ce394f90datagen2026-10-26 06:54 UTC33
  const d = parsePush(JSON.stringify(FULL), ORIGIN);
235ce394f90datagen2026-10-26 06:54 UTC34
  assert.equal(d.title, 'Ada in General');
235ce394f90datagen2026-10-26 06:54 UTC35
  assert.equal(d.body, 'hello');
235ce394f90datagen2026-10-26 06:54 UTC36
  assert.equal(d.tag, 'room:!r:x');
235ce394f90datagen2026-10-26 06:54 UTC37
  assert.equal(d.navigate, `${ORIGIN}/?room=%21r%3Ax`);
235ce394f90datagen2026-10-26 06:54 UTC38
  assert.equal(d.badge, 4);
235ce394f90datagen2026-10-26 06:54 UTC39
  assert.equal(d.roomId, '!r:x');
235ce394f90datagen2026-10-26 06:54 UTC40
  assert.equal(d.eventId, '$e');
235ce394f90datagen2026-10-26 06:54 UTC41
  assert.equal(d.mention, true);
235ce394f90datagen2026-10-26 06:54 UTC42
  assert.equal(d.encrypted, true);
235ce394f90datagen2026-10-26 06:54 UTC43
});
235ce394f90datagen2026-10-26 06:54 UTC44
235ce394f90datagen2026-10-26 06:54 UTC45
test('parsePush falls back for garbage, empty and missing data', () => {
235ce394f90datagen2026-10-26 06:54 UTC46
  for (const raw of [null, '', 'not json', '[]', '"str"', '{}', 42]) {
235ce394f90datagen2026-10-26 06:54 UTC47
    const d = parsePush(raw, ORIGIN);
235ce394f90datagen2026-10-26 06:54 UTC48
    assert.equal(d.title, 'New message');
235ce394f90datagen2026-10-26 06:54 UTC49
    assert.equal(d.body, '');
235ce394f90datagen2026-10-26 06:54 UTC50
    assert.equal(d.tag, 'malice');
235ce394f90datagen2026-10-26 06:54 UTC51
    assert.equal(d.roomId, null);
235ce394f90datagen2026-10-26 06:54 UTC52
    assert.equal(d.badge, null);
235ce394f90datagen2026-10-26 06:54 UTC53
  }
235ce394f90datagen2026-10-26 06:54 UTC54
});
235ce394f90datagen2026-10-26 06:54 UTC55
235ce394f90datagen2026-10-26 06:54 UTC56
test('parsePush derives the tag and badge from the malice block', () => {
235ce394f90datagen2026-10-26 06:54 UTC57
  const d = parsePush({ malice: { room_id: '!a:b', unread: 3 } }, ORIGIN);
235ce394f90datagen2026-10-26 06:54 UTC58
  assert.equal(d.tag, 'room:!a:b');
235ce394f90datagen2026-10-26 06:54 UTC59
  assert.equal(d.badge, 3);
235ce394f90datagen2026-10-26 06:54 UTC60
  assert.equal(parsePush({ notification: { app_badge: '0' } }, ORIGIN).badge, 0);
235ce394f90datagen2026-10-26 06:54 UTC61
  assert.equal(parsePush({ notification: { app_badge: '-2' } }, ORIGIN).badge, null);
235ce394f90datagen2026-10-26 06:54 UTC62
});
235ce394f90datagen2026-10-26 06:54 UTC63
235ce394f90datagen2026-10-26 06:54 UTC64
test('parsePush drops navigate urls from other origins', () => {
235ce394f90datagen2026-10-26 06:54 UTC65
  const d = parsePush({ notification: { navigate: 'https://evil.example/?room=x' } }, ORIGIN);
235ce394f90datagen2026-10-26 06:54 UTC66
  assert.equal(d.navigate, null);
235ce394f90datagen2026-10-26 06:54 UTC67
  assert.equal(parsePush({ notification: { navigate: '/?room=x' } }, ORIGIN).navigate, `${ORIGIN}/?room=x`);
235ce394f90datagen2026-10-26 06:54 UTC68
});
235ce394f90datagen2026-10-26 06:54 UTC69
235ce394f90datagen2026-10-26 06:54 UTC70
test('roomTarget prefers navigate then the room then the root', () => {
235ce394f90datagen2026-10-26 06:54 UTC71
  assert.equal(roomTarget('!r:x', `${ORIGIN}/?room=y`), `${ORIGIN}/?room=y`);
235ce394f90datagen2026-10-26 06:54 UTC72
  assert.equal(roomTarget('!r:x', null), '/?room=!r%3Ax');
235ce394f90datagen2026-10-26 06:54 UTC73
  assert.equal(roomTarget(null, null), '/');
235ce394f90datagen2026-10-26 06:54 UTC74
  assert.equal(roomFromTag('room:!r:x'), '!r:x');
235ce394f90datagen2026-10-26 06:54 UTC75
  assert.equal(roomFromTag('malice'), null);
235ce394f90datagen2026-10-26 06:54 UTC76
});
235ce394f90datagen2026-10-26 06:54 UTC77
235ce394f90datagen2026-10-26 06:54 UTC78
test('shouldSilence keeps the banner but mutes it for a focused window or a silent payload', () => {
235ce394f90datagen2026-10-26 06:54 UTC79
  const loud = parsePush(FULL, ORIGIN);
235ce394f90datagen2026-10-26 06:54 UTC80
  assert.equal(shouldSilence(loud, false), false);
235ce394f90datagen2026-10-26 06:54 UTC81
  assert.equal(shouldSilence(loud, true), true);
235ce394f90datagen2026-10-26 06:54 UTC82
  assert.equal(shouldSilence({ ...loud, silent: true }, false), true);
235ce394f90datagen2026-10-26 06:54 UTC83
});
235ce394f90datagen2026-10-26 06:54 UTC84
235ce394f90datagen2026-10-26 06:54 UTC85
test('parseResolved only replaces when the content differs', () => {
235ce394f90datagen2026-10-26 06:54 UTC86
  const d = parsePush(FULL, ORIGIN);
235ce394f90datagen2026-10-26 06:54 UTC87
  assert.deepEqual(parseResolved({ title: 'Ada', body: 'real text' }, d), { title: 'Ada', body: 'real text' });
235ce394f90datagen2026-10-26 06:54 UTC88
  assert.deepEqual(parseResolved({ body: 'real text' }, d), { title: d.title, body: 'real text' });
235ce394f90datagen2026-10-26 06:54 UTC89
  assert.equal(parseResolved({ title: d.title, body: d.body }, d), null);
235ce394f90datagen2026-10-26 06:54 UTC90
  assert.equal(parseResolved(null, d), null);
235ce394f90datagen2026-10-26 06:54 UTC91
  assert.equal(parseResolved({}, d), null);
235ce394f90datagen2026-10-26 06:54 UTC92
});
235ce394f90datagen2026-10-26 06:54 UTC93
235ce394f90datagen2026-10-26 06:54 UTC94
test('syncAction repairs a lost or rotated subscription', () => {
235ce394f90datagen2026-10-26 06:54 UTC95
  const stored = { endpoint: 'https://push/a', pushkey: 'k' };
235ce394f90datagen2026-10-26 06:54 UTC96
  assert.equal(syncAction(false, null, stored), 'none');
235ce394f90datagen2026-10-26 06:54 UTC97
  assert.equal(syncAction(true, null, stored), 'subscribe');
235ce394f90datagen2026-10-26 06:54 UTC98
  assert.equal(syncAction(true, 'https://push/b', stored), 'register');
235ce394f90datagen2026-10-26 06:54 UTC99
  assert.equal(syncAction(true, 'https://push/a', null), 'register');
235ce394f90datagen2026-10-26 06:54 UTC100
  assert.equal(syncAction(true, 'https://push/a', stored), 'none');
235ce394f90datagen2026-10-26 06:54 UTC101
});
235ce394f90datagen2026-10-26 06:54 UTC102
235ce394f90datagen2026-10-26 06:54 UTC103
test('base64url round trips without padding', () => {
235ce394f90datagen2026-10-26 06:54 UTC104
  const bytes = new Uint8Array([251, 255, 190, 1, 2]);
235ce394f90datagen2026-10-26 06:54 UTC105
  const text = bytesToUrlBase64(bytes);
235ce394f90datagen2026-10-26 06:54 UTC106
  assert.equal(text, '-_--AQI');
235ce394f90datagen2026-10-26 06:54 UTC107
  assert.deepEqual([...urlBase64ToBytes(text)], [...bytes]);
235ce394f90datagen2026-10-26 06:54 UTC108
});
235ce394f90datagen2026-10-26 06:54 UTC109
235ce394f90datagen2026-10-26 06:54 UTC110
test('parseStoredPush tolerates junk', () => {
235ce394f90datagen2026-10-26 06:54 UTC111
  assert.equal(parseStoredPush(null), null);
235ce394f90datagen2026-10-26 06:54 UTC112
  assert.equal(parseStoredPush('{'), null);
235ce394f90datagen2026-10-26 06:54 UTC113
  assert.deepEqual(parseStoredPush('{"endpoint":"e","pushkey":"k"}'), { endpoint: 'e', pushkey: 'k' });
235ce394f90datagen2026-10-26 06:54 UTC114
  assert.deepEqual(parseStoredPush('{"endpoint":"e"}'), { endpoint: 'e', pushkey: null });
235ce394f90datagen2026-10-26 06:54 UTC115
});
235ce394f90datagen2026-10-26 06:54 UTC116
235ce394f90datagen2026-10-26 06:54 UTC117
test('devicePusherRequest targets the same-origin gateway', () => {
235ce394f90datagen2026-10-26 06:54 UTC118
  const r = devicePusherRequest({ endpoint: 'https://push/a', p256dh: 'pk', auth: 'au' }, ORIGIN, 'en', 'b1');
235ce394f90datagen2026-10-26 06:54 UTC119
  assert.equal(r.op, 'set_pusher');
235ce394f90datagen2026-10-26 06:54 UTC120
  assert.equal(r.app_id, 'dev.malice.device');
235ce394f90datagen2026-10-26 06:54 UTC121
  assert.equal(r.pushkey, 'pk');
235ce394f90datagen2026-10-26 06:54 UTC122
  assert.equal(r.url, `${ORIGIN}/_matrix/push/v1/notify`);
235ce394f90datagen2026-10-26 06:54 UTC123
  assert.equal(r.format, 'Full');
235ce394f90datagen2026-10-26 06:54 UTC124
  assert.deepEqual(r.data, { endpoint: 'https://push/a', auth: 'au', browser: 'b1' });
235ce394f90datagen2026-10-26 06:54 UTC125
});
235ce394f90datagen2026-10-26 06:54 UTC126
235ce394f90datagen2026-10-26 06:54 UTC127
test('stalePushers only picks this browser own old endpoints', () => {
235ce394f90datagen2026-10-26 06:54 UTC128
  const mk = (pushkey: string, endpoint: string, browser: string, app_id = 'dev.malice.device') => ({ app_id, pushkey, data: { endpoint, browser } });
235ce394f90datagen2026-10-26 06:54 UTC129
  const reply = { pushers: [mk('a', 'old', 'b1'), mk('b', 'cur', 'b1'), mk('c', 'old', 'b2'), mk('d', 'old', 'b1', 'other.app')] };
235ce394f90datagen2026-10-26 06:54 UTC130
  assert.deepEqual(stalePushers(reply, 'b1', 'cur'), ['a']);
235ce394f90datagen2026-10-26 06:54 UTC131
  assert.deepEqual(stalePushers(null, 'b1', 'cur'), []);
235ce394f90datagen2026-10-26 06:54 UTC132
});