Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #105.
This commit is contained in:
@@ -14,7 +14,7 @@ function job(status: string, result: Draft | null, error: string | null = null)
|
||||
function renderJob(jobId: string | null) {
|
||||
const api = createApiClient(fakeLocation(), createNetworkStatus(window), createVerifiedSession(localStorage))
|
||||
const wrapper = ({ children }: { children: ReactNode }) => <ApiProvider client={api}>{children}</ApiProvider>
|
||||
return renderHook(({ id }) => useJob<Draft>(id), { wrapper, initialProps: { id: jobId } })
|
||||
return renderHook(({ id }) => useJob<Draft>('plan.draft', id), { wrapper, initialProps: { id: jobId } })
|
||||
}
|
||||
|
||||
// advance runs the timers due in ms and lets the polls they start answer.
|
||||
@@ -42,7 +42,7 @@ describe('useJob', () => {
|
||||
job('succeeded', { dinners: ['Tacos', 'Chili', 'Soup'] }),
|
||||
)
|
||||
const { result } = renderJob('j1')
|
||||
expect(result.current).toEqual({ status: undefined, result: null, error: null })
|
||||
expect(result.current).toMatchObject({ status: undefined, result: null, error: null })
|
||||
|
||||
await advance(0)
|
||||
expect(result.current.status).toBe('queued')
|
||||
@@ -51,7 +51,7 @@ describe('useJob', () => {
|
||||
await advance(499)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||||
await advance(1)
|
||||
expect(result.current).toEqual({ status: 'running', result: { dinners: ['Tacos'] }, error: null })
|
||||
expect(result.current).toMatchObject({ status: 'running', result: { dinners: ['Tacos'] }, error: null })
|
||||
|
||||
await advance(999)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
@@ -62,7 +62,7 @@ describe('useJob', () => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4)
|
||||
await advance(2000)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(5)
|
||||
expect(result.current).toEqual({
|
||||
expect(result.current).toMatchObject({
|
||||
status: 'succeeded',
|
||||
result: { dinners: ['Tacos', 'Chili', 'Soup'] },
|
||||
error: null,
|
||||
@@ -106,10 +106,76 @@ describe('useJob', () => {
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
await advance(1)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3)
|
||||
expect(result.current).toEqual({ status: 'succeeded', result: { dinners: ['Tacos', 'Chili'] }, error: null })
|
||||
expect(result.current).toMatchObject({ status: 'succeeded', result: { dinners: ['Tacos', 'Chili'] }, error: null })
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['an unknown status', { id: 'j1', kind: 'plan.draft', status: 'stuck', result: null, error: null }],
|
||||
['another job', { id: 'j2', kind: 'plan.draft', status: 'running', result: null, error: null }],
|
||||
['another kind', { id: 'j1', kind: 'suggestion_batch', status: 'running', result: null, error: null }],
|
||||
['no result', { id: 'j1', kind: 'plan.draft', status: 'running', error: null }],
|
||||
['an error that is not text', { id: 'j1', kind: 'plan.draft', status: 'failed', result: null, error: 500 }],
|
||||
['not a job', ['j1']],
|
||||
])('exposes an answer with %s as the error and stops, keeping the last status', async (_, body) => {
|
||||
const fetchMock = stubFetch(job('running', { dinners: ['Tacos'] }), jsonResponse(200, body))
|
||||
const { result } = renderJob('j1')
|
||||
|
||||
await advance(0)
|
||||
await advance(500)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
expect(result.current).toMatchObject({ status: 'running', result: { dinners: ['Tacos'] } })
|
||||
expect(result.current.error?.message).toBe('GET /api/v1/jobs/j1: response is not the plan.draft job j1')
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
await advance(10_000)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('polls again at once on poll after stopping on an answer that is not the job, then keeps polling', async () => {
|
||||
const fetchMock = stubFetch(
|
||||
jsonResponse(200, { id: 'j1', kind: 'plan.draft', status: 'stuck', result: null, error: null }),
|
||||
job('running', { dinners: ['Tacos'] }),
|
||||
job('succeeded', { dinners: ['Tacos', 'Chili'] }),
|
||||
)
|
||||
const { result } = renderJob('j1')
|
||||
await advance(0)
|
||||
expect(result.current.error).not.toBeNull()
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
|
||||
act(() => result.current.poll())
|
||||
await advance(0)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
expect(result.current).toMatchObject({ status: 'running', result: { dinners: ['Tacos'] }, error: null })
|
||||
await advance(500)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3)
|
||||
expect(result.current).toMatchObject({ status: 'succeeded', error: null })
|
||||
})
|
||||
|
||||
it('polls at once on poll during the backoff after a failed poll, starting the backoff again', async () => {
|
||||
const fetchMock = stubFetch(
|
||||
job('running', null),
|
||||
errorResponse(503, 'unavailable', true),
|
||||
errorResponse(503, 'unavailable', true),
|
||||
job('running', { dinners: ['Tacos'] }),
|
||||
job('succeeded', { dinners: ['Tacos'] }),
|
||||
)
|
||||
const { result } = renderJob('j1')
|
||||
await advance(0)
|
||||
await advance(500)
|
||||
await advance(1000)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3)
|
||||
|
||||
// The next poll was due 2 s later; poll asks now.
|
||||
act(() => result.current.poll())
|
||||
await advance(0)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4)
|
||||
expect(result.current).toMatchObject({ status: 'running', result: { dinners: ['Tacos'] }, error: null })
|
||||
expect(vi.getTimerCount()).toBe(1)
|
||||
await advance(500)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(5)
|
||||
expect(result.current.status).toBe('succeeded')
|
||||
})
|
||||
|
||||
it('stops polling on unmount after a failed poll', async () => {
|
||||
const fetchMock = stubFetch(errorResponse(503, 'unavailable', true))
|
||||
const { result, unmount } = renderJob('j1')
|
||||
@@ -125,12 +191,15 @@ describe('useJob', () => {
|
||||
})
|
||||
|
||||
it('stops polling the old job when the job id changes', async () => {
|
||||
const fetchMock = stubFetch(job('running', null), job('running', null))
|
||||
const fetchMock = stubFetch(
|
||||
job('running', null),
|
||||
jsonResponse(200, { id: 'j2', kind: 'plan.draft', status: 'running', result: null, error: null }),
|
||||
)
|
||||
const { result, rerender } = renderJob('j1')
|
||||
|
||||
await advance(0)
|
||||
rerender({ id: 'j2' })
|
||||
expect(result.current).toEqual({ status: undefined, result: null, error: null })
|
||||
expect(result.current).toMatchObject({ status: undefined, result: null, error: null })
|
||||
await advance(0)
|
||||
expect(request(fetchMock, 1).path).toBe('/api/v1/jobs/j2')
|
||||
expect(vi.getTimerCount()).toBe(1)
|
||||
@@ -165,6 +234,6 @@ describe('useJob', () => {
|
||||
const { result } = renderJob(null)
|
||||
await advance(5000)
|
||||
expect(fetchMock).not.toHaveBeenCalled()
|
||||
expect(result.current).toEqual({ status: undefined, result: null, error: null })
|
||||
expect(result.current).toMatchObject({ status: undefined, result: null, error: null })
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user