diff --git a/packages/lib/src/__tests__/request-handling.test.ts b/packages/lib/src/__tests__/request-handling.test.ts index cb5a371..499e99b 100644 --- a/packages/lib/src/__tests__/request-handling.test.ts +++ b/packages/lib/src/__tests__/request-handling.test.ts @@ -246,6 +246,209 @@ describe('Request Handling', () => { }); }); + describe('Query parameters handling', () => { + it('should parse single query parameter', async () => { + const handler = vi.fn((req) => `Got param: ${req.query.name}`); + workerify.get('/test', handler); + + await workerify.listen(); + + const consumerId = mockChannel.getLastConsumerId(); + mockChannel.simulateMessage({ + type: 'workerify:handle', + id: 'req-1', + consumerId, + request: { + url: 'http://localhost:3000/test?name=John', + method: 'GET', + headers: {}, + body: null, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + url: 'http://localhost:3000/test?name=John', + method: 'GET', + query: { name: 'John' }, + }), + expect.objectContaining({ + status: 200, + statusText: 'OK', + }), + ); + + const responses = mockChannel.lastMessages.filter( + (msg) => msg.type === 'workerify:response', + ); + expect(responses[0].body).toBe('Got param: John'); + }); + + it('should parse multiple query parameters', async () => { + const handler = vi.fn((req) => ({ + name: req.query.name, + age: req.query.age, + city: req.query.city, + })); + workerify.get('/user', handler); + + await workerify.listen(); + + const consumerId = mockChannel.getLastConsumerId(); + mockChannel.simulateMessage({ + type: 'workerify:handle', + id: 'req-1', + consumerId, + request: { + url: 'http://localhost:3000/user?name=John&age=30&city=NYC', + method: 'GET', + headers: {}, + body: null, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + query: { name: 'John', age: '30', city: 'NYC' }, + }), + expect.objectContaining({ + status: 200, + statusText: 'OK', + }), + ); + + const responses = mockChannel.lastMessages.filter( + (msg) => msg.type === 'workerify:response', + ); + expect(responses[0].body).toEqual({ + name: 'John', + age: '30', + city: 'NYC', + }); + }); + + it('should handle requests without query parameters', async () => { + const handler = vi.fn( + (req) => `Has params: ${Object.keys(req.query).length > 0}`, + ); + workerify.get('/test', handler); + + await workerify.listen(); + + const consumerId = mockChannel.getLastConsumerId(); + mockChannel.simulateMessage({ + type: 'workerify:handle', + id: 'req-1', + consumerId, + request: { + url: 'http://localhost:3000/test', + method: 'GET', + headers: {}, + body: null, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + query: {}, + }), + expect.objectContaining({ + status: 200, + statusText: 'OK', + }), + ); + + const responses = mockChannel.lastMessages.filter( + (msg) => msg.type === 'workerify:response', + ); + expect(responses[0].body).toBe('Has params: false'); + }); + + it('should handle URL-encoded query parameters', async () => { + const handler = vi.fn((req) => req.query); + workerify.get('/search', handler); + + await workerify.listen(); + + const consumerId = mockChannel.getLastConsumerId(); + mockChannel.simulateMessage({ + type: 'workerify:handle', + id: 'req-1', + consumerId, + request: { + url: 'http://localhost:3000/search?q=hello%20world&filter=active%26verified', + method: 'GET', + headers: {}, + body: null, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + query: { q: 'hello world', filter: 'active&verified' }, + }), + expect.objectContaining({ + status: 200, + statusText: 'OK', + }), + ); + }); + + it('should handle query parameters with route parameters', async () => { + const handler = vi.fn((req) => ({ + userId: req.params.id, + filter: req.query.filter, + sort: req.query.sort, + })); + workerify.get('/users/:id', handler); + + await workerify.listen(); + + const consumerId = mockChannel.getLastConsumerId(); + mockChannel.simulateMessage({ + type: 'workerify:handle', + id: 'req-1', + consumerId, + request: { + url: 'http://localhost:3000/users/123?filter=active&sort=desc', + method: 'GET', + headers: {}, + body: null, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + params: { id: '123' }, + query: { filter: 'active', sort: 'desc' }, + }), + expect.objectContaining({ + status: 200, + statusText: 'OK', + }), + ); + + const responses = mockChannel.lastMessages.filter( + (msg) => msg.type === 'workerify:response', + ); + expect(responses[0].body).toEqual({ + userId: '123', + filter: 'active', + sort: 'desc', + }); + }); + }); + describe('Form data handling', () => { it('should parse form-encoded data in POST requests', async () => { const handler = vi.fn((req) => req.body); diff --git a/packages/lib/src/__tests__/test-utils.ts b/packages/lib/src/__tests__/test-utils.ts index 4bdabe6..046d21a 100644 --- a/packages/lib/src/__tests__/test-utils.ts +++ b/packages/lib/src/__tests__/test-utils.ts @@ -116,6 +116,7 @@ export function createMockRequest( }, body: body || null, params: {}, + query: {}, }; } diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index c67a8ff..e158eed 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -63,6 +63,14 @@ export class Workerify { // Add params to request request.params = params || {}; + // Parse query parameters + const urlObj = new URL(request.url); + const query: Record = {}; + for (const [key, value] of urlObj.searchParams.entries()) { + query[key] = value; + } + request.query = query; + // Parse form-encoded data for POST requests if ( request.method === 'POST' && diff --git a/packages/lib/src/types.ts b/packages/lib/src/types.ts index 9deb77a..df78ea4 100644 --- a/packages/lib/src/types.ts +++ b/packages/lib/src/types.ts @@ -17,6 +17,7 @@ export interface WorkerifyRequest { headers: Record; body?: WorkerifyBody; params: Record; + query: Record; } export interface WorkerifyReply {