diff --git a/dsh-community-market/tests/host-routes.spec.ts b/dsh-community-market/tests/host-routes.spec.ts new file mode 100644 index 0000000000..e8b2e2ae36 --- /dev/null +++ b/dsh-community-market/tests/host-routes.spec.ts @@ -0,0 +1,112 @@ +import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http' +import type { AddressInfo } from 'node:net' +import type { Context } from '@deepseek-ai/cordis' +import type { SettingsScope } from '@deepseek-ai/dsh-settings' +import { describe, expect, it } from 'vitest' +import { + DSH_1024STORE_ADAPTER_ID, + DSH_1024STORE_ENDPOINT, + DSH_1024STORE_KEY, + DSH_1024STORE_PROVIDER_ID, +} from '../src/adapters/dsh-1024store.js' +import type { MarketSettingsDocument } from '../src/catalog/source-store.js' +import type { LocalSourceRecord } from '../src/contracts/index.js' +import { marketRoutes, registerMarketRoutes } from '../src/host/routes.js' + +type RouteHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise + +interface MarketServer { + readonly baseUrl: string + readonly close: () => Promise +} + +const builtInSource = (overrides: Partial = {}): LocalSourceRecord => ({ + sourceRecordId: '018f1f77-a5c4-7b73-a9ae-0242ac120002', + registrationKind: 'built-in', + adapterId: DSH_1024STORE_ADAPTER_ID, + providerId: DSH_1024STORE_PROVIDER_ID, + builtInProviderKey: DSH_1024STORE_KEY, + enabled: false, + order: 0, + ...overrides, +}) + +async function startMarketServer(initialSources: readonly LocalSourceRecord[]): Promise { + const routes = new Map() + let document: MarketSettingsDocument = { sources: initialSources } + const scope = { + get: () => document, + update: async (patch: object) => { + document = { ...document, ...patch as Partial } + }, + } as unknown as SettingsScope + const ctx = { + webServer: { + register: (route: { readonly path: string; readonly handler: RouteHandler }) => { + routes.set(route.path, route.handler) + return () => { routes.delete(route.path) } + }, + }, + } as unknown as Context + const disposeRoutes = registerMarketRoutes(ctx, scope) + const server = createServer((req, res) => { + const pathname = new URL(req.url ?? '/', 'http://localhost').pathname + const handler = routes.get(pathname) + if (handler === undefined) { + res.statusCode = 404 + res.end() + return + } + void Promise.resolve(handler(req, res)).catch((cause: unknown) => { + res.statusCode = 500 + res.end(cause instanceof Error ? cause.message : String(cause)) + }) + }) + await new Promise((resolve, reject) => { + server.once('error', reject) + server.listen(0, '127.0.0.1', resolve) + }) + const { port } = server.address() as AddressInfo + return { + baseUrl: `http://127.0.0.1:${String(port)}`, + close: async () => { + disposeRoutes() + await closeServer(server) + }, + } +} + +async function closeServer(server: Server): Promise { + await new Promise((resolve, reject) => { + server.close(error => { if (error === undefined) resolve(); else reject(error) }) + }) +} + +describe('community market Host routes', () => { + it('returns settings-backed source state with built-in provider metadata', async () => { + const server = await startMarketServer([builtInSource()]) + try { + const response = await fetch(`${server.baseUrl}${marketRoutes.state}`) + + expect(response.status).toBe(200) + expect(response.headers.get('cache-control')).toBe('no-store') + expect(response.headers.get('x-content-type-options')).toBe('nosniff') + await expect(response.json()).resolves.toMatchObject({ + sources: [{ + sourceRecordId: builtInSource().sourceRecordId, + name: 'DSH 1024Store', + endpoint: DSH_1024STORE_ENDPOINT, + partnership: true, + enabled: false, + }], + builtIns: [{ + key: DSH_1024STORE_KEY, + providerId: DSH_1024STORE_PROVIDER_ID, + partnership: true, + }], + }) + } finally { + await server.close() + } + }) +})