import test from 'brittle'
import { createEnvelope, TYPES, PLATFORMS, encode, decode, envelopeCodec } from '../index.js'
import cenc from 'compact-encoding'
test('createEnvelope - basic text message', (t) => {
const env = createEnvelope({
type: TYPES.TEXT,
platform: PLATFORMS.IRC,
author: 'testuser',
content: 'Hello from IRC!',
channel: '#test'
})
t.is(env.type, TYPES.TEXT)
t.is(env.platform, PLATFORMS.IRC)
t.is(env.author, 'testuser')
t.is(env.content, 'Hello from IRC!')
t.is(env.channel, '#test')
t.ok(env.id, 'has content-hash id')
t.ok(env.timestamp > 0, 'has timestamp')
})
test('createEnvelope - defaults', (t) => {
const env = createEnvelope({ content: 'test' })
t.is(env.type, TYPES.TEXT)
t.is(env.platform, PLATFORMS.KEET)
t.is(env.author, '')
t.is(env.replyTo, '')
t.is(env.mediaUrl, '')
t.is(env.editOf, '')
t.is(env.deleteOf, '')
})
test('createEnvelope - custom id', (t) => {
const env = createEnvelope({ content: 'test', id: 'custom-123' })
t.is(env.id, 'custom-123')
})
test('createEnvelope - content hash is deterministic', (t) => {
const ts = Date.now()
const a = createEnvelope({ platform: PLATFORMS.IRC, author: 'u', content: 'msg', timestamp: ts })
const b = createEnvelope({ platform: PLATFORMS.IRC, author: 'u', content: 'msg', timestamp: ts })
t.is(a.id, b.id)
})
test('createEnvelope - different content gives different hash', (t) => {
const ts = Date.now()
const a = createEnvelope({ author: 'u', content: 'msg1', timestamp: ts })
const b = createEnvelope({ author: 'u', content: 'msg2', timestamp: ts })
t.not(a.id, b.id)
})
test('createEnvelope - all message types', (t) => {
for (const [name, value] of Object.entries(TYPES)) {
const env = createEnvelope({ type: value, content: `${name} message` })
t.is(env.type, value, `type ${name} = ${value}`)
}
})
test('createEnvelope - all platforms', (t) => {
for (const [name, value] of Object.entries(PLATFORMS)) {
const env = createEnvelope({ platform: value, content: `from ${name}` })
t.is(env.platform, value, `platform ${name} = ${value}`)
}
})
test('createEnvelope - media message', (t) => {
const env = createEnvelope({
type: TYPES.MEDIA,
platform: PLATFORMS.DISCORD,
author: 'user#1234',
content: 'Check this out',
mediaUrl: 'https://cdn.discord.com/attachments/abc.png',
mediaType: 'image/png'
})
t.is(env.type, TYPES.MEDIA)
t.is(env.mediaUrl, 'https://cdn.discord.com/attachments/abc.png')
t.is(env.mediaType, 'image/png')
})
test('createEnvelope - reply message', (t) => {
const original = createEnvelope({ content: 'original' })
const reply = createEnvelope({
type: TYPES.REPLY,
content: 'reply to that',
replyTo: original.id
})
t.is(reply.type, TYPES.REPLY)
t.is(reply.replyTo, original.id)
})
test('createEnvelope - edit message', (t) => {
const original = createEnvelope({ content: 'typo' })
const edit = createEnvelope({
type: TYPES.EDIT,
content: 'fixed',
editOf: original.id
})
t.is(edit.type, TYPES.EDIT)
t.is(edit.editOf, original.id)
})
test('createEnvelope - delete message', (t) => {
const original = createEnvelope({ content: 'oops' })
const del = createEnvelope({
type: TYPES.DELETE,
deleteOf: original.id
})
t.is(del.type, TYPES.DELETE)
t.is(del.deleteOf, original.id)
})
test('codec - encode/decode roundtrip', (t) => {
const env = createEnvelope({
type: TYPES.TEXT,
platform: PLATFORMS.IRC,
author: 'testuser',
content: 'Hello world!',
channel: '#general',
timestamp: 1707000000000
})
const buf = encode(env)
t.ok(Buffer.isBuffer(buf), 'encoded to buffer')
t.ok(buf.length > 0, 'buffer has content')
const decoded = decode(buf)
t.alike(decoded, env, 'roundtrip preserves all fields')
})
test('codec - roundtrip all message types', (t) => {
for (const [name, type] of Object.entries(TYPES)) {
const env = createEnvelope({
type,
platform: PLATFORMS.KEET,
author: 'user',
content: `${name} content`,
channel: 'room1'
})
const decoded = decode(encode(env))
t.is(decoded.type, type, `roundtrip for ${name}`)
t.is(decoded.content, env.content)
}
})
test('codec - roundtrip with all fields populated', (t) => {
const env = createEnvelope({
type: TYPES.REPLY,
platform: PLATFORMS.DISCORD,
author: 'alice#1234',
content: 'replying with image',
channel: 'discord-channel-123',
replyTo: 'abc123',
mediaUrl: 'https://example.com/image.png',
mediaType: 'image/png',
editOf: 'def456',
deleteOf: 'ghi789',
id: 'custom-id-000',
timestamp: 1707123456789
})
const decoded = decode(encode(env))
t.alike(decoded, env, 'all fields preserved')
})
test('codec - empty strings handled correctly', (t) => {
const env = createEnvelope({ content: '' })
const decoded = decode(encode(env))
t.is(decoded.content, '')
t.is(decoded.replyTo, '')
t.is(decoded.mediaUrl, '')
})
test('codec - unicode content', (t) => {
const env = createEnvelope({
author: 'ユーザー',
content: '🎉 Hello 世界! مرحبا'
})
const decoded = decode(encode(env))
t.is(decoded.author, 'ユーザー')
t.is(decoded.content, '🎉 Hello 世界! مرحبا')
})
test('codec - large content', (t) => {
const content = 'x'.repeat(10000)
const env = createEnvelope({ content })
const decoded = decode(encode(env))
t.is(decoded.content, content)
})
test('codec - compact size is smaller than JSON', (t) => {
const env = createEnvelope({
type: TYPES.TEXT,
platform: PLATFORMS.IRC,
author: 'user',
content: 'Hello world',
channel: '#test'
})
const compactBuf = encode(env)
const jsonBuf = Buffer.from(JSON.stringify(env))
t.ok(compactBuf.length < jsonBuf.length, `compact (${compactBuf.length}) < JSON (${jsonBuf.length})`)
})
test('codec - works with cenc.encode/decode directly', (t) => {
const env = createEnvelope({ content: 'direct codec usage' })
const buf = cenc.encode(envelopeCodec, env)
const decoded = cenc.decode(envelopeCodec, buf)
t.alike(decoded, env)
})
test('TYPES constants', (t) => {
t.is(TYPES.TEXT, 0)
t.is(TYPES.MEDIA, 1)
t.is(TYPES.REACTION, 2)
t.is(TYPES.EDIT, 3)
t.is(TYPES.DELETE, 4)
t.is(TYPES.SYSTEM, 5)
t.is(TYPES.REPLY, 6)
})
test('PLATFORMS constants', (t) => {
t.is(PLATFORMS.KEET, 0)
t.is(PLATFORMS.IRC, 1)
t.is(PLATFORMS.DISCORD, 2)
t.is(PLATFORMS.MATRIX, 3)
t.is(PLATFORMS.MASTODON, 4)
t.is(PLATFORMS.EMAIL, 5)
t.is(PLATFORMS.ANDROID, 6)
})