Files
sigil/test/setup.ts
T
xiaoju e86bae8d4a refactor: migrate to Dynamic Workers — fix /run/{name} 404
Root cause: CF blocks Worker-to-Worker fetch on workers.dev (error 1042).
Gateway Worker could not proxy requests to child worker subdomains.

Fix: Replace CF API worker scripts with Dynamic Workers (LOADER binding).
- deploy() writes code to KV only, no CF API calls
- invoke() uses LOADER.get(id, fn) to execute code inline
- remove() clears KV only, no CF API delete
- Removed cf-api.ts, slot management, subdomain routing
- 67/67 tests passing, production verified

Reported-by: 小墨 🖊️ (KUMA)
小橘 🍊(NEKO Team)
2026-04-03 10:57:50 +00:00

227 lines
6.2 KiB
TypeScript

// Dynamic Workers backend test setup.
// MockLoader simulates the CF LOADER binding (worker_loaders).
import { EmbeddingService } from '../src/embedding.js'
export interface MockKvEntry {
value: string
metadata?: unknown
}
/**
* In-memory KVNamespace mock.
*/
export function createMockKv(): KVNamespace {
const store = new Map<string, MockKvEntry>()
return {
async get(key: string, options?: { type?: string } | string): Promise<unknown> {
const entry = store.get(key)
if (!entry) return null
const type = typeof options === 'string' ? options : options?.type ?? 'text'
if (type === 'json') {
try {
return JSON.parse(entry.value)
} catch {
return null
}
}
if (type === 'arrayBuffer') {
return new TextEncoder().encode(entry.value).buffer
}
if (type === 'stream') {
return new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(entry.value))
controller.close()
},
})
}
return entry.value
},
async getWithMetadata(key: string, options?: { type?: string } | string): Promise<{ value: unknown; metadata: unknown }> {
const entry = store.get(key)
if (!entry) return { value: null, metadata: null }
const type = typeof options === 'string' ? options : options?.type ?? 'text'
let value: unknown = entry.value
if (type === 'json') value = JSON.parse(entry.value)
return { value, metadata: entry.metadata ?? null }
},
async put(key: string, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: { expiration?: number; expirationTtl?: number; metadata?: unknown }): Promise<void> {
let strVal: string
if (typeof value === 'string') {
strVal = value
} else if (value instanceof ArrayBuffer) {
strVal = new TextDecoder().decode(value)
} else {
strVal = String(value)
}
store.set(key, { value: strVal, metadata: options?.metadata })
},
async delete(key: string): Promise<void> {
store.delete(key)
},
async list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<KVNamespaceListResult<unknown, string>> {
const prefix = options?.prefix ?? ''
const limit = options?.limit ?? 1000
const keys = Array.from(store.keys())
.filter(k => k.startsWith(prefix))
.slice(0, limit)
.map(name => ({ name, expiration: undefined, metadata: undefined }))
return {
keys,
list_complete: true,
cursor: '',
cacheStatus: null,
}
},
} as unknown as KVNamespace
}
export interface MockLoaderGetCall {
workerId: string
}
/**
* Mock Dynamic Workers LOADER binding.
* Records LOADER.get() calls and returns a mock Worker whose
* getEntrypoint().fetch() delegates to the provided invokeResponse factory.
*
* NOTE: CF LOADER.get() is synchronous — returns a Worker instance directly.
* The mock mirrors this behavior.
*/
export function createMockLoader(overrides?: {
invokeResponse?: (workerId: string, request: Request) => Response | Promise<Response>
}) {
const getCalls: MockLoaderGetCall[] = []
// Synchronous LOADER mock — matches the real CF LOADER.get() API
const loaderBinding = {
get(workerId: string, _loadFn: () => { compatibilityDate: string; mainModule: string; modules: Record<string, string>; globalOutbound: null }) {
getCalls.push({ workerId })
return {
getEntrypoint() {
return {
async fetch(request: Request): Promise<Response> {
if (overrides?.invokeResponse) {
return overrides.invokeResponse(workerId, request)
}
return new Response('mock response', { status: 200 })
},
}
},
}
},
}
return {
getCalls,
/** The LOADER binding to pass to WorkerPool constructor */
loader: loaderBinding,
loaderCalls(): string[] {
return getCalls.map(c => c.workerId)
},
reset(): void {
getCalls.length = 0
},
}
}
/**
* Create a test request helper.
*/
export function makeRequest(
method: string,
path: string,
options?: {
body?: unknown
token?: string
headers?: Record<string, string>
},
): Request {
const url = `https://sigil.shazhou.workers.dev${path}`
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options?.headers,
}
if (options?.token) {
headers['Authorization'] = `Bearer ${options.token}`
}
const init: RequestInit = {
method,
headers,
}
if (options?.body !== undefined) {
init.body = JSON.stringify(options.body)
}
return new Request(url, init)
}
// Simple deterministic hash (for mock vectors)
function simpleHash(text: string): number {
let h = 0x811c9dc5
for (let i = 0; i < text.length; i++) {
h ^= text.charCodeAt(i)
h = (h * 0x01000193) >>> 0
}
return h
}
// Generate a deterministic unit vector of given dimension
function generateDeterministicVector(seed: number, dim: number): number[] {
const vec: number[] = []
let s = seed
for (let i = 0; i < dim; i++) {
s = (s * 1664525 + 1013904223) >>> 0
vec.push((s / 0xffffffff) * 2 - 1)
}
const norm = Math.sqrt(vec.reduce((a, x) => a + x * x, 0))
return vec.map(x => x / norm)
}
/**
* Mock EmbeddingService for unit tests.
* Returns deterministic vectors. Supports manual vector overrides
* to simulate semantic similarity.
*/
export class MockEmbeddingService {
private overrides = new Map<string, number[]>()
static buildCapabilityText(params: any): string {
return EmbeddingService.buildCapabilityText(params)
}
setVector(textOrKey: string, vector: number[]): void {
this.overrides.set(textOrKey, vector)
}
async embed(text: string): Promise<number[]> {
if (this.overrides.has(text)) {
return this.overrides.get(text)!
}
const hash = simpleHash(text)
return generateDeterministicVector(hash, 768)
}
async embedQuery(query: string): Promise<number[]> {
if (this.overrides.has(query)) {
return this.overrides.get(query)!
}
return this.embed(query)
}
}