/** * API Keys Integration Tests * TDD: 🔴 RED → 🟢 GREEN → 🔵 REFACTOR */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import crypto from 'crypto'; // Mock database connection const TEST_DB_TYPE = process.env.DATABASE_TYPE || 'postgres'; describe('API Keys System', () => { describe('Key Hash Generation', () => { it('should generate consistent SHA-256 hash for API key', () => { const apiKey = 'sk-test1234567890abcdef12345678'; const hash1 = crypto.createHash('sha256').update(apiKey).digest('hex'); const hash2 = crypto.createHash('sha256').update(apiKey).digest('hex'); expect(hash1).toBe(hash2); expect(hash1.length).toBe(64); }); it('should extract 7-char prefix from API key', () => { const apiKey = 'sk-a6814fc13a53dd76a54e52eff53ca403'; const prefix = apiKey.substring(0, 7); expect(prefix).toBe('sk-a681'); expect(prefix.length).toBe(7); }); }); describe('API Key Validation', () => { it('should validate API key format (sk-XXXXXXXX)', () => { const validKey = 'sk-a6814fc13a53dd76a54e52eff53ca403'; const invalidKeys = [ 'invalid', 'pk-12345', 'sk-', '', null, undefined ]; expect(validKey.startsWith('sk-')).toBe(true); expect(validKey.length).toBe(35); // sk- + 32 hex chars invalidKeys.forEach(key => { expect(key?.startsWith?.('sk-') && key?.length === 35).toBeFalsy(); }); }); }); describe('API Key Storage', () => { it('should store key with prefix and hash, not plain text', async () => { const apiKey = 'sk-a6814fc13a53dd76a54e52eff53ca403'; const prefix = apiKey.substring(0, 7); const hash = crypto.createHash('sha256').update(apiKey).digest('hex'); // Simulate storage structure const storedKey = { key_prefix: prefix, key_hash: hash, // api_key should NOT be stored }; expect(storedKey.key_prefix).toBe('sk-a681'); expect(storedKey.key_hash).toBe(hash); expect(storedKey.api_key).toBeUndefined(); }); }); describe('API Key Authentication', () => { it('should authenticate valid key by matching prefix and hash', async () => { const apiKey = 'sk-a6814fc13a53dd76a54e52eff53ca403'; const storedPrefix = 'sk-a681'; const storedHash = crypto.createHash('sha256').update(apiKey).digest('hex'); // Simulate authentication const inputPrefix = apiKey.substring(0, 7); const inputHash = crypto.createHash('sha256').update(apiKey).digest('hex'); expect(inputPrefix).toBe(storedPrefix); expect(inputHash).toBe(storedHash); }); it('should reject key with wrong hash', async () => { const apiKey = 'sk-a6814fc13a53dd76a54e52eff53ca403'; const wrongKey = 'sk-a681000000000000000000000000000'; const correctHash = crypto.createHash('sha256').update(apiKey).digest('hex'); const wrongHash = crypto.createHash('sha256').update(wrongKey).digest('hex'); expect(correctHash).not.toBe(wrongHash); }); }); }); describe('API Keys Route', () => { describe('GET /api/v3/api-keys', () => { it('should require project_id parameter', async () => { // This test validates the route requires project_id const requiresProjectId = true; // Based on route code expect(requiresProjectId).toBe(true); }); it('should return empty array if no keys exist', async () => { const emptyResult = { success: true, data: [] }; expect(emptyResult.success).toBe(true); expect(emptyResult.data).toEqual([]); }); }); describe('POST /api/v3/api-keys', () => { it('should create API key and return full key only once', async () => { // Simulate key creation response const response = { success: true, data: { id: 1, key: 'sk-newkey123456789012345678901234', // Only returned once! key_prefix: 'sk-newk', name: 'Test Key', scopes: ['*'], is_active: true } }; expect(response.success).toBe(true); expect(response.data.key).toBeDefined(); expect(response.data.key_prefix).toBe(response.data.key.substring(0, 7)); }); it('should store key_hash but NOT the full key', async () => { const apiKey = 'sk-newkey123456789012345678901234'; // What gets stored in DB const dbRecord = { key_prefix: apiKey.substring(0, 7), key_hash: crypto.createHash('sha256').update(apiKey).digest('hex'), name: 'Test Key', scopes: JSON.stringify(['*']), is_active: 1 }; // Verify no plain key stored expect(dbRecord.api_key).toBeUndefined(); expect(dbRecord.key).toBeUndefined(); expect(dbRecord.key_hash.length).toBe(64); }); }); });