import { expect, tap } from '@git.zone/tstest/tapbundle'; import { SmartServe, Route, Get, Post, Guard, Transform, Intercept, Compress, NoCompress, HttpError, type IRequestContext, } from '../ts/index.js'; import * as zlib from 'zlib'; import * as http from 'http'; import { promisify } from 'util'; const gunzip = promisify(zlib.gunzip); const brotliDecompress = promisify(zlib.brotliDecompress); /** * Make a raw HTTP request without automatic decompression * (Node.js fetch auto-decompresses, which breaks our tests) */ function rawRequest(options: { port: number; path: string; headers?: Record; }): Promise<{ status: number; headers: http.IncomingHttpHeaders; body: Buffer }> { return new Promise((resolve, reject) => { const req = http.request( { hostname: 'localhost', port: options.port, path: options.path, method: 'GET', headers: options.headers, }, (res) => { const chunks: Buffer[] = []; res.on('data', (chunk) => chunks.push(chunk)); res.on('end', () => { resolve({ status: res.statusCode ?? 200, headers: res.headers, body: Buffer.concat(chunks), }); }); res.on('error', reject); } ); req.on('error', reject); req.end(); }); } // Test controller @Route('/api') class TestController { @Get('/hello') hello() { return { message: 'Hello World' }; } @Get('/users/:id') getUser(ctx: IRequestContext) { return { id: ctx.params.id, name: 'Test User' }; } @Post('/echo') echo(ctx: IRequestContext<{ text: string }>) { return { echo: ctx.body?.text }; } } // Controller with guards const isAuthenticated = (ctx: IRequestContext) => { return ctx.headers.has('Authorization'); }; @Route('/protected') @Guard(isAuthenticated) class ProtectedController { @Get('/data') getData() { return { secret: 'protected data' }; } } // Controller with transforms const wrapResponse = (data: T) => ({ success: true, data, timestamp: Date.now() }); @Route('/wrapped') @Transform(wrapResponse) class WrappedController { @Get('/info') getInfo() { return { version: '1.0.0' }; } } // Controller with compression decorators @Route('/compression') class CompressionController { @Get('/large') getLargeData() { // Return data larger than default threshold (1024 bytes) return { data: 'x'.repeat(2000), items: Array.from({ length: 50 }, (_, i) => ({ id: i, name: `Item ${i}`, description: `This is a description for item ${i} with some extra text to make it longer`, })), }; } @Get('/small') getSmallData() { // Return data smaller than threshold return { ok: true }; } @Get('/no-compress') @NoCompress() getNoCompress() { // Should never be compressed return { data: 'x'.repeat(2000), compressed: false, }; } @Get('/force-compress') @Compress({ level: 9 }) getForceCompress() { return { data: 'y'.repeat(2000), compressed: true, }; } } tap.test('SmartServe should create server instance', async () => { const server = new SmartServe({ port: 3456 }); expect(server).toBeInstanceOf(SmartServe); expect(server.isRunning()).toBeFalse(); }); tap.test('SmartServe should register controllers', async () => { const server = new SmartServe({ port: 3457 }); server.register(TestController); server.register(ProtectedController); server.register(WrappedController); expect(server).toBeInstanceOf(SmartServe); }); tap.test('SmartServe should start and stop', async () => { const server = new SmartServe({ port: 3458 }); server.register(TestController); const instance = await server.start(); expect(instance.port).toEqual(3458); expect(instance.runtime).toEqual('node'); expect(server.isRunning()).toBeTrue(); await server.stop(); expect(server.isRunning()).toBeFalse(); }); tap.test('SmartServe should handle GET request', async () => { const server = new SmartServe({ port: 3459 }); server.register(TestController); await server.start(); try { const response = await fetch('http://localhost:3459/api/hello'); const data = await response.json(); expect(response.status).toEqual(200); expect(data.message).toEqual('Hello World'); } finally { await server.stop(); } }); tap.test('SmartServe should handle path parameters', async () => { const server = new SmartServe({ port: 3460 }); server.register(TestController); await server.start(); try { const response = await fetch('http://localhost:3460/api/users/123'); const data = await response.json(); expect(response.status).toEqual(200); expect(data.id).toEqual('123'); expect(data.name).toEqual('Test User'); } finally { await server.stop(); } }); tap.test('SmartServe should handle POST with body', async () => { const server = new SmartServe({ port: 3461 }); server.register(TestController); await server.start(); try { const response = await fetch('http://localhost:3461/api/echo', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Hello!' }), }); const data = await response.json(); expect(response.status).toEqual(200); expect(data.echo).toEqual('Hello!'); } finally { await server.stop(); } }); tap.test('SmartServe should enforce guards', async () => { const server = new SmartServe({ port: 3462 }); server.register(ProtectedController); await server.start(); try { // Without auth header - should be forbidden const response1 = await fetch('http://localhost:3462/protected/data'); expect(response1.status).toEqual(403); // With auth header - should succeed const response2 = await fetch('http://localhost:3462/protected/data', { headers: { Authorization: 'Bearer token123' }, }); const data = await response2.json(); expect(response2.status).toEqual(200); expect(data.secret).toEqual('protected data'); } finally { await server.stop(); } }); tap.test('SmartServe should apply transforms', async () => { const server = new SmartServe({ port: 3463 }); server.register(WrappedController); await server.start(); try { const response = await fetch('http://localhost:3463/wrapped/info'); const data = await response.json(); expect(response.status).toEqual(200); expect(data.success).toBeTrue(); expect(data.data.version).toEqual('1.0.0'); expect(data.timestamp).toBeTypeofNumber(); } finally { await server.stop(); } }); tap.test('SmartServe should return 404 for unknown routes', async () => { const server = new SmartServe({ port: 3464 }); server.register(TestController); await server.start(); try { const response = await fetch('http://localhost:3464/unknown/route'); expect(response.status).toEqual(404); } finally { await server.stop(); } }); tap.test('HttpError should create proper responses', async () => { const error = HttpError.notFound('Resource not found', { id: '123' }); expect(error.status).toEqual(404); expect(error.message).toEqual('Resource not found'); expect(error.details).toEqual({ id: '123' }); const response = error.toResponse(); expect(response.status).toEqual(404); const body = await response.json(); expect(body.error).toEqual('Resource not found'); expect(body.details.id).toEqual('123'); }); // ============================================================================ // Compression Tests // ============================================================================ tap.test('Compression should apply gzip when Accept-Encoding is sent', async () => { const server = new SmartServe({ port: 3470 }); server.register(CompressionController); await server.start(); try { const response = await rawRequest({ port: 3470, path: '/compression/large', headers: { 'Accept-Encoding': 'gzip' }, }); expect(response.status).toEqual(200); expect(response.headers['content-encoding']).toEqual('gzip'); expect(response.headers['vary']).toInclude('Accept-Encoding'); // Decompress and verify content const decompressed = await gunzip(response.body); const data = JSON.parse(decompressed.toString()); expect(data.data).toStartWith('xxx'); expect(data.items.length).toEqual(50); } finally { await server.stop(); } }); tap.test('Compression should apply brotli when preferred', async () => { const server = new SmartServe({ port: 3471 }); server.register(CompressionController); await server.start(); try { const response = await rawRequest({ port: 3471, path: '/compression/large', headers: { 'Accept-Encoding': 'br, gzip' }, }); expect(response.status).toEqual(200); expect(response.headers['content-encoding']).toEqual('br'); // Decompress and verify content const decompressed = await brotliDecompress(response.body); const data = JSON.parse(decompressed.toString()); expect(data.data).toStartWith('xxx'); } finally { await server.stop(); } }); tap.test('Compression should skip small responses', async () => { const server = new SmartServe({ port: 3472 }); server.register(CompressionController); await server.start(); try { const response = await rawRequest({ port: 3472, path: '/compression/small', headers: { 'Accept-Encoding': 'gzip, br' }, }); expect(response.status).toEqual(200); // Small response should NOT be compressed expect(response.headers['content-encoding']).toBeUndefined(); const data = JSON.parse(response.body.toString()); expect(data.ok).toBeTrue(); } finally { await server.stop(); } }); tap.test('Compression should respect @NoCompress decorator', async () => { const server = new SmartServe({ port: 3473 }); server.register(CompressionController); await server.start(); try { const response = await rawRequest({ port: 3473, path: '/compression/no-compress', headers: { 'Accept-Encoding': 'gzip, br' }, }); expect(response.status).toEqual(200); // Should NOT be compressed due to @NoCompress expect(response.headers['content-encoding']).toBeUndefined(); const data = JSON.parse(response.body.toString()); expect(data.compressed).toBeFalse(); } finally { await server.stop(); } }); tap.test('Compression should skip when Accept-Encoding not sent', async () => { const server = new SmartServe({ port: 3474 }); server.register(CompressionController); await server.start(); try { const response = await rawRequest({ port: 3474, path: '/compression/large', // No Accept-Encoding header }); expect(response.status).toEqual(200); // Should NOT be compressed when client doesn't accept it expect(response.headers['content-encoding']).toBeUndefined(); const data = JSON.parse(response.body.toString()); expect(data.data).toStartWith('xxx'); } finally { await server.stop(); } }); tap.test('Compression can be disabled globally', async () => { const server = new SmartServe({ port: 3475, compression: false, }); server.register(CompressionController); await server.start(); try { const response = await rawRequest({ port: 3475, path: '/compression/large', headers: { 'Accept-Encoding': 'gzip, br' }, }); expect(response.status).toEqual(200); // Should NOT be compressed when globally disabled expect(response.headers['content-encoding']).toBeUndefined(); } finally { await server.stop(); } }); export default tap.start();