import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test'; import postgres, { type Sql } from 'postgres'; import { PostgresPrekeyStore } from '../src/index.js'; function randBytes(n: number): Uint8Array { const buf = new Uint8Array(n); crypto.getRandomValues(buf); return buf; } const PG_URL = process.env.SHADE_TEST_PG_URL; if (!PG_URL) { describe.skip('PostgresPrekeyStore (skipped — set SHADE_TEST_PG_URL)', () => { test('placeholder', () => {}); }); } else { describe('PostgresPrekeyStore', () => { let sql: Sql; let store: PostgresPrekeyStore; beforeAll(async () => { sql = postgres(PG_URL!); await sql`DROP TABLE IF EXISTS shade_server_identities CASCADE`; await sql`DROP TABLE IF EXISTS shade_server_signed_prekeys CASCADE`; await sql`DROP TABLE IF EXISTS shade_server_one_time_prekeys CASCADE`; store = await PostgresPrekeyStore.fromClient(sql); }); beforeEach(async () => { await sql`TRUNCATE shade_server_identities, shade_server_signed_prekeys, shade_server_one_time_prekeys`; }); afterAll(async () => { await sql.end(); }); test('identity crud', async () => { const sig = randBytes(32); const dh = randBytes(32); await store.saveIdentity('bob', sig, dh); const id = await store.getIdentity('bob'); expect(id!.identitySigningKey).toEqual(sig); expect(id!.identityDHKey).toEqual(dh); }); test('signed prekey crud', async () => { const pub = randBytes(32); const sig = randBytes(64); await store.saveSignedPreKey('bob', 1, pub, sig); const spk = await store.getSignedPreKey('bob'); expect(spk!.keyId).toBe(1); expect(spk!.publicKey).toEqual(pub); expect(spk!.signature).toEqual(sig); }); test('one-time prekey FIFO consumption', async () => { await store.saveOneTimePreKeys('bob', [ { keyId: 100, publicKey: randBytes(32) }, { keyId: 101, publicKey: randBytes(32) }, { keyId: 102, publicKey: randBytes(32) }, ]); expect(await store.getOneTimePreKeyCount('bob')).toBe(3); const k1 = await store.consumeOneTimePreKey('bob'); expect(k1!.keyId).toBe(100); const k2 = await store.consumeOneTimePreKey('bob'); expect(k2!.keyId).toBe(101); const k3 = await store.consumeOneTimePreKey('bob'); expect(k3!.keyId).toBe(102); expect(await store.consumeOneTimePreKey('bob')).toBeNull(); }); test('multi-address isolation', async () => { await store.saveIdentity('alice', randBytes(32), randBytes(32)); await store.saveIdentity('bob', randBytes(32), randBytes(32)); await store.saveOneTimePreKeys('alice', [{ keyId: 1, publicKey: randBytes(32) }]); await store.saveOneTimePreKeys('bob', [ { keyId: 1, publicKey: randBytes(32) }, { keyId: 2, publicKey: randBytes(32) }, ]); expect(await store.getOneTimePreKeyCount('alice')).toBe(1); expect(await store.getOneTimePreKeyCount('bob')).toBe(2); await store.deleteAll('bob'); expect(await store.getIdentity('alice')).not.toBeNull(); expect(await store.getIdentity('bob')).toBeNull(); expect(await store.getOneTimePreKeyCount('alice')).toBe(1); expect(await store.getOneTimePreKeyCount('bob')).toBe(0); }); test('concurrent consume uses FOR UPDATE SKIP LOCKED', async () => { // Insert 10 keys const keys = Array.from({ length: 10 }, (_, i) => ({ keyId: 1000 + i, publicKey: randBytes(32), })); await store.saveOneTimePreKeys('bob', keys); // Consume concurrently const consumed = await Promise.all( Array.from({ length: 10 }, () => store.consumeOneTimePreKey('bob')), ); // All 10 should be consumed, each unique expect(consumed.filter((k) => k !== null).length).toBe(10); const ids = consumed.map((k) => k!.keyId).sort(); const unique = new Set(ids); expect(unique.size).toBe(10); // no duplicates }); }); }