import {describe, it, expect} from 'vitest';
import {encryptToken, decryptToken, parseEncryptionKey, TokenPayload} from './token-encryption.js';
import {randomBytes} from 'crypto';
describe('token-encryption', () => {
const testSecret = randomBytes(32);
const testPayload: TokenPayload = {
access_token: 'ya29.test-access-token',
refresh_token: '1//test-refresh-token',
expiry_date: Date.now() + 3600000,
email: 'test@example.com',
};
describe('encryptToken and decryptToken', () => {
it('encrypts and decrypts payload correctly', async () => {
const encrypted = await encryptToken(testPayload, testSecret);
const decrypted = await decryptToken(encrypted, testSecret);
expect(decrypted.access_token).toBe(testPayload.access_token);
expect(decrypted.refresh_token).toBe(testPayload.refresh_token);
expect(decrypted.expiry_date).toBe(testPayload.expiry_date);
expect(decrypted.email).toBe(testPayload.email);
});
it('produces JWE format with 5 parts', async () => {
const encrypted = await encryptToken(testPayload, testSecret);
const parts = encrypted.split('.');
expect(parts.length).toBe(5);
});
it('includes iat claim', async () => {
const before = Math.floor(Date.now() / 1000);
const encrypted = await encryptToken(testPayload, testSecret);
const after = Math.floor(Date.now() / 1000);
const decrypted = await decryptToken(encrypted, testSecret);
const iat = (decrypted as unknown as {iat: number}).iat;
expect(iat).toBeGreaterThanOrEqual(before);
expect(iat).toBeLessThanOrEqual(after);
});
it('fails with wrong key', async () => {
const encrypted = await encryptToken(testPayload, testSecret);
const wrongSecret = randomBytes(32);
await expect(decryptToken(encrypted, wrongSecret)).rejects.toThrow();
});
it('fails with tampered token', async () => {
const encrypted = await encryptToken(testPayload, testSecret);
const tampered = encrypted.slice(0, -5) + 'XXXXX';
await expect(decryptToken(tampered, testSecret)).rejects.toThrow();
});
});
describe('parseEncryptionKey', () => {
it('parses 64-character hex string', () => {
const hexKey = 'a'.repeat(64);
const result = parseEncryptionKey(hexKey);
expect(result.length).toBe(32);
expect(result).toBeInstanceOf(Uint8Array);
});
it('parses base64 encoded 32-byte key', () => {
const key = randomBytes(32);
const base64Key = key.toString('base64');
const result = parseEncryptionKey(base64Key);
expect(result.length).toBe(32);
expect(Buffer.from(result).equals(key)).toBe(true);
});
it('throws for invalid key length', () => {
expect(() => parseEncryptionKey('tooshort')).toThrow(
'TOKEN_ENCRYPTION_KEY must be 32 bytes'
);
});
it('throws for invalid hex characters', () => {
const invalidHex = 'g'.repeat(64);
expect(() => parseEncryptionKey(invalidHex)).toThrow();
});
});
});