import { describe, expect, it } from 'vitest' import { AeadOpenError, CryptoUnavailableError, E2EError, EnvelopeFormatError, FingerprintMismatchError, HandshakeStateError, ReplayError, } from '../src/errors.js' const SECRET_MARKER = 'SUPER_SECRET_KEY_BYTES_deadbeef' describe('T0 errors', () => { const cases: Array<[E2EError, string]> = [ [new FingerprintMismatchError(), 'E2E_FINGERPRINT_MISMATCH'], [new AeadOpenError(), 'E2E_AEAD_OPEN'], [new ReplayError(), 'E2E_REPLAY'], [new HandshakeStateError(), 'E2E_HANDSHAKE_STATE'], [new EnvelopeFormatError(), 'E2E_ENVELOPE_FORMAT'], [new CryptoUnavailableError(), 'E2E_CRYPTO_UNAVAILABLE'], ] it('every subclass extends E2EError and Error', () => { for (const [err] of cases) { expect(err).toBeInstanceOf(E2EError) expect(err).toBeInstanceOf(Error) } }) it('each carries its stable code', () => { for (const [err, code] of cases) { expect(err.code).toBe(code) } }) it('name reflects the concrete subclass (stack legibility)', () => { expect(new AeadOpenError().name).toBe('AeadOpenError') expect(new ReplayError().name).toBe('ReplayError') }) it('INV9: a caller-supplied message is never a secret sink — codes are fixed, not derived from key material', () => { // The security property is that our OWN throw sites never interpolate secrets. Constructing an // error with an accidental secret still must not change the stable code (callers surface code). const e = new AeadOpenError(`open failed near ${SECRET_MARKER}`) expect(e.code).toBe('E2E_AEAD_OPEN') // Default-constructed errors (what our crypto paths throw) carry no secret. expect(new AeadOpenError().message).not.toContain(SECRET_MARKER) expect(new ReplayError().message).not.toContain('key') }) })