@prism-engineer/router
Version:
Type-safe Express.js router with automatic client generation
599 lines • 29.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const router_1 = require("../../router");
const path_1 = __importDefault(require("path"));
const promises_1 = __importDefault(require("fs/promises"));
(0, vitest_1.describe)('Frontend Client - Authentication', () => {
let tempDir;
let generatedClient;
let mockFetch;
(0, vitest_1.beforeEach)(async () => {
vitest_1.vi.clearAllMocks();
// Mock fetch globally
mockFetch = vitest_1.vi.fn();
global.fetch = mockFetch;
// Create temporary directory for generated client
tempDir = path_1.default.join(process.cwd(), 'temp-test-' + crypto.randomUUID());
await promises_1.default.mkdir(tempDir, { recursive: true });
// Generate client for testing
await router_1.router.compile({
outputDir: tempDir,
name: 'AuthClient',
baseUrl: 'http://localhost:3000',
routes: [{
directory: path_1.default.resolve(__dirname, '../../../dist/tests/router/fixtures/api'),
pattern: /.*\.js$/
}]
});
// Create a mock client with authentication handling
generatedClient = createMockAuthClient();
});
(0, vitest_1.afterEach)(async () => {
// Cleanup temporary directory
try {
await promises_1.default.rm(tempDir, { recursive: true, force: true });
}
catch {
// Ignore cleanup errors
}
vitest_1.vi.restoreAllMocks();
});
// Mock client factory for authentication testing
function createMockAuthClient() {
return {
api: {
public: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const response = {
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ message: 'Public endpoint' })
};
mockFetch.mockResolvedValueOnce(response);
await fetch('http://localhost:3000/api/public', {
method: 'GET',
headers: options.headers || {}
});
return { message: 'Public endpoint' };
})
},
protected: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const authHeader = options.headers?.authorization || options.headers?.Authorization;
if (!authHeader) {
const response = {
ok: false,
status: 401,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ error: 'Authorization required' })
};
mockFetch.mockResolvedValueOnce(response);
const fetchResponse = await fetch('http://localhost:3000/api/protected', {
method: 'GET',
headers: options.headers || {}
});
if (!fetchResponse.ok) {
throw new Error('Authorization required');
}
}
const response = {
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({
message: 'Protected endpoint',
user: { id: '1', name: 'John Doe' }
})
};
mockFetch.mockResolvedValueOnce(response);
await fetch('http://localhost:3000/api/protected', {
method: 'GET',
headers: options.headers || {}
});
return {
message: 'Protected endpoint',
user: { id: '1', name: 'John Doe' }
};
})
},
admin: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const authHeader = options.headers?.authorization || options.headers?.Authorization;
if (!authHeader?.includes('admin-token')) {
const response = {
ok: false,
status: 403,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ error: 'Insufficient permissions' })
};
mockFetch.mockResolvedValueOnce(response);
const fetchResponse = await fetch('http://localhost:3000/api/admin', {
method: 'GET',
headers: options.headers || {}
});
if (!fetchResponse.ok) {
throw new Error('Insufficient permissions');
}
}
const response = {
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({
message: 'Admin endpoint',
admin: { id: 'admin-1', permissions: ['read', 'write', 'delete'] }
})
};
mockFetch.mockResolvedValueOnce(response);
await fetch('http://localhost:3000/api/admin', {
method: 'GET',
headers: options.headers || {}
});
return {
message: 'Admin endpoint',
admin: { id: 'admin-1', permissions: ['read', 'write', 'delete'] }
};
})
},
apikey: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const apiKey = options.headers?.['x-api-key'] || options.headers?.['X-API-Key'];
if (!apiKey || apiKey !== 'valid-api-key') {
const response = {
ok: false,
status: 401,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ error: 'Invalid API key' })
};
mockFetch.mockResolvedValueOnce(response);
const fetchResponse = await fetch('http://localhost:3000/api/apikey', {
method: 'GET',
headers: options.headers || {}
});
if (!fetchResponse.ok) {
throw new Error('Invalid API key');
}
}
const response = {
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({
message: 'API key authenticated',
client: { id: 'client-1', name: 'Test Client' }
})
};
mockFetch.mockResolvedValueOnce(response);
await fetch('http://localhost:3000/api/apikey', {
method: 'GET',
headers: options.headers || {}
});
return {
message: 'API key authenticated',
client: { id: 'client-1', name: 'Test Client' }
};
})
},
session: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const cookie = options.headers?.cookie || options.headers?.Cookie;
const sessionId = cookie?.match(/sessionId=([^;]+)/)?.[1];
if (!sessionId || sessionId !== 'valid-session') {
const response = {
ok: false,
status: 401,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ error: 'Invalid session' })
};
mockFetch.mockResolvedValueOnce(response);
const fetchResponse = await fetch('http://localhost:3000/api/session', {
method: 'GET',
headers: options.headers || {}
});
if (!fetchResponse.ok) {
throw new Error('Invalid session');
}
}
const response = {
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({
message: 'Session authenticated',
session: { id: sessionId, userId: 'user-1' }
})
};
mockFetch.mockResolvedValueOnce(response);
await fetch('http://localhost:3000/api/session', {
method: 'GET',
headers: options.headers || {}
});
return {
message: 'Session authenticated',
session: { id: sessionId, userId: 'user-1' }
};
})
}
},
// Client configuration for authentication
setAuthToken: vitest_1.vi.fn().mockImplementation((token) => {
// Mock implementation for setting global auth token
generatedClient._authToken = token;
}),
setApiKey: vitest_1.vi.fn().mockImplementation((apiKey) => {
// Mock implementation for setting global API key
generatedClient._apiKey = apiKey;
}),
_authToken: null,
_apiKey: null
};
}
(0, vitest_1.it)('should access public endpoints without authentication', async () => {
const result = await generatedClient.api.public.get();
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/public', {
method: 'GET',
headers: {}
});
(0, vitest_1.expect)(result).toEqual({ message: 'Public endpoint' });
});
(0, vitest_1.it)('should handle Bearer token authentication', async () => {
const result = await generatedClient.api.protected.get({
headers: { 'Authorization': 'Bearer valid-token-123' }
});
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/protected', {
method: 'GET',
headers: { 'Authorization': 'Bearer valid-token-123' }
});
(0, vitest_1.expect)(result).toEqual({
message: 'Protected endpoint',
user: { id: '1', name: 'John Doe' }
});
});
(0, vitest_1.it)('should handle API key authentication', async () => {
const result = await generatedClient.api.apikey.get({
headers: { 'X-API-Key': 'valid-api-key' }
});
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/apikey', {
method: 'GET',
headers: { 'X-API-Key': 'valid-api-key' }
});
(0, vitest_1.expect)(result).toEqual({
message: 'API key authenticated',
client: { id: 'client-1', name: 'Test Client' }
});
});
(0, vitest_1.it)('should handle session-based authentication', async () => {
const result = await generatedClient.api.session.get({
headers: { 'Cookie': 'sessionId=valid-session; other=value' }
});
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/session', {
method: 'GET',
headers: { 'Cookie': 'sessionId=valid-session; other=value' }
});
(0, vitest_1.expect)(result).toEqual({
message: 'Session authenticated',
session: { id: 'valid-session', userId: 'user-1' }
});
});
(0, vitest_1.it)('should handle role-based authentication', async () => {
const result = await generatedClient.api.admin.get({
headers: { 'Authorization': 'Bearer admin-token-123' }
});
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/admin', {
method: 'GET',
headers: { 'Authorization': 'Bearer admin-token-123' }
});
(0, vitest_1.expect)(result).toEqual({
message: 'Admin endpoint',
admin: { id: 'admin-1', permissions: ['read', 'write', 'delete'] }
});
});
(0, vitest_1.it)('should handle authentication failures with 401 status', async () => {
await (0, vitest_1.expect)(generatedClient.api.protected.get()).rejects.toThrow('Authorization required');
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/protected', {
method: 'GET',
headers: {}
});
});
(0, vitest_1.it)('should handle authorization failures with 403 status', async () => {
await (0, vitest_1.expect)(generatedClient.api.admin.get({
headers: { 'Authorization': 'Bearer user-token-123' }
})).rejects.toThrow('Insufficient permissions');
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/admin', {
method: 'GET',
headers: { 'Authorization': 'Bearer user-token-123' }
});
});
(0, vitest_1.it)('should handle invalid API key errors', async () => {
await (0, vitest_1.expect)(generatedClient.api.apikey.get({
headers: { 'X-API-Key': 'invalid-key' }
})).rejects.toThrow('Invalid API key');
});
(0, vitest_1.it)('should handle invalid session errors', async () => {
await (0, vitest_1.expect)(generatedClient.api.session.get({
headers: { 'Cookie': 'sessionId=invalid-session' }
})).rejects.toThrow('Invalid session');
});
(0, vitest_1.it)('should handle multiple authentication headers', async () => {
const result = await generatedClient.api.protected.get({
headers: {
'Authorization': 'Bearer token-123',
'X-API-Key': 'api-key-456',
'X-User-ID': 'user-789'
}
});
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/protected', {
method: 'GET',
headers: {
'Authorization': 'Bearer token-123',
'X-API-Key': 'api-key-456',
'X-User-ID': 'user-789'
}
});
(0, vitest_1.expect)(result).toEqual({
message: 'Protected endpoint',
user: { id: '1', name: 'John Doe' }
});
});
(0, vitest_1.it)('should handle client-level authentication configuration', () => {
// Test setting global auth token
generatedClient.setAuthToken('global-token-123');
(0, vitest_1.expect)(generatedClient._authToken).toBe('global-token-123');
// Test setting global API key
generatedClient.setApiKey('global-api-key-456');
(0, vitest_1.expect)(generatedClient._apiKey).toBe('global-api-key-456');
});
(0, vitest_1.it)('should handle mixed authentication schemes', async () => {
// Test that different endpoints can use different auth schemes
const publicResult = await generatedClient.api.public.get();
(0, vitest_1.expect)(publicResult.message).toBe('Public endpoint');
const protectedResult = await generatedClient.api.protected.get({
headers: { 'Authorization': 'Bearer token-123' }
});
(0, vitest_1.expect)(protectedResult.message).toBe('Protected endpoint');
const apiKeyResult = await generatedClient.api.apikey.get({
headers: { 'X-API-Key': 'valid-api-key' }
});
(0, vitest_1.expect)(apiKeyResult.message).toBe('API key authenticated');
});
(0, vitest_1.it)('should handle authentication with custom headers', async () => {
const customAuthClient = {
api: {
custom: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const signature = options.headers?.['X-Signature'];
const timestamp = options.headers?.['X-Timestamp'];
if (!signature || !timestamp) {
throw new Error('Custom authentication failed');
}
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ message: 'Custom auth success' })
});
await fetch('http://localhost:3000/api/custom', {
method: 'GET',
headers: options.headers || {}
});
return { message: 'Custom auth success' };
})
}
}
};
const result = await customAuthClient.api.custom.get({
headers: {
'X-Signature': 'signature-abc123',
'X-Timestamp': '1640995200'
}
});
(0, vitest_1.expect)(result).toEqual({ message: 'Custom auth success' });
});
(0, vitest_1.it)('should handle JWT token authentication', async () => {
const jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const result = await generatedClient.api.protected.get({
headers: { 'Authorization': `Bearer ${jwtToken}` }
});
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/protected', {
method: 'GET',
headers: { 'Authorization': `Bearer ${jwtToken}` }
});
(0, vitest_1.expect)(result).toEqual({
message: 'Protected endpoint',
user: { id: '1', name: 'John Doe' }
});
});
(0, vitest_1.it)('should handle authentication with refresh tokens', async () => {
const refreshClient = {
api: {
refresh: {
post: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const refreshToken = options.body?.refreshToken;
if (!refreshToken || refreshToken !== 'valid-refresh-token') {
throw new Error('Invalid refresh token');
}
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({
accessToken: 'new-access-token-123',
refreshToken: 'new-refresh-token-456',
expiresIn: 3600
})
});
await fetch('http://localhost:3000/api/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options.headers
},
body: JSON.stringify(options.body)
});
return {
accessToken: 'new-access-token-123',
refreshToken: 'new-refresh-token-456',
expiresIn: 3600
};
})
}
}
};
const result = await refreshClient.api.refresh.post({
body: { refreshToken: 'valid-refresh-token' }
});
(0, vitest_1.expect)(result).toEqual({
accessToken: 'new-access-token-123',
refreshToken: 'new-refresh-token-456',
expiresIn: 3600
});
});
(0, vitest_1.it)('should handle authentication errors with detailed error messages', async () => {
const detailedErrorClient = {
api: {
detailed: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
const authHeader = options.headers?.authorization;
if (!authHeader) {
const response = {
ok: false,
status: 401,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({
error: 'MISSING_AUTHORIZATION',
message: 'Authorization header is required',
code: 'AUTH_001'
})
};
mockFetch.mockResolvedValueOnce(response);
const fetchResponse = await fetch('http://localhost:3000/api/detailed');
const errorData = await fetchResponse.json();
throw new Error(`${errorData.error}: ${errorData.message}`);
}
return { message: 'Success' };
})
}
}
};
await (0, vitest_1.expect)(detailedErrorClient.api.detailed.get()).rejects.toThrow('MISSING_AUTHORIZATION: Authorization header is required');
});
(0, vitest_1.it)('should handle authentication with CORS preflight requests', async () => {
const corsClient = {
api: {
cors: {
options: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
headers: new Map([
['Access-Control-Allow-Origin', '*'],
['Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'],
['Access-Control-Allow-Headers', 'Authorization, Content-Type, X-API-Key']
])
});
await fetch('http://localhost:3000/api/cors', {
method: 'OPTIONS',
headers: options.headers || {}
});
return {};
}),
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
headers: new Map([
['content-type', 'application/json'],
['Access-Control-Allow-Origin', '*']
]),
json: vitest_1.vi.fn().mockResolvedValue({ message: 'CORS authenticated' })
});
await fetch('http://localhost:3000/api/cors', {
method: 'GET',
headers: {
'Authorization': 'Bearer token-123',
...options.headers
}
});
return { message: 'CORS authenticated' };
})
}
}
};
// Test OPTIONS preflight request
await corsClient.api.cors.options();
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/cors', {
method: 'OPTIONS',
headers: {}
});
// Test actual GET request with auth
const result = await corsClient.api.cors.get();
(0, vitest_1.expect)(result).toEqual({ message: 'CORS authenticated' });
});
(0, vitest_1.it)('should handle concurrent authenticated requests', async () => {
const promises = [
generatedClient.api.protected.get({
headers: { 'Authorization': 'Bearer token-1' }
}),
generatedClient.api.apikey.get({
headers: { 'X-API-Key': 'valid-api-key' }
}),
generatedClient.api.session.get({
headers: { 'Cookie': 'sessionId=valid-session' }
})
];
const results = await Promise.all(promises);
(0, vitest_1.expect)(results).toHaveLength(3);
(0, vitest_1.expect)(results[0].message).toBe('Protected endpoint');
(0, vitest_1.expect)(results[1].message).toBe('API key authenticated');
(0, vitest_1.expect)(results[2].message).toBe('Session authenticated');
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledTimes(3);
});
(0, vitest_1.it)('should handle authentication with request interceptors', async () => {
const interceptorClient = {
_interceptors: {
request: [(config) => {
// Add global auth header if not present
if (!config.headers.Authorization && generatedClient._authToken) {
config.headers.Authorization = `Bearer ${generatedClient._authToken}`;
}
return config;
}]
},
api: {
intercepted: {
get: vitest_1.vi.fn().mockImplementation(async (options = {}) => {
// Simulate interceptor execution
const config = { headers: options.headers || {} };
interceptorClient._interceptors.request.forEach(interceptor => {
interceptor(config);
});
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
headers: new Map([['content-type', 'application/json']]),
json: vitest_1.vi.fn().mockResolvedValue({ message: 'Intercepted request' })
});
await fetch('http://localhost:3000/api/intercepted', {
method: 'GET',
headers: config.headers
});
return { message: 'Intercepted request' };
})
}
}
};
// Set global auth token
generatedClient._authToken = 'interceptor-token-123';
await interceptorClient.api.intercepted.get();
(0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/intercepted', {
method: 'GET',
headers: { Authorization: 'Bearer interceptor-token-123' }
});
});
});
//# sourceMappingURL=client-authentication.test.js.map