|
| 1 | +import * as net from 'net'; |
| 2 | +import * as http2 from 'http2'; |
| 3 | +import { expect } from 'chai'; |
| 4 | +import { DestroyableServer, makeDestroyable } from 'destroyable-server'; |
| 5 | + |
| 6 | +import { createServer } from '../src/server.js'; |
| 7 | + |
| 8 | +describe("Delay endpoint", () => { |
| 9 | + |
| 10 | + let server: DestroyableServer; |
| 11 | + let serverPort: number; |
| 12 | + |
| 13 | + beforeEach(async () => { |
| 14 | + server = makeDestroyable(await createServer({ |
| 15 | + domain: 'localhost' |
| 16 | + })); |
| 17 | + await new Promise<void>((resolve) => server.listen(resolve)); |
| 18 | + serverPort = (server.address() as net.AddressInfo).port; |
| 19 | + }); |
| 20 | + |
| 21 | + afterEach(async () => { |
| 22 | + await server.destroy(); |
| 23 | + }); |
| 24 | + |
| 25 | + it("delays then returns httpbin-style response", async () => { |
| 26 | + const start = Date.now(); |
| 27 | + const response = await fetch(`http://localhost:${serverPort}/delay/0.1`); |
| 28 | + const elapsed = Date.now() - start; |
| 29 | + |
| 30 | + expect(response.status).to.equal(200); |
| 31 | + expect(elapsed).to.be.greaterThan(90); |
| 32 | + |
| 33 | + const body = await response.json(); |
| 34 | + expect(body).to.have.property('url'); |
| 35 | + expect(body).to.have.property('headers'); |
| 36 | + }); |
| 37 | + |
| 38 | + it("rejects invalid delay values", async () => { |
| 39 | + const response = await fetch(`http://localhost:${serverPort}/delay/notanumber`); |
| 40 | + expect(response.status).to.equal(400); |
| 41 | + }); |
| 42 | + |
| 43 | + it("forwards to /status endpoint", async () => { |
| 44 | + const response = await fetch(`http://localhost:${serverPort}/delay/0.05/status/201`); |
| 45 | + expect(response.status).to.equal(201); |
| 46 | + }); |
| 47 | + |
| 48 | + it("rejects excessively deep chains", async () => { |
| 49 | + const deepPath = '/delay/0.001'.repeat(15) + '/status/200'; |
| 50 | + const response = await fetch(`http://localhost:${serverPort}${deepPath}`); |
| 51 | + expect(response.status).to.equal(400); |
| 52 | + }); |
| 53 | + |
| 54 | + describe("forwarding to /echo", () => { |
| 55 | + |
| 56 | + it("echoes HTTP/1 request data after delay", async () => { |
| 57 | + const response = await fetch(`http://localhost:${serverPort}/delay/0.05/echo`, { |
| 58 | + headers: { 'test-header': 'test-value' } |
| 59 | + }); |
| 60 | + |
| 61 | + expect(response.status).to.equal(200); |
| 62 | + |
| 63 | + const rawBody = await response.text(); |
| 64 | + // Raw data reflects what was actually sent by the client |
| 65 | + expect(rawBody).to.include('GET /delay/0.05/echo HTTP/1.1'); |
| 66 | + expect(rawBody).to.include('test-header: test-value'); |
| 67 | + }); |
| 68 | + |
| 69 | + it("echoes HTTP/2 request data after delay", async () => { |
| 70 | + const client = http2.connect(`http://localhost:${serverPort}`); |
| 71 | + |
| 72 | + const req = client.request({ |
| 73 | + ':path': '/delay/0.05/echo', |
| 74 | + ':method': 'GET', |
| 75 | + 'test-header': 'test-value' |
| 76 | + }); |
| 77 | + |
| 78 | + const [headers, body] = await new Promise<[http2.IncomingHttpHeaders, string]>((resolve, reject) => { |
| 79 | + let headers: http2.IncomingHttpHeaders; |
| 80 | + const chunks: Buffer[] = []; |
| 81 | + |
| 82 | + req.on('response', (h) => { headers = h; }); |
| 83 | + req.on('data', (chunk) => chunks.push(chunk)); |
| 84 | + req.on('end', () => resolve([headers, Buffer.concat(chunks).toString()])); |
| 85 | + req.on('error', reject); |
| 86 | + }); |
| 87 | + |
| 88 | + client.close(); |
| 89 | + |
| 90 | + expect(headers[':status']).to.equal(200); |
| 91 | + |
| 92 | + const lines = body.trim().split('\n'); |
| 93 | + const frames = lines.map(line => JSON.parse(line)); |
| 94 | + |
| 95 | + const headersFrames = frames.filter((f: any) => f.type === 'HEADERS' && f.decoded_headers); |
| 96 | + expect(headersFrames.length).to.be.greaterThan(0); |
| 97 | + expect(headersFrames[0].decoded_headers).to.have.property('test-header', 'test-value'); |
| 98 | + }); |
| 99 | + |
| 100 | + it("chains multiple delays before /echo with raw data preserved", async () => { |
| 101 | + const start = Date.now(); |
| 102 | + |
| 103 | + const response = await fetch(`http://localhost:${serverPort}/delay/0.1/delay/0.1/echo`, { |
| 104 | + headers: { 'chain-test': 'multi-delay' } |
| 105 | + }); |
| 106 | + |
| 107 | + expect(response.status).to.equal(200); |
| 108 | + expect(Date.now() - start).to.be.greaterThan(200); |
| 109 | + |
| 110 | + const rawBody = await response.text(); |
| 111 | + // Raw data reflects what was actually sent by the client |
| 112 | + expect(rawBody).to.include('GET /delay/0.1/delay/0.1/echo HTTP/1.1'); |
| 113 | + expect(rawBody).to.include('chain-test: multi-delay'); |
| 114 | + }); |
| 115 | + |
| 116 | + }); |
| 117 | + |
| 118 | +}); |
0 commit comments