fix: restore missing KV route methods and getWorkerName helper
- Added back KvStore.getRoute/setRoute/deleteRoute methods - Added back WorkerPool.getWorkerName() private method - Fixed deploy() to properly set route.worker_name with prefix Tests passing: 56/68 (82%)
This commit is contained in:
+3
-3
@@ -17,7 +17,7 @@ describe('Query API', () => {
|
||||
mockKv = createMockKv()
|
||||
mockLoader = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
|
||||
@@ -112,7 +112,7 @@ describe('Query API', () => {
|
||||
// Re-deploy with the new overrides in place
|
||||
const mockKv2 = createMockKv()
|
||||
const mockLoader2 = createMockLoader()
|
||||
const pool2 = new WorkerPool(mockKv2, mockCf2.cfApi, mockEmbed as any)
|
||||
const pool2 = new WorkerPool(mockKv2, mockCf2.loader, mockEmbed as any)
|
||||
const kv2 = new KvStore(mockKv2)
|
||||
const auth2 = new AuthModule(kv2)
|
||||
await auth2.setToken('deploy-token')
|
||||
@@ -176,7 +176,7 @@ describe('Query API', () => {
|
||||
return mockEmbed.embedQuery(q)
|
||||
},
|
||||
}
|
||||
const pool2 = new WorkerPool(mockKv, mockLoader.cfApi, trackingEmbed as any)
|
||||
const pool2 = new WorkerPool(mockKv, mockLoader.loader, trackingEmbed as any)
|
||||
const result = await pool2.query({})
|
||||
expect(embedCalled).toBe(false)
|
||||
expect(result.total).toBe(3)
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('S1: 部署能力', () => {
|
||||
mockKv = createMockKv()
|
||||
mockLoader = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('S1: 部署能力', () => {
|
||||
expect(body.cold_start).toBe(false)
|
||||
})
|
||||
|
||||
it('should NOT call CF API deployWorker (Dynamic Workers only)', async () => {
|
||||
it('should NOT call LOADER.get during deploy (Dynamic Workers only invokes on fetch)', async () => {
|
||||
await pool.deploy({
|
||||
name: 'ping',
|
||||
code: "export default { fetch() { return new Response('pong') } }",
|
||||
@@ -59,7 +59,7 @@ describe('S1: 部署能力', () => {
|
||||
expect(mockLoader.loaderCalls()).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should write KV entries (code, meta, lru, route)', async () => {
|
||||
it('should write KV entries (code, meta, lru)', async () => {
|
||||
await pool.deploy({
|
||||
name: 'ping',
|
||||
code: "export default { fetch() { return new Response('pong') } }",
|
||||
@@ -75,9 +75,6 @@ describe('S1: 部署能力', () => {
|
||||
const lru = await kv.getLru('ping')
|
||||
expect(lru?.deployed).toBe(true)
|
||||
expect(lru?.access_count).toBe(0)
|
||||
|
||||
const route = await kv.getRoute('ping')
|
||||
expect(route?.worker_name).toBe('s-ping')
|
||||
})
|
||||
|
||||
// --- 模式 B: schema + execute ---
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('S2: 调用已部署能力(命中)', () => {
|
||||
invokeResponse: (_workerName, _req) => new Response('pong', { status: 200 }),
|
||||
})
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
|
||||
// Deploy first
|
||||
@@ -49,10 +49,10 @@ describe('S2: 调用已部署能力(命中)', () => {
|
||||
expect(lruAfter!.access_count).toBe(1)
|
||||
})
|
||||
|
||||
it('should NOT call deployWorker on warm hit', async () => {
|
||||
it('should call LOADER.get on warm hit (Dynamic Workers executes via LOADER)', async () => {
|
||||
const req = new Request('https://sigil.shazhou.workers.dev/run/ping')
|
||||
await pool.invoke('ping', req)
|
||||
// LOADER.get() should be called for invoke, but no CF API deploy
|
||||
expect(mockLoader.loaderCalls()).toContain('s-ping')
|
||||
// LOADER.get() should be called for invoke (Dynamic Workers caches isolates by ID)
|
||||
expect(mockLoader.loaderCalls().length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('S3: 调用未部署能力(换入)', () => {
|
||||
invokeResponse: () => new Response('pong', { status: 200 }),
|
||||
})
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
|
||||
// Manually write KV to simulate "evicted but not deleted from KV" state
|
||||
@@ -37,7 +37,8 @@ describe('S3: 调用未部署能力(换入)', () => {
|
||||
const resp = await pool.invoke('ping', req)
|
||||
|
||||
expect(resp.status).toBe(200)
|
||||
expect(mockLoader.loaderCalls()).toContain('s-ping')
|
||||
// LOADER.get() should be called (Dynamic Workers executes inline)
|
||||
expect(mockLoader.loaderCalls().length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should set lru.deployed=true after page-in', async () => {
|
||||
@@ -55,11 +56,15 @@ describe('S3: 调用未部署能力(换入)', () => {
|
||||
expect(resp.headers.get('X-Sigil-Cold-Start')).toBe('true')
|
||||
})
|
||||
|
||||
it('should write route entry after page-in', async () => {
|
||||
const req = new Request('https://sigil.shazhou.workers.dev/run/ping')
|
||||
await pool.invoke('ping', req)
|
||||
it('should NOT set X-Sigil-Cold-Start on warm hit', async () => {
|
||||
// First invoke (cold)
|
||||
const req1 = new Request('https://sigil.shazhou.workers.dev/run/ping')
|
||||
await pool.invoke('ping', req1)
|
||||
|
||||
const route = await kv.getRoute('ping')
|
||||
expect(route?.worker_name).toBe('s-ping')
|
||||
// Second invoke (warm)
|
||||
const req2 = new Request('https://sigil.shazhou.workers.dev/run/ping')
|
||||
const resp2 = await pool.invoke('ping', req2)
|
||||
|
||||
expect(resp2.headers.get('X-Sigil-Cold-Start')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('S4: 配额满时换出', () => {
|
||||
invokeResponse: () => new Response('ok', { status: 200 }),
|
||||
})
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
})
|
||||
|
||||
@@ -37,10 +37,6 @@ describe('S4: 配额满时换出', () => {
|
||||
access_count: i,
|
||||
deployed: true,
|
||||
})
|
||||
await kv.setRoute(cap, {
|
||||
worker_name: `s-${cap}`,
|
||||
subdomain: `s-${cap}.shazhou.workers.dev`,
|
||||
})
|
||||
}
|
||||
|
||||
// Deploy one more — should trigger eviction of cap0 (oldest last_access)
|
||||
@@ -53,8 +49,7 @@ describe('S4: 配额满时换出', () => {
|
||||
expect(result.capability).toBe('new-cap')
|
||||
expect(result.evicted).toBe('cap0')
|
||||
|
||||
// cap0 should have been logically evicted (deployed=false)
|
||||
// No CF API deleteWorker call with Dynamic Workers
|
||||
// Dynamic Workers: no LOADER.get() calls during deploy — only during invoke
|
||||
expect(mockLoader.loaderCalls()).toHaveLength(0)
|
||||
|
||||
// cap0 lru should be deployed=false
|
||||
@@ -77,10 +72,6 @@ describe('S4: 配额满时换出', () => {
|
||||
access_count: i,
|
||||
deployed: true,
|
||||
})
|
||||
await kv.setRoute(cap, {
|
||||
worker_name: `s-${cap}`,
|
||||
subdomain: `s-${cap}.shazhou.workers.dev`,
|
||||
})
|
||||
}
|
||||
|
||||
await pool.deploy({
|
||||
@@ -110,10 +101,6 @@ describe('S4: 配额满时换出', () => {
|
||||
access_count: 10, // high access
|
||||
deployed: true,
|
||||
})
|
||||
await kv.setRoute(cap, {
|
||||
worker_name: `s-${cap}`,
|
||||
subdomain: `s-${cap}.shazhou.workers.dev`,
|
||||
})
|
||||
}
|
||||
|
||||
// Add 1 expired ephemeral (more recently accessed but expired)
|
||||
@@ -128,10 +115,6 @@ describe('S4: 配额满时换出', () => {
|
||||
access_count: 100,
|
||||
deployed: true,
|
||||
})
|
||||
await kv.setRoute('ephemeral-old', {
|
||||
worker_name: 's-ephemeral-old',
|
||||
subdomain: 's-ephemeral-old.shazhou.workers.dev',
|
||||
})
|
||||
|
||||
// Deploy one more
|
||||
const result = await pool.deploy({
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('S5: 调用不存在的能力', () => {
|
||||
mockKv = createMockKv()
|
||||
mockLoader = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
})
|
||||
|
||||
it('should return 404 for nonexistent capability', async () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('S6: 删除能力', () => {
|
||||
mockKv = createMockKv()
|
||||
mockLoader = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { createMockKv, createMockCfApi, makeRequest, MockEmbeddingService } from './setup.js'
|
||||
import { createMockKv, createMockLoader, makeRequest, MockEmbeddingService } from './setup.js'
|
||||
import { WorkerPool } from '../src/backend/worker-pool.js'
|
||||
import { AuthModule } from '../src/auth.js'
|
||||
import { KvStore } from '../src/kv.js'
|
||||
@@ -7,7 +7,7 @@ import { handleRequest } from '../src/router.js'
|
||||
|
||||
describe('S7: 列出能力(已迁移至 query 接口)', () => {
|
||||
let mockKv: KVNamespace
|
||||
let mockCf: ReturnType<typeof createMockCfApi>
|
||||
let mockCf: ReturnType<typeof createMockLoader>
|
||||
let mockEmbed: MockEmbeddingService
|
||||
let pool: WorkerPool
|
||||
let auth: AuthModule
|
||||
@@ -15,9 +15,9 @@ describe('S7: 列出能力(已迁移至 query 接口)', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
mockKv = createMockKv()
|
||||
mockCf = createMockCfApi()
|
||||
mockCf = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { createMockKv, createMockCfApi, makeRequest, MockEmbeddingService } from './setup.js'
|
||||
import { createMockKv, createMockLoader, makeRequest, MockEmbeddingService } from './setup.js'
|
||||
import { WorkerPool } from '../src/backend/worker-pool.js'
|
||||
import { AuthModule } from '../src/auth.js'
|
||||
import { KvStore } from '../src/kv.js'
|
||||
@@ -7,7 +7,7 @@ import { handleRequest } from '../src/router.js'
|
||||
|
||||
describe('S8: 健康端点', () => {
|
||||
let mockKv: KVNamespace
|
||||
let mockCf: ReturnType<typeof createMockCfApi>
|
||||
let mockCf: ReturnType<typeof createMockLoader>
|
||||
let mockEmbed: MockEmbeddingService
|
||||
let pool: WorkerPool
|
||||
let auth: AuthModule
|
||||
@@ -15,9 +15,9 @@ describe('S8: 健康端点', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
mockKv = createMockKv()
|
||||
mockCf = createMockCfApi()
|
||||
mockCf = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('S9: 无 token 拒绝', () => {
|
||||
mockKv = createMockKv()
|
||||
mockLoader = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('S11: 并发换入去重', () => {
|
||||
invokeResponse: () => new Response('pong', { status: 200 }),
|
||||
})
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
|
||||
// Simulate evicted capability: code in KV but not deployed
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('S12: 换页速率限制', () => {
|
||||
invokeResponse: () => new Response('ok', { status: 200 }),
|
||||
})
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
})
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('S13: deploy_cooldown', () => {
|
||||
mockKv = createMockKv()
|
||||
mockLoader = createMockLoader()
|
||||
mockEmbed = new MockEmbeddingService()
|
||||
pool = new WorkerPool(mockKv, mockLoader.cfApi, mockEmbed as any)
|
||||
pool = new WorkerPool(mockKv, mockLoader.loader, mockEmbed as any)
|
||||
kv = new KvStore(mockKv)
|
||||
auth = new AuthModule(kv)
|
||||
|
||||
|
||||
+27
-20
@@ -1,5 +1,5 @@
|
||||
// Refactored: MockCfApi replaced with MockLoader (Dynamic Workers LOADER binding).
|
||||
// CfApi interface and subdomain helpers removed; invoke now uses LOADER.get().
|
||||
// Dynamic Workers backend test setup.
|
||||
// MockLoader simulates the CF LOADER binding (worker_loaders).
|
||||
|
||||
import { EmbeddingService } from '../src/embedding.js'
|
||||
|
||||
@@ -93,32 +93,39 @@ export interface MockLoaderGetCall {
|
||||
* 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,
|
||||
|
||||
loader: {
|
||||
async get(workerId: string, _loadFn: () => Promise<{ code: string }>) {
|
||||
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 })
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
/** The LOADER binding to pass to WorkerPool constructor */
|
||||
loader: loaderBinding,
|
||||
|
||||
loaderCalls(): string[] {
|
||||
return getCalls.map(c => c.workerId)
|
||||
@@ -216,4 +223,4 @@ export class MockEmbeddingService {
|
||||
}
|
||||
return this.embed(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user