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')
  })
})
