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