// test/encryption.js - Tests for lib/encryption.cjs
const test = require('brittle')
const { encrypt, decrypt, isValidFormat } = require('../lib/encryption.cjs')
const { eventFlush } = require('./helpers/index.cjs')
test('encrypt produces valid base64', async function (t) {
const plaintext = 'test secret message'
const password = 'strong-password-123'
const encrypted = encrypt(plaintext, password)
t.ok(encrypted, 'encrypted output exists')
t.ok(typeof encrypted === 'string', 'encrypted is string')
t.ok(encrypted.length > plaintext.length, 'encrypted is longer than plaintext')
t.ok(/^[A-Za-z0-9+/=]+$/.test(encrypted), 'is valid base64')
const b4a = require('b4a')
const decoded = b4a.from(encrypted, 'base64')
t.ok(decoded.length >= 60, 'decoded has minimum length (salt 32 + iv 12 + tag 16)')
})
test('decrypt with correct password', async function (t) {
const plaintext = 'my secret wallet mnemonic'
const password = 'correct-password'
const encrypted = encrypt(plaintext, password)
const decrypted = decrypt(encrypted, password)
t.is(decrypted, plaintext, 'decrypted matches original')
})
test('decrypt throws with wrong password', async function (t) {
const plaintext = 'sensitive data'
const password = 'correct-password'
const wrongPassword = 'wrong-password'
const encrypted = encrypt(plaintext, password)
await t.exception(() => {
decrypt(encrypted, wrongPassword)
}, 'throws with wrong password')
})
test('decrypt throws with corrupted ciphertext', async function (t) {
const plaintext = 'test data'
const password = 'password123'
const encrypted = encrypt(plaintext, password)
const corrupted = encrypted.slice(0, -5) + 'XXXXX'
await t.exception(() => {
decrypt(corrupted, password)
}, 'throws with corrupted data')
})
test('decrypt throws with truncated data', async function (t) {
const plaintext = 'test'
const password = 'pass'
const encrypted = encrypt(plaintext, password)
const truncated = encrypted.slice(0, -20)
await t.exception(() => {
decrypt(truncated, password)
}, 'throws with truncated data')
})
test('decrypt throws with invalid base64', async function (t) {
const invalid = 'not-valid-base64!!!'
await t.exception(() => {
decrypt(invalid, 'password')
}, 'throws with invalid base64')
})
test('isValidFormat validates correct format', async function (t) {
const plaintext = 'test'
const password = 'password'
const encrypted = encrypt(plaintext, password)
t.ok(isValidFormat(encrypted), 'recognizes valid format')
t.absent(isValidFormat(''), 'rejects empty string')
t.absent(isValidFormat('!!!###'), 'rejects invalid base64')
})
test('round-trip encryption', async function (t) {
const testCases = [
'simple text',
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'🔐 Unicode emoji test 日本語',
'a'.repeat(1000),
''
]
const password = 'test-password-xyz'
for (const plaintext of testCases) {
const encrypted = encrypt(plaintext, password)
const decrypted = decrypt(encrypted, password)
t.is(decrypted, plaintext, `round-trip: "${plaintext.slice(0, 20)}"`)
}
})
test('different passwords produce different ciphertexts', async function (t) {
const plaintext = 'same message'
const password1 = 'password-one'
const password2 = 'password-two'
const encrypted1 = encrypt(plaintext, password1)
const encrypted2 = encrypt(plaintext, password2)
t.not(encrypted1, encrypted2, 'different ciphertexts')
t.is(decrypt(encrypted1, password1), plaintext, 'first decrypts')
t.is(decrypt(encrypted2, password2), plaintext, 'second decrypts')
})
test('random IV makes ciphertexts different', async function (t) {
const plaintext = 'repeated message'
const password = 'same-password'
const encrypted1 = encrypt(plaintext, password)
const encrypted2 = encrypt(plaintext, password)
t.not(encrypted1, encrypted2, 'random IV changes output')
t.is(decrypt(encrypted1, password), plaintext, 'first decrypts')
t.is(decrypt(encrypted2, password), plaintext, 'second decrypts')
})