ts / test / mailbox.test.ts
898 words
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
 * The p2p offline mailbox is a node-hosted data plane, so these assert the
 * URL shape, room encoding, payload handling and cursor semantics rather
 * than appearing in the API coverage table.
 */
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { isApiError, isConfigError } from '../src/errors.js';
import * as mailbox from '../src/mailbox.js';
import { bodyJson, captureClient, caught, fakeFetch, type Reply } from './helpers.js';

const creds: mailbox.MailboxCredentials = {
  token: '1790000000:app-1:abc:.deadbeef',
  ttl: 3600,
  expires_at: 1790000000,
  endpoint: 'https://sueta.ap2p.ro/mailbox/v1',
};

const text = (body: BodyInit | null | undefined): string =>
  body instanceof Uint8Array ? new TextDecoder().decode(body) : String(body);

test('credentials mint through the ird API and unwrap the credentials', async () => {
  const { client, calls } = captureClient({ apiKey: 'ird_k' }, [{ status: 200, body: { credentials: creds } }]);
  assert.deepEqual(await mailbox.credentials(client, 'app-1', { room: 'r', ttl_sec: 60 }), creds);
  assert.equal(calls[0]!.url, 'https://api.ird.ro/p2p/apps/app-1/mailbox-credentials');
  assert.deepEqual(bodyJson(calls[0]!), { room: 'r', ttl_sec: 60 });
});

test('deposit', async (t) => {
  await t.test('posts raw bytes to the node with the mailbox token', async () => {
    const { fetch, calls } = fakeFetch([
      { status: 201, body: { seq: '1844674407370955161', id: 'ab12', expires_at: 1790086400, deduped: false } },
    ]);
    const resp = await mailbox.deposit(creds, 'room-1', 'hello', { fetch });
    assert.equal(resp.seq, '1844674407370955161');
    assert.equal(resp.deduped, false);
    const req = calls[0]!;
    assert.equal(req.method, 'POST');
    assert.equal(req.url, 'https://sueta.ap2p.ro/mailbox/v1/rooms/room-1/messages');
    assert.equal(req.headers.Authorization, `Bearer ${creds.token}`);
    assert.equal(req.headers['Content-Type'], 'application/octet-stream');
    assert.equal(text(req.body), 'hello');
  });
  await t.test('a string payload is data, not a file path', async () => {
    const { fetch, calls } = fakeFetch([{ status: 201, body: { seq: '1' } }]);
    await mailbox.deposit(creds, 'r', '/etc/hosts', { fetch });
    assert.equal(text(calls[0]!.body), '/etc/hosts');
  });
  await t.test('a room id with slashes stays one path segment', async () => {
    const { fetch, calls } = fakeFetch([{ status: 201, body: { seq: '1' } }]);
    await mailbox.deposit(creds, '/orbitdb/zdpuAn Address', new Uint8Array([120]), { fetch });
    assert.equal(calls[0]!.url, 'https://sueta.ap2p.ro/mailbox/v1/rooms/%2Forbitdb%2FzdpuAn%20Address/messages');
  });
  await t.test('a trailing slash on the endpoint does not double up', async () => {
    const { fetch, calls } = fakeFetch([{ status: 201, body: { seq: '1' } }]);
    await mailbox.deposit({ ...creds, endpoint: 'https://sueta.ap2p.ro/mailbox/v1/' }, 'r', 'x', { fetch });
    assert.equal(calls[0]!.url, 'https://sueta.ap2p.ro/mailbox/v1/rooms/r/messages');
  });
  await t.test('a blank room and a missing endpoint are refused before any request', async () => {
    const { fetch, calls } = fakeFetch();
    const blank = await caught(() => mailbox.deposit(creds, '', 'x', { fetch }));
    assert.ok(isConfigError(blank));
    assert.match(blank.message, /room must be/);
    const noEndpoint = await caught(() =>
      mailbox.deposit({ ...creds, endpoint: '' } as mailbox.MailboxCredentials, 'r', 'x', { fetch }),
    );
    assert.ok(isConfigError(noEndpoint));
    assert.match(noEndpoint.message, /must carry an endpoint/);
    assert.equal(calls.length, 0);
  });
  await t.test("the node's bare {error: code} envelope maps to an api error", async () => {
    const { fetch } = fakeFetch([{ status: 413, body: { error: 'message_too_large' } }]);
    const e = await caught(() => mailbox.deposit(creds, 'r', 'x', { fetch }));
    assert.ok(isApiError(e));
    assert.equal(e.code, 'message_too_large');
    assert.equal(e.status, 413);
    assert.equal(e.message, 'message_too_large');
  });
});

