import test from 'brittle' import { readFile } from 'node:fs/promises' import { EventEmitter } from 'node:events' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = dirname(fileURLToPath(import.meta.url)) async function load() { const parts = await Promise.all([ readFile(join(__dirname, '../lib/irc/irc-parse.js'), 'utf8'), readFile(join(__dirname, '../lib/irc/irc-isupport.js'), 'utf8'), readFile(join(__dirname, '../lib/irc/irc-sasl.js'), 'utf8'), readFile(join(__dirname, '../lib/irc/irc-state.js'), 'utf8'), readFile(join(__dirname, '../lib/irc/irc-config.js'), 'utf8'), readFile(join(__dirname, '../lib/irc/irc-client.js'), 'utf8') ]) const hook = {} new Function( 'exports', parts.join('\n') + '\nexports.bareIrcCreateClient = bareIrcCreateClient;\nexports.bareIrcSaslPlainChunks = bareIrcSaslPlainChunks;\nexports.bareIrcParseIsupport = bareIrcParseIsupport;\n' )(hook) return hook } function fakeSocket() { const s = new EventEmitter() s.writes = [] s.write = function (x) { s.writes.push(String(x)) return true } s.end = function () { s.emit('close') } return s } test('CAP LS 302 then NICK USER on attach', async (t) => { const h = await load() const sock = fakeSocket() const c = h.bareIrcCreateClient({ nick: 'tester' }) c.attach(sock) const blob = sock.writes.join('') t.ok(/CAP LS :?302/.test(blob)) t.ok(/NICK :?tester/.test(blob)) t.ok(blob.includes('USER ')) }) test('PING answers PONG; 001 assigns nick and CAP REQ', async (t) => { const h = await load() const sock = fakeSocket() const c = h.bareIrcCreateClient({ nick: 'alice' }) let registered = '' c.on('registered', (p) => { registered = p.nick }) c.attach(sock) sock.emit('data', 'PING :irc.example.com\r\n') t.ok(/PONG\s+:?irc\.example\.com/.test(sock.writes.join(''))) sock.emit( 'data', ':irc.example.com CAP * LS :sasl multi-prefix echo-message\r\n' ) t.ok(sock.writes.join('').includes('CAP REQ')) sock.emit( 'data', ':irc.example.com CAP * ACK :sasl multi-prefix echo-message\r\n' ) sock.emit('data', ':irc.example.com CAP * ACK :sasl\r\n') sock.emit('data', ':irc.example.com 001 alice :Welcome to the network\r\n') t.is(registered, 'alice') t.is(c.state.nick, 'alice') }) test('PRIVMSG lands in channel buffer', async (t) => { const h = await load() const sock = fakeSocket() const c = h.bareIrcCreateClient({ nick: 'alice' }) c.attach(sock) sock.emit('data', ':bob!b@h PRIVMSG #chan :hello there\r\n') const buf = Object.values(c.state.buffers).find((b) => b.kind === 'channel') t.ok(buf) t.is(buf.name, '#chan') t.ok(buf.lines.some((l) => l.text === 'hello there' && l.from === 'bob')) }) test('SASL PLAIN payload is null-separated base64', async (t) => { const h = await load() const chunks = h.bareIrcSaslPlainChunks('user', 'pass') t.ok(chunks.length >= 1) const raw = Buffer.from(chunks.join(''), 'base64').toString('utf8') t.is(raw, '\0user\0pass') }) test('ISUPPORT tokens parse', async (t) => { const h = await load() const tok = h.bareIrcParseIsupport([ 'alice', 'CHANTYPES=#&', 'NICKLEN=16', 'are supported by this server' ]) t.is(tok.CHANTYPES, '#&') t.is(tok.NICKLEN, '16') })