// Small smoke test for the deployed NATS-over-WebSocket server. // Verifies: connect, pub/sub roundtrip, request/reply, JetStream stream lifecycle. // // Setup (once): // npm install nats.ws ws // // Usage: // NATS_URL=wss://nats.tes.gd NATS_USER=admin NATS_PASSWORD=... node test-nats.mjs // Polyfill the browser WebSocket global; nats.ws requires it. if (typeof globalThis.WebSocket === 'undefined') { const { default: WS } = await import('ws'); globalThis.WebSocket = WS; } import { connect, StringCodec } from 'nats.ws'; const url = process.env.NATS_URL ?? 'wss://nats.tes.gd'; const user = process.env.NATS_USER ?? 'admin'; const pass = process.env.NATS_PASSWORD; if (!pass) { console.error('NATS_PASSWORD is required'); process.exit(1); } const sc = StringCodec(); const fail = (msg, err) => { console.error(`FAIL: ${msg}`, err ?? ''); process.exit(1); }; console.log(`→ connecting to ${url} as ${user}`); let nc; try { nc = await connect({ servers: url, user, pass, timeout: 5000 }); } catch (e) { fail('connect', e); } console.log(`✓ connected server=${nc.info.server_name} version=${nc.info.version} jetstream=${nc.info.jetstream}`); // 1. Pub/sub roundtrip const subj = `test.${Date.now()}`; const sub = nc.subscribe(subj, { max: 1 }); const gotPub = (async () => { for await (const m of sub) return sc.decode(m.data); })(); nc.publish(subj, sc.encode('hello nats')); const payload = await Promise.race([gotPub, new Promise((_, r) => setTimeout(() => r(new Error('timeout')), 3000))]); if (payload !== 'hello nats') fail(`pub/sub payload mismatch: "${payload}"`); console.log(`✓ pub/sub subject=${subj} payload="${payload}"`); // 2. Request/reply const svcSub = nc.subscribe('svc.echo', { max: 1 }); (async () => { for await (const m of svcSub) m.respond(sc.encode(`echo:${sc.decode(m.data)}`)); })(); const reply = await nc.request('svc.echo', sc.encode('ping'), { timeout: 3000 }); if (sc.decode(reply.data) !== 'echo:ping') fail(`req/rep payload mismatch: "${sc.decode(reply.data)}"`); console.log(`✓ request/rep reply="${sc.decode(reply.data)}"`); // 3. JetStream: create stream, publish, consume, delete const jsm = await nc.jetstreamManager(); const streamName = `TEST_${Date.now()}`; const streamSubj = `js.${streamName}.>`; await jsm.streams.add({ name: streamName, subjects: [streamSubj], storage: 'file' }); const js = nc.jetstream(); const ack = await js.publish(`${streamSubj.replace('>', 'hello')}`, sc.encode('jetstream-payload')); if (ack.seq !== 1) fail(`js publish seq expected 1, got ${ack.seq}`); console.log(`✓ jetstream stream=${streamName} seq=${ack.seq} domain=${ack.domain ?? '-'}`); const consumerName = `TEST_C_${Date.now()}`; await jsm.consumers.add(streamName, { durable_name: consumerName, ack_policy: 'explicit' }); const consumer = await js.consumers.get(streamName, consumerName); const iter = await consumer.fetch({ max_messages: 1, expires: 2000 }); let jsPayload; for await (const m of iter) { jsPayload = sc.decode(m.data); m.ack(); } if (jsPayload !== 'jetstream-payload') fail(`jetstream payload mismatch: "${jsPayload}"`); console.log(`✓ jetstream consumed payload="${jsPayload}"`); await jsm.streams.delete(streamName); console.log(`✓ cleanup stream=${streamName} deleted`); await nc.drain(); console.log('✓ ALL CHECKS PASSED');