Results polled one drafts endpoint per schedule every minute, archived ones included, so the Network tab filled with video-drafts reads. Each poll now reads GET /agents/status once (with If-None-Match, mostly a 304) and reads a schedule's drafts again only when its status changed, while a production plan is at work, or after 30 minutes so signed video URLs stay valid. Without the status read, every schedule is read as before. The poll is now every 30s. Also documents how Express uses the engine API in docs/engine-api.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.1 KiB
TypeScript
58 lines
2.1 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { limiter, pacer, request } from './client'
|
|
|
|
describe('limiter', () => {
|
|
it('runs at most n calls at once, all of them, failures included', async () => {
|
|
const limit = limiter(2)
|
|
let running = 0, peak = 0
|
|
const call = (n: number) => limit(async () => {
|
|
peak = Math.max(peak, ++running)
|
|
await new Promise(r => setTimeout(r, 5))
|
|
running--
|
|
if (n === 2) throw new Error('503')
|
|
return n
|
|
})
|
|
const out = await Promise.allSettled([1, 2, 3, 4, 5].map(call))
|
|
expect(peak).toBe(2)
|
|
expect(out.map(r => (r.status === 'fulfilled' ? r.value : 'x'))).toEqual([1, 'x', 3, 4, 5])
|
|
})
|
|
})
|
|
|
|
describe('pacer', () => {
|
|
it('lets a burst through, then spaces the rest out in order', async () => {
|
|
const pace = pacer(100, 2)
|
|
const t0 = Date.now()
|
|
const order: number[] = []
|
|
await Promise.all([1, 2, 3, 4, 5, 6].map(n => pace.wait().then(() => order.push(n))))
|
|
// Two at once, then four more at 10 ms each.
|
|
expect(Date.now() - t0).toBeGreaterThanOrEqual(35)
|
|
expect(order).toEqual([1, 2, 3, 4, 5, 6])
|
|
})
|
|
|
|
it('pauses everything after the proxy turns one away', async () => {
|
|
const pace = pacer(100, 5)
|
|
pace.pause(50)
|
|
const t0 = Date.now()
|
|
await pace.wait()
|
|
expect(Date.now() - t0).toBeGreaterThanOrEqual(45)
|
|
})
|
|
})
|
|
|
|
describe('request with revalidate', () => {
|
|
afterEach(() => vi.unstubAllGlobals())
|
|
|
|
it('sends the last ETag back and answers a 304 with the last body', async () => {
|
|
const sent: (string | null)[] = []
|
|
vi.stubGlobal('fetch', vi.fn(async (_url: string, init: RequestInit) => {
|
|
const tag = new Headers(init.headers).get('If-None-Match')
|
|
sent.push(tag)
|
|
return tag
|
|
? new Response(null, { status: 304, headers: { ETag: 'W/"1"' } })
|
|
: new Response(JSON.stringify({ agents: [1] }), { status: 200, headers: { ETag: 'W/"1"' } })
|
|
}))
|
|
expect(await request('/etag-test', { revalidate: true })).toEqual({ agents: [1] })
|
|
expect(await request('/etag-test', { revalidate: true })).toEqual({ agents: [1] })
|
|
expect(sent).toEqual([null, 'W/"1"'])
|
|
})
|
|
})
|