| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- /** Node half: registers the /api prefix route bridging to the api gateway. */
- import { EventEmitter } from 'node:events'
- import { Readable } from 'node:stream'
- import { Context } from 'cordis'
- import { describe, expect, it } from 'vitest'
- import type { IncomingMessage, ServerResponse } from 'node:http'
- import type { ApiProxy } from '@deepseek-ai/dsh-host-apiproxy/api'
- import type { HttpServerService, WebRoute } from '@deepseek-ai/dsh-host-webserver'
- import { API_PATH, apply, inject } from '../src/index.ts'
- /** Structural httpServer fake: the plugin only touches register(). */
- function fakeHttpServer(routes: WebRoute[]): Pick<HttpServerService, 'register' | 'tapIndex' | 'port'> {
- return {
- register(route) {
- routes.push(route)
- return () => { routes.splice(routes.indexOf(route), 1) }
- },
- tapIndex: () => () => {},
- port: 0,
- }
- }
- /** Bodyless GET carrying the given headers (enough for the trust fence + bridge). */
- function fakeRequest(headers: Record<string, string>): IncomingMessage {
- const request = Readable.from([]) as unknown as IncomingMessage
- Object.assign(request, { url: `${API_PATH}/session.list`, method: 'GET', headers })
- return request
- }
- /** Response recorder compatible with both the fence's short-circuit and the bridge. */
- function fakeResponse(): { response: ServerResponse; state: { status?: number; body?: unknown } } {
- const state: { status?: number; body?: unknown } = {}
- const response = Object.assign(new EventEmitter(), {
- writableEnded: false,
- writeHead(value: number) { state.status = value; return this },
- write() { return true },
- end(this: { writableEnded: boolean }, value?: unknown) {
- if (value !== undefined) state.body = value
- this.writableEnded = true
- return this
- },
- }) as unknown as ServerResponse
- return { response, state }
- }
- async function mounted(config?: { trustedHosts?: string[] }): Promise<{ routes: WebRoute[]; dispose: () => Promise<void> }> {
- const ctx = new Context()
- const routes: WebRoute[] = []
- ctx.provide('httpServer', fakeHttpServer(routes) as HttpServerService)
- ctx.provide('apiProxy', {} as unknown as ApiProxy)
- const fiber = ctx.plugin({ inject: [...inject], apply }, config)
- await fiber.await()
- return { routes, dispose: () => fiber.dispose() }
- }
- describe('connection node half', () => {
- it('registers the /api prefix route and removes it with the fiber', async () => {
- const { routes, dispose } = await mounted()
- expect(routes).toHaveLength(1)
- expect(routes[0]).toMatchObject({ kind: 'prefix', path: API_PATH })
- await dispose()
- expect(routes).toHaveLength(0)
- })
- it('refuses an untrusted Host on any /api path before the bridge runs', async () => {
- const { routes, dispose } = await mounted()
- const { response, state } = fakeResponse()
- await routes[0]!.handler(fakeRequest({
- host: 'harness.example', origin: 'http://harness.example', 'sec-fetch-site': 'same-origin',
- }), response)
- expect(state.status).toBe(403)
- expect(state.body).toBe('forbidden')
- await dispose()
- })
- it('passes loopback and declared-authority requests through to the bridge', async () => {
- const { routes, dispose } = await mounted({ trustedHosts: ['harness.example:3080'] })
- // Loopback, no browser markers (curl shape): the fence passes; the carrier
- // answers 404 for a GET unary path — proof the bridge ran.
- const loopback = fakeResponse()
- await routes[0]!.handler(fakeRequest({ host: '127.0.0.1:3080' }), loopback.response)
- expect(loopback.state.status).toBe(404)
- // Declared public authority, same-origin browser shape.
- const declared = fakeResponse()
- await routes[0]!.handler(fakeRequest({
- host: 'harness.example:3080', origin: 'http://harness.example:3080', 'sec-fetch-site': 'same-origin',
- }), declared.response)
- expect(declared.state.status).toBe(404)
- await dispose()
- })
- })
|