import test from 'brittle'
import { Vault, generateKey, deriveKey } from '../index.js'
import b4a from 'b4a'
import { tmpdir } from 'os'
import { join } from 'path'
import { randomBytes } from 'crypto'
function tmpStorage () {
return join(tmpdir(), 'bridge-vault-test-' + randomBytes(8).toString('hex'))
}
test('Vault - put and get credential', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.put('irc', 'libera', {
server: 'irc.libera.chat',
port: 6697,
nick: 'testbot',
password: 'secret123'
})
const cred = await vault.get('irc', 'libera')
t.is(cred.server, 'irc.libera.chat')
t.is(cred.port, 6697)
t.is(cred.nick, 'testbot')
t.is(cred.password, 'secret123')
await vault.close()
})
test('Vault - get nonexistent returns null', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
const cred = await vault.get('irc', 'nonexistent')
t.is(cred, null)
await vault.close()
})
test('Vault - delete credential', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.put('discord', 'mybot', { token: 'abc123' })
t.ok(await vault.get('discord', 'mybot'))
await vault.del('discord', 'mybot')
t.is(await vault.get('discord', 'mybot'), null)
await vault.close()
})
test('Vault - update credential', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.put('irc', 'server1', { nick: 'old' })
await vault.put('irc', 'server1', { nick: 'new' })
const cred = await vault.get('irc', 'server1')
t.is(cred.nick, 'new')
await vault.close()
})
test('Vault - list credentials for platform', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.put('irc', 'libera', { nick: 'bot1' })
await vault.put('irc', 'oftc', { nick: 'bot2' })
await vault.put('discord', 'server1', { token: 'xyz' })
const ircCreds = await vault.list('irc')
t.is(ircCreds.length, 2)
t.ok(ircCreds.find(c => c.name === 'libera'))
t.ok(ircCreds.find(c => c.name === 'oftc'))
const discordCreds = await vault.list('discord')
t.is(discordCreds.length, 1)
t.is(discordCreds[0].name, 'server1')
await vault.close()
})
test('Vault - listPlatforms', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.put('irc', 'a', { x: 1 })
await vault.put('discord', 'b', { x: 2 })
await vault.put('matrix', 'c', { x: 3 })
const platforms = await vault.listPlatforms()
t.is(platforms.length, 3)
t.ok(platforms.includes('irc'))
t.ok(platforms.includes('discord'))
t.ok(platforms.includes('matrix'))
await vault.close()
})
test('Vault - encryption works (wrong key fails)', async (t) => {
const storage = tmpStorage()
const key1 = generateKey()
const key2 = generateKey()
const vault1 = new Vault(storage, { key: key1 })
await vault1.ready()
await vault1.put('irc', 'test', { secret: 'hidden' })
await vault1.close()
const vault2 = new Vault(storage, { key: key2 })
await vault2.ready()
try {
await vault2.get('irc', 'test')
t.fail('should have thrown')
} catch (err) {
t.ok(err.message.includes('Decryption failed'), 'wrong key throws')
}
await vault2.close()
})
test('Vault - same key reopens vault', async (t) => {
const storage = tmpStorage()
const key = generateKey()
const vault1 = new Vault(storage, { key })
await vault1.ready()
await vault1.put('irc', 'test', { value: 42 })
await vault1.close()
const vault2 = new Vault(storage, { key })
await vault2.ready()
const cred = await vault2.get('irc', 'test')
t.is(cred.value, 42)
await vault2.close()
})
test('Vault - complex credential values', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.put('mastodon', 'instance1', {
baseUrl: 'https://mastodon.social',
accessToken: 'token123',
scopes: ['read', 'write', 'push'],
metadata: {
createdAt: '2024-01-01',
expiresAt: null,
refreshToken: 'refresh456'
}
})
const cred = await vault.get('mastodon', 'instance1')
t.is(cred.baseUrl, 'https://mastodon.social')
t.alike(cred.scopes, ['read', 'write', 'push'])
t.is(cred.metadata.refreshToken, 'refresh456')
t.is(cred.metadata.expiresAt, null)
await vault.close()
})
test('generateKey - returns buffer of correct size', (t) => {
const key = generateKey()
t.ok(b4a.isBuffer(key))
t.is(key.length, 32)
})
test('deriveKey - derives from password', (t) => {
const { key, salt } = deriveKey('my-password')
t.ok(b4a.isBuffer(key))
t.is(key.length, 32)
t.ok(b4a.isBuffer(salt))
})
test('deriveKey - same password + salt gives same key', (t) => {
const { key: key1, salt } = deriveKey('password')
const { key: key2 } = deriveKey('password', salt)
t.ok(b4a.equals(key1, key2))
})
test('deriveKey - different passwords give different keys', (t) => {
const { key: key1, salt } = deriveKey('password1')
const { key: key2 } = deriveKey('password2', salt)
t.absent(b4a.equals(key1, key2))
})
test('Vault - empty list returns empty array', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
const creds = await vault.list('nonexistent')
t.alike(creds, [])
await vault.close()
})
test('Vault - ready is idempotent', async (t) => {
const vault = new Vault(tmpStorage())
await vault.ready()
await vault.ready()
await vault.ready()
await vault.put('irc', 'test', { ok: true })
const cred = await vault.get('irc', 'test')
t.is(cred.ok, true)
await vault.close()
})