76 lines
3.1 KiB
JavaScript
76 lines
3.1 KiB
JavaScript
// 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'); }
|
|
});
|
|
});
|