// test/input-sanitizer.cjs - Tests for lib/input-sanitizer.cjs
const test = require('brittle')
const {
sanitizeContactName,
sanitizeDeviceName,
sanitizeNotes,
validateChain,
validateNetwork,
sanitizeHex,
validateInviteCode
} = require('../lib/input-sanitizer.cjs')
test('sanitizeContactName enforces 100 char limit', function (t) {
const long = 'a'.repeat(200)
const sanitized = sanitizeContactName(long)
t.is(sanitized.length, 100, 'truncates to 100 chars')
})
test('sanitizeContactName allows safe characters', function (t) {
const safe = "John Doe-Smith (Main Wallet) 123"
const sanitized = sanitizeContactName(safe)
t.is(sanitized, safe, 'allows alphanumeric and safe chars')
})
test('sanitizeContactName removes null bytes', function (t) {
const malicious = "name\x00injected"
const sanitized = sanitizeContactName(malicious)
t.absent(sanitized.includes('\x00'), 'removes null bytes')
})
test('sanitizeDeviceName enforces 50 char limit', function (t) {
const long = 'a'.repeat(100)
const sanitized = sanitizeDeviceName(long)
t.is(sanitized.length, 50, 'truncates to 50 chars')
})
test('sanitizeNotes enforces 500 char limit', function (t) {
const long = 'a'.repeat(1000)
const sanitized = sanitizeNotes(long)
t.is(sanitized.length, 500, 'truncates to 500 chars')
})
test('sanitizeNotes blocks dangerous characters', function (t) {
const dangerous = "note with <script>alert('xss')</script> tags"
const sanitized = sanitizeNotes(dangerous)
t.absent(sanitized.includes('<'), 'removes <')
t.absent(sanitized.includes('>'), 'removes >')
})
test('validateChain whitelists bitcoin and ethereum', function (t) {
t.ok(validateChain('bitcoin'), 'allows bitcoin')
t.ok(validateChain('ethereum'), 'allows ethereum')
t.absent(validateChain('litecoin'), 'rejects litecoin')
t.absent(validateChain('bitcoin; DROP TABLE'), 'rejects injection')
})
test('validateNetwork whitelists known networks', function (t) {
t.ok(validateNetwork('testnet'), 'allows testnet')
t.ok(validateNetwork('mainnet'), 'allows mainnet')
t.ok(validateNetwork('sepolia'), 'allows sepolia')
t.absent(validateNetwork('custom'), 'rejects custom')
})
test('sanitizeHex allows only hex characters', function (t) {
const validHex = 'abcdef0123456789'
const sanitized = sanitizeHex(validHex)
t.is(sanitized, validHex, 'allows valid hex')
const invalid = 'abcdefGHIJKL'
const sanitized2 = sanitizeHex(invalid)
t.is(sanitized2, 'abcdef', 'removes non-hex')
})
test('validateInviteCode enforces length limits', function (t) {
const tooShort = 'a'.repeat(40)
const justRight = 'a'.repeat(100)
const tooLong = 'a'.repeat(1500)
t.absent(validateInviteCode(tooShort), 'rejects too short')
t.ok(validateInviteCode(justRight), 'allows valid length')
t.absent(validateInviteCode(tooLong), 'rejects too long')
})
test('all sanitizers reject non-string inputs', function (t) {
t.exception(() => sanitizeContactName(123), 'contact name rejects number')
t.exception(() => sanitizeDeviceName(null), 'device name rejects null')
t.exception(() => sanitizeNotes(undefined), 'notes rejects undefined')
})