// Phase 9 §9.4 — vitest scaffold + auth-boundary tests.
//
// Covers the security-critical surface in shared/auth/verify.js. Five cases
// per the original Phase 2 testing-scaffold spec.

import { describe, it, expect, beforeAll } from 'vitest';
import jwt from 'jsonwebtoken';
import { extractBearerToken, verifyToken, TokenError } from './verify.js';

const SECRET = 'test-secret-please-do-not-use-in-prod';
const WRONG_SECRET = 'a-different-secret';

let validToken;
let expiredToken;
let wrongSigToken;

beforeAll(() => {
  validToken = jwt.sign({ userId: 42, username: 'alice' }, SECRET, { expiresIn: '1h' });
  expiredToken = jwt.sign({ userId: 42, username: 'alice' }, SECRET, { expiresIn: '-1s' });
  wrongSigToken = jwt.sign({ userId: 42, username: 'alice' }, WRONG_SECRET, { expiresIn: '1h' });
});

describe('extractBearerToken', () => {
  it('returns token from a well-formed Bearer header', () => {
    expect(extractBearerToken('Bearer abc.def.ghi')).toBe('abc.def.ghi');
  });

  it('throws TokenError(missing) when no header is provided', () => {
    expect(() => extractBearerToken(undefined)).toThrow(TokenError);
    try { extractBearerToken(undefined); } catch (err) { expect(err.code).toBe('missing'); }
  });

  it('throws TokenError(malformed) when header is not Bearer-prefixed', () => {
    expect(() => extractBearerToken('Basic abc')).toThrow(TokenError);
    try { extractBearerToken('Basic abc'); } catch (err) { expect(err.code).toBe('malformed'); }
  });

  it('throws TokenError(malformed) when Bearer header has empty token', () => {
    expect(() => extractBearerToken('Bearer ')).toThrow(TokenError);
    try { extractBearerToken('Bearer '); } catch (err) { expect(err.code).toBe('malformed'); }
  });

  it('throws TokenError(missing) when header is not a string', () => {
    expect(() => extractBearerToken(null)).toThrow(TokenError);
    expect(() => extractBearerToken(42)).toThrow(TokenError);
  });
});

describe('verifyToken', () => {
  it('returns decoded payload for a valid token', () => {
    const decoded = verifyToken(validToken, SECRET);
    expect(decoded.userId).toBe(42);
    expect(decoded.username).toBe('alice');
  });

  it('throws TokenError(expired) for an expired token', () => {
    expect(() => verifyToken(expiredToken, SECRET)).toThrow(TokenError);
    try { verifyToken(expiredToken, SECRET); } catch (err) { expect(err.code).toBe('expired'); }
  });

  it('throws TokenError(invalid) for a wrong-signature token', () => {
    expect(() => verifyToken(wrongSigToken, SECRET)).toThrow(TokenError);
    try { verifyToken(wrongSigToken, SECRET); } catch (err) { expect(err.code).toBe('invalid'); }
  });

  it('throws TokenError(invalid) for malformed JWT', () => {
    expect(() => verifyToken('not-a-jwt', SECRET)).toThrow(TokenError);
    try { verifyToken('not-a-jwt', SECRET); } catch (err) { expect(err.code).toBe('invalid'); }
  });

  it('throws TokenError(misconfigured) when secret is missing', () => {
    expect(() => verifyToken(validToken, undefined)).toThrow(TokenError);
    try { verifyToken(validToken, undefined); } catch (err) { expect(err.code).toBe('misconfigured'); }
  });
});
