61 lines
3.6 KiB
TypeScript
61 lines
3.6 KiB
TypeScript
import { expect, test } from 'bun:test'
|
|
import { patchCollectionTask, upsertCollectionTask, isStaleTaskUpdate, type CollectionQueueItem } from '../src/admin/pages/datasource-task-queue'
|
|
|
|
const item = (id: number | string | null, status: CollectionQueueItem['status'] = 'failed', type = 'collect'): CollectionQueueItem => ({
|
|
key: id == null ? `source:${type}:20` : `task:${type}:${id}`, taskId: id, sourceId: '20', source: 'celestrak_tle', name: 'CelesTrak', taskType: type,
|
|
status, error: status === 'failed' ? `failure-${id}` : undefined, createdAt: 10, updatedAt: 20, completedAt: status === 'failed' ? 20 : undefined,
|
|
})
|
|
|
|
test('a retry creates a new queue attempt and leaves every failed attempt untouched', () => {
|
|
const history = [item(97), item(96), item(95)]
|
|
let queue = upsertCollectionTask(history, item(98, 'queued'))
|
|
queue = patchCollectionTask(queue, { taskId: '98', sourceId: '20', source: 'celestrak_tle', status: 'running', progress: 40 }, 100)
|
|
expect(queue).toHaveLength(4)
|
|
expect(queue[0].taskId).toBe('98')
|
|
expect(queue[0].status).toBe('running')
|
|
expect(queue.slice(1)).toEqual(history)
|
|
queue = patchCollectionTask(queue, { taskId: 98, sourceId: '20', status: 'failed', error: 'new failure' }, 120)
|
|
expect(queue.slice(1)).toEqual(history)
|
|
expect(queue[0].completedAt).toBe(120)
|
|
})
|
|
|
|
test('late websocket and upsert events cannot resurrect or reclassify a completed attempt', () => {
|
|
const failed = item(97)
|
|
expect(patchCollectionTask([failed], { taskId: 97, sourceId: '20', status: 'running' }, 200)).toEqual([failed])
|
|
expect(upsertCollectionTask([failed], item('97', 'queued'))).toEqual([failed])
|
|
expect(patchCollectionTask([failed], { taskId: 97, status: 'success' }, 200)).toEqual([failed])
|
|
expect(patchCollectionTask([failed], { taskId: 97, status: 'failed', error: 'more detail' }, 200)[0].completedAt).toBe(20)
|
|
})
|
|
|
|
test('same-source concurrent attempts and different task types are independent', () => {
|
|
const queue = upsertCollectionTask([item(98, 'running')], item(99, 'queued'))
|
|
expect(queue).toHaveLength(2)
|
|
expect(patchCollectionTask(queue, { taskId: 98, sourceId: '20', status: 'failed' })[0].status).toBe('queued')
|
|
expect(patchCollectionTask([item(98, 'running')], { taskId: 98, taskType: 'clear_data', status: 'success' })[0].status).toBe('running')
|
|
})
|
|
|
|
test('only an active ID-less placeholder binds by source; ID-less updates cannot touch real tasks', () => {
|
|
const placeholder = item(null, 'queued')
|
|
const bound = patchCollectionTask([placeholder, item(97)], { taskId: 98, sourceId: '20', status: 'running' }, 50)
|
|
expect(bound[0].key).toBe('task:collect:98')
|
|
expect(bound[0].createdAt).toBe(10)
|
|
expect(bound[1]).toEqual(item(97))
|
|
expect(patchCollectionTask(bound, { sourceId: '20', status: 'failed' }, 60)).toEqual(bound)
|
|
const differentSource = { ...placeholder, sourceId: '21' }
|
|
expect(patchCollectionTask([differentSource], { taskId: 99, sourceId: '20', source: 'celestrak_tle', status: 'running' })).toEqual([differentSource])
|
|
})
|
|
|
|
test('old poll responses cannot replace the datasource row for a newer task', () => {
|
|
expect(isStaleTaskUpdate(98, '97')).toBe(true)
|
|
expect(isStaleTaskUpdate(98, null)).toBe(true)
|
|
expect(isStaleTaskUpdate(98, '98')).toBe(false)
|
|
expect(isStaleTaskUpdate(98, 99)).toBe(false)
|
|
expect(isStaleTaskUpdate(null, 99)).toBe(false)
|
|
})
|
|
|
|
test('late running updates for the same completed task do not reactivate the datasource row', () => {
|
|
expect(isStaleTaskUpdate(98, '98', 'failed', 'running')).toBe(true)
|
|
expect(isStaleTaskUpdate(98, '98', 'success', 'queued')).toBe(true)
|
|
expect(isStaleTaskUpdate(98, '99', 'failed', 'queued')).toBe(false)
|
|
})
|