mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-30 04:40:37 +00:00
One Vite build emits dist/index.html and dist/preview.html sharing every chunk; the only difference is one prepended bootstrap entry whose module connects the worker host, so the page from the stock entry onward is the served startup chain verbatim. The dist moves to a relative base so the preview mounts under any static directory, and the served form anchors deep SPA-fallback paths with a rendered <base href="/">. The preview-boot e2e serves the real built pages, packs the VFS image when absent, and holds the boot line's lowering contract, the interactive hero, and a clean page-error channel in headless Chromium.
177 lines
7.3 KiB
TypeScript
177 lines
7.3 KiB
TypeScript
/**
|
|
* REAL-composition coverage: a test-only cordis.yml booted through the
|
|
* vendored Loader mounts the webserver and frontend-static rows, and every
|
|
* assertion observes the served HTTP surface — asset serving, explicit index
|
|
* entry points with index taps, 404 misses, traversal rejection, 405 on non-
|
|
* GET/HEAD, and seat release on fiber disposal (HMR safety).
|
|
*/
|
|
|
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { Context } from '@deepseek-ai/cordis'
|
|
import Loader from '@deepseek-ai/cordis-plugin-loader'
|
|
import Include from '@deepseek-ai/cordis-plugin-include'
|
|
import HttpServer from '@deepseek-ai/dsh-host-webserver'
|
|
import * as FrontendStatic from '../src/index.ts'
|
|
|
|
let root: string | undefined
|
|
let context: Context | undefined
|
|
|
|
afterEach(async () => {
|
|
await context?.fiber.dispose()
|
|
context = undefined
|
|
if (root !== undefined) await rm(root, { recursive: true, force: true })
|
|
root = undefined
|
|
})
|
|
|
|
/** Write a dist fixture and a two-row cordis.yml, then boot it through the real Loader. */
|
|
async function loadComposition(): Promise<Context> {
|
|
root = await mkdtemp(join(tmpdir(), 'dsh-frontend-static-'))
|
|
const dist = join(root, 'dist')
|
|
await mkdir(dist)
|
|
const distIndex = join(dist, 'index.html')
|
|
await writeFile(distIndex, '<head></head><body>shell</body>')
|
|
await writeFile(join(dist, 'app.js'), 'export {}')
|
|
await writeFile(join(dist, 'blob.bin'), 'BLOB')
|
|
await writeFile(join(dist, 'manifest.webmanifest'), '{}')
|
|
await mkdir(join(dist, 'empty'))
|
|
const configPath = join(root, 'cordis.yml')
|
|
await writeFile(configPath, [
|
|
"- name: '@deepseek-ai/dsh-host-webserver'",
|
|
' config:',
|
|
" host: '127.0.0.1'",
|
|
' port: 0',
|
|
'- id: frontend',
|
|
" name: '@deepseek-ai/dsh-host-frontend-static'",
|
|
' config:',
|
|
` distIndex: '${distIndex}'`,
|
|
'',
|
|
].join('\n'))
|
|
|
|
context = new Context()
|
|
context.baseUrl = pathToFileURL(root).href + '/'
|
|
await context.plugin(Loader)
|
|
context.loader.builtins.include = Include
|
|
const modules = new Map<string, unknown>([
|
|
['@deepseek-ai/dsh-host-webserver', HttpServer],
|
|
['@deepseek-ai/dsh-host-frontend-static', FrontendStatic],
|
|
])
|
|
context.loader.internal = {
|
|
version: 'v2',
|
|
async import(specifier: string) {
|
|
if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`)
|
|
return modules.get(specifier)
|
|
},
|
|
} as unknown as NonNullable<typeof context.loader.internal>
|
|
await context.loader.create({
|
|
name: 'cordis:include',
|
|
config: { path: pathToFileURL(configPath).href },
|
|
})
|
|
await context.loader.await()
|
|
return context
|
|
}
|
|
|
|
/** GET (by default) one path against the running server; returns status, content-type, and a body prefix. */
|
|
async function request(port: number, path: string, init?: RequestInit): Promise<{ status: number; type: string | null; body: string }> {
|
|
const response = await fetch(`http://127.0.0.1:${String(port)}${path}`, init)
|
|
return {
|
|
status: response.status,
|
|
type: response.headers.get('content-type'),
|
|
// Window wide enough to keep index body markers visible behind the
|
|
// served prelude (base anchor + injection rows + boot-readiness tail).
|
|
body: (await response.text()).slice(0, 200),
|
|
}
|
|
}
|
|
|
|
describe('real Loader composition', () => {
|
|
it('serves explicit index entries and files while preserving HTTP error semantics', { timeout: 60_000 }, async () => {
|
|
const loaded = await loadComposition()
|
|
const unloaded = [...loaded.loader.entries()]
|
|
.filter(entry => entry.fiber === undefined && !entry.disabled)
|
|
.map(entry => entry.options.name)
|
|
expect(unloaded).toEqual([])
|
|
const server = loaded.webServer
|
|
const port = server.port
|
|
|
|
// Real assets with their MIME types; a live rebuild is served on the next read.
|
|
expect(await request(port, '/app.js')).toMatchObject({ status: 200, type: 'text/javascript; charset=utf-8', body: 'export {}' })
|
|
expect(await request(port, '/manifest.webmanifest')).toMatchObject({
|
|
status: 200,
|
|
type: 'application/manifest+json',
|
|
body: '{}',
|
|
})
|
|
expect(await request(port, '/app.js', { method: 'HEAD' })).toEqual({
|
|
status: 200,
|
|
type: 'text/javascript; charset=utf-8',
|
|
body: '',
|
|
})
|
|
await writeFile(join(root!, 'dist', 'app.js'), 'export const rebuilt = true')
|
|
expect(await request(port, '/app.js')).toMatchObject({ status: 200, body: 'export const rebuilt = true' })
|
|
|
|
// Unknown extension ships as octet-stream.
|
|
expect(await request(port, '/blob.bin')).toMatchObject({ status: 200, type: 'application/octet-stream', body: 'BLOB' })
|
|
|
|
// Only the root and index path render index.html through registered taps.
|
|
const untap = server.tapIndex(html => html.replace('<head>', '<head><script>window.__T__=1</script>'))
|
|
for (const path of ['/', '/index.html', '/?fixture']) {
|
|
const got = await request(port, path)
|
|
expect(got.status).toBe(200)
|
|
expect(got.type).toBe('text/html; charset=utf-8')
|
|
expect(got.body).toContain('__T__')
|
|
expect(got.body).toContain('shell')
|
|
}
|
|
expect(await request(port, '/', { method: 'HEAD' })).toEqual({
|
|
status: 200,
|
|
type: 'text/html; charset=utf-8',
|
|
body: '',
|
|
})
|
|
untap()
|
|
expect((await request(port, '/')).body).not.toContain('__T__')
|
|
|
|
// A missing configured index follows the same empty-404 contract for both
|
|
// of its public entry paths and for both supported methods.
|
|
await rm(join(root!, 'dist', 'index.html'))
|
|
for (const path of ['/', '/index.html']) {
|
|
const get = await request(port, path)
|
|
const head = await request(port, path, { method: 'HEAD' })
|
|
expect(get).toEqual({ status: 404, type: null, body: '' })
|
|
expect(head).toEqual(get)
|
|
}
|
|
|
|
// Ordinary unknown paths and static-resource misses are empty 404s for
|
|
// both GET and HEAD; neither class can be mistaken for the HTML shell.
|
|
const ordinaryMisses = ['/no/such/route', '/api/no/such/route', '/empty', '/app.js/child']
|
|
const assetMisses = [
|
|
'/missing.js',
|
|
'/missing.css',
|
|
'/missing.mjs',
|
|
'/missing.js.map',
|
|
'/missing.webmanifest',
|
|
'/missing.manifest',
|
|
]
|
|
for (const path of [...ordinaryMisses, ...assetMisses]) {
|
|
const get = await request(port, path)
|
|
const head = await request(port, path, { method: 'HEAD' })
|
|
expect(get).toEqual({ status: 404, type: null, body: '' })
|
|
expect(head).toEqual(get)
|
|
}
|
|
|
|
// Traversal outside the dist root is 403, non-GET/HEAD is 405, and a
|
|
// malformed filesystem target still reaches the webserver's 400 guard.
|
|
expect((await request(port, '/..%2f..%2fetc%2fpasswd')).status).toBe(403)
|
|
expect((await request(port, '/app.js', { method: 'POST' })).status).toBe(405)
|
|
expect((await request(port, '/bad%00path')).status).toBe(400)
|
|
|
|
// HMR safety: disposing the frontend row releases the fallback seat (the
|
|
// unclaimed webserver answers 404) and the seat is claimable again.
|
|
const frontendEntry = [...loaded.loader.entries()].find(e => e.options.id === 'frontend')
|
|
expect(frontendEntry).toBeDefined()
|
|
await frontendEntry!.fiber?.dispose()
|
|
expect((await request(port, '/no/such/route')).status).toBe(404)
|
|
expect(() => server.registerFallback(() => {})).not.toThrow()
|
|
})
|
|
})
|