Explorar o código

test(webhook-github): verify chunked overflow response

Tianyi Cui hai 1 mes
pai
achega
2b2a8e8240
Modificáronse 1 ficheiros con 44 adicións e 2 borrados
  1. 44 2
      packages/webhook/webhook-github/tests/handler.spec.ts

+ 44 - 2
packages/webhook/webhook-github/tests/handler.spec.ts

@@ -1,5 +1,5 @@
 import { createHmac } from 'node:crypto'
-import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'
+import { createServer, request as httpRequest, type IncomingMessage, type Server, type ServerResponse } from 'node:http'
 import type { AddressInfo } from 'node:net'
 import type { Context } from '@deepseek-ai/cordis'
 import { afterEach, describe, expect, it, vi } from 'vitest'
@@ -81,6 +81,37 @@ async function post(
   })
 }
 
+/** Send body chunks without Content-Length through a real Node client socket. */
+async function postChunked(
+  base: string,
+  chunks: readonly string[],
+  endDelayMs = 0,
+): Promise<{ body: string; status: number }> {
+  return await new Promise((resolve, reject) => {
+    const request = httpRequest(base, {
+      method: 'POST',
+      headers: {
+        connection: 'close',
+        'content-type': 'application/json',
+        'transfer-encoding': 'chunked',
+        'x-hub-signature-256': 'sha256=unused',
+        'x-github-event': 'pull_request',
+        'x-github-delivery': 'chunked-delivery',
+      },
+    }, (response) => {
+      let body = ''
+      response.setEncoding('utf8')
+      response.on('data', (chunk: string) => { body += chunk })
+      response.on('end', () => { resolve({ body, status: response.statusCode ?? 0 }) })
+    })
+    request.once('error', reject)
+    request.once('socket', (socket) => { socket.setNoDelay(true) })
+    for (const chunk of chunks) request.write(chunk)
+    if (endDelayMs === 0) request.end()
+    else setTimeout(() => { request.end() }, endDelayMs)
+  })
+}
+
 describe('GitHub webhook HTTP handler', () => {
   it('verifies, projects, dispatches, and answers 202', async () => {
     const fake = fakeContext()
@@ -180,7 +211,7 @@ describe('GitHub webhook HTTP handler', () => {
     expect(fake.dispatch).not.toHaveBeenCalled()
   })
 
-  it('rejects declared and streamed bodies over the configured cap', async () => {
+  it('rejects a declared body over the configured cap', async () => {
     const fake = fakeContext()
     const base = await serve(fake.ctx, 2)
     const response = await post(base, '{} ')
@@ -188,6 +219,17 @@ describe('GitHub webhook HTTP handler', () => {
     expect(fake.dispatch).not.toHaveBeenCalled()
   })
 
+  it('answers 413 for a chunked body over the cap without resetting the connection', async () => {
+    const fake = fakeContext()
+    const base = await serve(fake.ctx, 2)
+
+    await expect(postChunked(base, ['abc'], 50)).resolves.toEqual({
+      body: 'request body is too large',
+      status: 413,
+    })
+    expect(fake.dispatch).not.toHaveBeenCalled()
+  })
+
   it('answers 503 when the credential or runtime is unavailable', async () => {
     const missing = fakeContext()
     missing.setSecret(undefined)