UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

401 lines 20.1 kB
"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 - Method Calls', () => { 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: 'TestClient', baseUrl: 'http://localhost:3000', routes: [{ directory: path_1.default.resolve(__dirname, '../../../dist/tests/router/fixtures/api'), pattern: /.*\.js$/ }] }); // Create a mock client with the expected structure generatedClient = createMockClient(); }); (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 testing function createMockClient() { return { api: { hello: { get: vitest_1.vi.fn().mockImplementation(async (options = {}) => { const url = 'http://localhost:3000/api/hello'; mockFetch.mockResolvedValueOnce({ ok: true, status: 200, headers: new Map([['content-type', 'application/json']]), json: async () => ({ message: 'Hello World' }) }); await fetch(url, { method: 'GET', headers: options.headers || {} }); return { message: 'Hello World' }; }) }, users: { get: vitest_1.vi.fn().mockImplementation(async (options = {}) => { const url = new URL('http://localhost:3000/api/users'); if (options.query) { Object.entries(options.query).forEach(([key, value]) => { url.searchParams.append(key, String(value)); }); } mockFetch.mockResolvedValueOnce({ ok: true, status: 200, headers: new Map([['content-type', 'application/json']]), json: async () => [{ id: 1, name: 'John Doe', email: 'john@example.com' }] }); await fetch(url.toString(), { method: 'GET', headers: options.headers || {} }); return [{ id: 1, name: 'John Doe', email: 'john@example.com' }]; }), post: vitest_1.vi.fn().mockImplementation(async (options = {}) => { mockFetch.mockResolvedValueOnce({ ok: true, status: 201, headers: new Map([['content-type', 'application/json']]), json: async () => ({ id: 2, ...options.body }) }); await fetch('http://localhost:3000/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json', ...options.headers }, body: JSON.stringify(options.body) }); return { id: 2, ...options.body }; }), _userId_: { get: vitest_1.vi.fn().mockImplementation(async (userId, options = {}) => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, headers: new Map([['content-type', 'application/json']]), json: async () => ({ id: parseInt(userId), name: 'John Doe', email: 'john@example.com' }) }); await fetch(`http://localhost:3000/api/users/${userId}`, { method: 'GET', headers: options.headers || {} }); return { id: parseInt(userId), name: 'John Doe', email: 'john@example.com' }; }) } }, v1: { posts: { get: vitest_1.vi.fn().mockImplementation(async (options = {}) => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, headers: new Map([['content-type', 'application/json']]), json: async () => [{ id: 1, title: 'Test Post V1', content: 'Test content', version: 'v1' }] }); await fetch('http://localhost:3000/api/v1/posts', { method: 'GET', headers: options.headers || {} }); return [{ id: 1, title: 'Test Post V1', content: 'Test content', version: 'v1' }]; }) } }, v2: { posts: { get: vitest_1.vi.fn().mockImplementation(async (options = {}) => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, headers: new Map([['content-type', 'application/json']]), json: async () => [{ id: 1, title: 'Test Post V2', content: 'Test content', version: 'v2' }] }); await fetch('http://localhost:3000/api/v2/posts', { method: 'GET', headers: options.headers || {} }); return [{ id: 1, title: 'Test Post V2', content: 'Test content', version: 'v2' }]; }) } } }, admin: { dashboard: { get: vitest_1.vi.fn().mockImplementation(async (options = {}) => { mockFetch.mockResolvedValueOnce({ ok: true, status: 200, headers: new Map([['content-type', 'application/json']]), json: async () => ({ stats: { users: 100, posts: 500 } }) }); await fetch('http://localhost:3000/admin/dashboard', { method: 'GET', headers: options.headers || {} }); return { stats: { users: 100, posts: 500 } }; }) } } }; } (0, vitest_1.it)('should call GET method without parameters', async () => { const result = await generatedClient.api.hello.get(); (0, vitest_1.expect)(generatedClient.api.hello.get).toHaveBeenCalledWith(); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/hello', { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual({ message: 'Hello World' }); }); (0, vitest_1.it)('should call GET method with query parameters', async () => { const result = await generatedClient.api.users.get({ query: { page: 1, limit: 10, search: 'john' } }); (0, vitest_1.expect)(generatedClient.api.users.get).toHaveBeenCalledWith({ query: { page: 1, limit: 10, search: 'john' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users?page=1&limit=10&search=john', vitest_1.expect.objectContaining({ method: 'GET' })); (0, vitest_1.expect)(result).toEqual([{ id: 1, name: 'John Doe', email: 'john@example.com' }]); }); (0, vitest_1.it)('should call GET method with headers', async () => { const result = await generatedClient.api.hello.get({ headers: { 'Authorization': 'Bearer token123', 'X-API-Version': 'v1' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/hello', { method: 'GET', headers: { 'Authorization': 'Bearer token123', 'X-API-Version': 'v1' } }); (0, vitest_1.expect)(result).toEqual({ message: 'Hello World' }); }); (0, vitest_1.it)('should call POST method with request body', async () => { const userData = { name: 'Jane Doe', email: 'jane@example.com' }; const result = await generatedClient.api.users.post({ body: userData }); (0, vitest_1.expect)(generatedClient.api.users.post).toHaveBeenCalledWith({ body: userData }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); (0, vitest_1.expect)(result).toEqual({ id: 2, ...userData }); }); (0, vitest_1.it)('should call POST method with body and headers', async () => { const userData = { name: 'Alice Smith', email: 'alice@example.com' }; const result = await generatedClient.api.users.post({ body: userData, headers: { 'Authorization': 'Bearer token123', 'X-Request-ID': 'req-456' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token123', 'X-Request-ID': 'req-456' }, body: JSON.stringify(userData) }); (0, vitest_1.expect)(result).toEqual({ id: 2, ...userData }); }); (0, vitest_1.it)('should call method with path parameters', async () => { const userId = '123'; const result = await generatedClient.api.users._userId_.get(userId); (0, vitest_1.expect)(generatedClient.api.users._userId_.get).toHaveBeenCalledWith(userId); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith(`http://localhost:3000/api/users/${userId}`, { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual({ id: 123, name: 'John Doe', email: 'john@example.com' }); }); (0, vitest_1.it)('should call method with path parameters and options', async () => { const userId = '456'; const result = await generatedClient.api.users._userId_.get(userId, { headers: { 'Authorization': 'Bearer token123' } }); (0, vitest_1.expect)(generatedClient.api.users._userId_.get).toHaveBeenCalledWith(userId, { headers: { 'Authorization': 'Bearer token123' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith(`http://localhost:3000/api/users/${userId}`, { method: 'GET', headers: { 'Authorization': 'Bearer token123' } }); (0, vitest_1.expect)(result).toEqual({ id: 456, name: 'John Doe', email: 'john@example.com' }); }); (0, vitest_1.it)('should call nested route methods (v1 routes)', async () => { const result = await generatedClient.api.v1.posts.get(); (0, vitest_1.expect)(generatedClient.api.v1.posts.get).toHaveBeenCalledWith(); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/posts', { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual([{ id: 1, title: 'Test Post V1', content: 'Test content', version: 'v1' }]); }); (0, vitest_1.it)('should call nested route methods (v2 routes)', async () => { const result = await generatedClient.api.v2.posts.get(); (0, vitest_1.expect)(generatedClient.api.v2.posts.get).toHaveBeenCalledWith(); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v2/posts', { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual([{ id: 1, title: 'Test Post V2', content: 'Test content', version: 'v2' }]); }); (0, vitest_1.it)('should call admin route methods', async () => { const result = await generatedClient.admin.dashboard.get(); (0, vitest_1.expect)(generatedClient.admin.dashboard.get).toHaveBeenCalledWith(); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/admin/dashboard', { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual({ stats: { users: 100, posts: 500 } }); }); (0, vitest_1.it)('should handle method calls with optional parameters', async () => { // Test calling with undefined options const result = await generatedClient.api.hello.get(undefined); (0, vitest_1.expect)(generatedClient.api.hello.get).toHaveBeenCalledWith(undefined); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/hello', { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual({ message: 'Hello World' }); }); (0, vitest_1.it)('should handle method calls with empty options', async () => { const result = await generatedClient.api.users.get({}); (0, vitest_1.expect)(generatedClient.api.users.get).toHaveBeenCalledWith({}); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users', { method: 'GET', headers: {} }); (0, vitest_1.expect)(result).toEqual([{ id: 1, name: 'John Doe', email: 'john@example.com' }]); }); (0, vitest_1.it)('should handle method calls with partial options', async () => { const result = await generatedClient.api.users.get({ query: { page: 1 } // Only some query parameters }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users?page=1', vitest_1.expect.objectContaining({ method: 'GET' })); (0, vitest_1.expect)(result).toEqual([{ id: 1, name: 'John Doe', email: 'john@example.com' }]); }); (0, vitest_1.it)('should handle concurrent method calls', async () => { const promises = [ generatedClient.api.hello.get(), generatedClient.api.users.get(), generatedClient.admin.dashboard.get() ]; const results = await Promise.all(promises); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledTimes(3); (0, vitest_1.expect)(results).toHaveLength(3); (0, vitest_1.expect)(results[0]).toEqual({ message: 'Hello World' }); (0, vitest_1.expect)(results[1]).toEqual([{ id: 1, name: 'John Doe', email: 'john@example.com' }]); (0, vitest_1.expect)(results[2]).toEqual({ stats: { users: 100, posts: 500 } }); }); (0, vitest_1.it)('should handle method calls with complex query parameters', async () => { const complexQuery = { search: 'john doe', filters: 'active,verified', sort: 'created_desc', page: 2, limit: 25, include: 'profile,settings' }; await generatedClient.api.users.get({ query: complexQuery }); const expectedUrl = 'http://localhost:3000/api/users?' + 'search=john+doe&filters=active%2Cverified&sort=created_desc&page=2&limit=25&include=profile%2Csettings'; (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith(expectedUrl, vitest_1.expect.objectContaining({ method: 'GET' })); }); (0, vitest_1.it)('should handle method calls with various HTTP methods', async () => { const postData = { name: 'Test User', email: 'test@example.com' }; // Test different HTTP methods if they exist in the client await generatedClient.api.users.post({ body: postData }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(postData) }); }); (0, vitest_1.it)('should handle method calls with custom Content-Type headers', async () => { await generatedClient.api.users.post({ body: { name: 'Custom User' }, headers: { 'Content-Type': 'application/vnd.api+json' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users', { method: 'POST', headers: { 'Content-Type': 'application/vnd.api+json' }, body: JSON.stringify({ name: 'Custom User' }) }); }); (0, vitest_1.it)('should maintain method call context and state', async () => { // First call await generatedClient.api.hello.get({ headers: { 'X-Call': '1' } }); // Second call with different headers await generatedClient.api.hello.get({ headers: { 'X-Call': '2' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenNthCalledWith(1, 'http://localhost:3000/api/hello', { method: 'GET', headers: { 'X-Call': '1' } }); (0, vitest_1.expect)(mockFetch).toHaveBeenNthCalledWith(2, 'http://localhost:3000/api/hello', { method: 'GET', headers: { 'X-Call': '2' } }); }); (0, vitest_1.it)('should handle method calls with URL encoding', async () => { const userId = '123/abc'; // ID with special characters await generatedClient.api.users._userId_.get(userId); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users/123/abc', { method: 'GET', headers: {} }); }); (0, vitest_1.it)('should handle method calls with boolean and number query parameters', async () => { await generatedClient.api.users.get({ query: { active: true, limit: 50, verified: false, score: 95.5 } }); (0, vitest_1.expect)(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/users?active=true&limit=50&verified=false&score=95.5', vitest_1.expect.objectContaining({ method: 'GET' })); }); }); //# sourceMappingURL=client-method-calls.test.js.map