test('replay', async (t) => {
  await t.test('cursor and limit ride the query string', async () => {
    const { fetch, calls } = fakeFetch([
      { status: 200, body: { messages: [], has_more: false, oldest_seq: '0', latest_seq: '0' } },
    ]);
    await mailbox.replay(creds, 'r', { after: '1844674407370955161', limit: 500, fetch });
    assert.equal(
      calls[0]!.url,
      'https://sueta.ap2p.ro/mailbox/v1/rooms/r/messages?after=1844674407370955161&limit=500',
    );
    assert.equal(calls[0]!.method, 'GET');
  });
  await t.test('payload decoding', async () => {
    const { fetch } = fakeFetch([
      {
        status: 200,
        body: { messages: [{ seq: '5', id: 'ab', ts: 1, payload: 'aGVsbG8=' }], has_more: false, oldest_seq: '5', latest_seq: '5' },
      },
    ]);
    const msg = (await mailbox.replay(creds, 'r', { fetch })).messages[0]!;
    assert.equal(mailbox.payloadString(msg), 'hello');
    assert.deepEqual([...mailbox.payloadBytes(msg)], [104, 101, 108, 108, 111]);
  });
});

test('isHistoryGap', () => {
  assert.equal(mailbox.isHistoryGap('100', { oldest_seq: '200' }), true, 'a cursor older than oldest_seq is a gap');
  // "1099511627776" < "99" lexicographically but is far larger
  assert.equal(mailbox.isHistoryGap('1099511627776', { oldest_seq: '99' }), false, 'seqs compare as integers');
  assert.equal(mailbox.isHistoryGap('99', { oldest_seq: '1099511627776' }), true);
  assert.equal(mailbox.isHistoryGap('200', { oldest_seq: '200' }), false, 'at oldest_seq is no gap');
  assert.equal(mailbox.isHistoryGap('300', { oldest_seq: '200' }), false, 'after oldest_seq is no gap');
  assert.equal(mailbox.isHistoryGap(undefined, { oldest_seq: '200' }), false, 'a first visit is never a gap');
  assert.equal(mailbox.isHistoryGap('', { oldest_seq: '200' }), false);
  assert.equal(mailbox.isHistoryGap('5', { oldest_seq: '0' }), false, 'an empty room is never a gap');
  assert.equal(mailbox.isHistoryGap('5', {}), false);
});

test('replayAll', async (t) => {
  const page = (body: object): Reply => ({ status: 200, body });
  await t.test('pages on has_more, advancing after by the last seq', async () => {
    const { fetch, calls } = fakeFetch([
      page({
        messages: [
          { seq: '10', payload: 'YQ==' },
          { seq: '20', payload: 'Yg==' },
        ],
        has_more: true,
        oldest_seq: '10',
        latest_seq: '30',
      }),
      page({ messages: [{ seq: '30', payload: 'Yw==' }], has_more: false, oldest_seq: '10', latest_seq: '30' }),
    ]);
    const { messages, cursor, gap } = await mailbox.replayAll(creds, 'r', { limit: 2, fetch });
    assert.deepEqual(
      messages.map((m) => m.seq),
      ['10', '20', '30'],
    );
    assert.equal(cursor, '30');
    assert.equal(gap, false);
    assert.deepEqual(
      calls.map((c) => c.url),
      [
        'https://sueta.ap2p.ro/mailbox/v1/rooms/r/messages?limit=2',
        'https://sueta.ap2p.ro/mailbox/v1/rooms/r/messages?after=20&limit=2',
      ],
    );
  });
  await t.test('a quiet room keeps the cursor it was given', async () => {
    const { fetch } = fakeFetch([page({ messages: [], has_more: false, oldest_seq: '5', latest_seq: '9' })]);
    const result = await mailbox.replayAll(creds, 'r', { after: '9', fetch });
    assert.equal(result.messages.length, 0);
    assert.equal(result.cursor, '9');
    assert.equal(result.gap, false);
  });
  await t.test('an evicted cursor is reported as a gap, from the first page', async () => {
    const { fetch } = fakeFetch([
      page({ messages: [{ seq: '50', payload: 'YQ==' }], has_more: false, oldest_seq: '40', latest_seq: '50' }),
    ]);
    assert.equal((await mailbox.replayAll(creds, 'r', { after: '10', fetch })).gap, true);
  });
});

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/ts.git