task-144: 前端 live progress 合并补强(mergeHistoryItemPreservingLiveProgress 纯函数:缺失保留/有效覆盖/可下载不合并/幂等)+ 11 条测试

This commit is contained in:
2026-09-02 05:48:09 +08:00
parent 5e5056b99d
commit 6c674f0f1d
2 changed files with 186 additions and 0 deletions
@@ -0,0 +1,134 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import {
canDownloadItem,
FILE_PROGRESS_KEYS,
hasMeaningfulProgress,
mergeHistoryItemPreservingLiveProgress,
} from '../src/shared/merge-live-progress.ts'
interface HistoryItem {
taskId: number
resultId?: number
fileReady?: boolean
fileStatus?: string
fileProgressPercent?: number
fileProgressCurrent?: number
fileProgressTotal?: number
fileProgressMessage?: string
downloadUrl?: string
sourceFilename?: string
}
const historyMissingProgress: HistoryItem = {
taskId: 1,
resultId: 100,
fileStatus: 'RUNNING',
sourceFilename: 'source.xlsx',
}
const liveWithProgress: HistoryItem = {
taskId: 1,
resultId: 100,
fileStatus: 'RUNNING',
fileProgressPercent: 45,
fileProgressCurrent: 9,
fileProgressTotal: 20,
fileProgressMessage: 'LLM 处理中',
}
test('merge keeps live progress when history missing file progress', () => {
const merged = mergeHistoryItemPreservingLiveProgress(historyMissingProgress, liveWithProgress)
assert.equal(merged.fileProgressPercent, 45, 'history 缺失进度时保留 live 的百分比')
assert.equal(merged.fileProgressCurrent, 9)
assert.equal(merged.fileProgressTotal, 20)
assert.equal(merged.fileProgressMessage, 'LLM 处理中')
assert.equal(merged.sourceFilename, 'source.xlsx', '非进度字段保留 history')
})
test('merge incoming wins when live has progress', () => {
const history = { ...historyMissingProgress, fileProgressPercent: 10, fileProgressMessage: '旧消息' }
const merged = mergeHistoryItemPreservingLiveProgress(history, liveWithProgress)
assert.equal(merged.fileProgressPercent, 45, 'live 有有效进度时以 live 为准')
assert.equal(merged.fileProgressMessage, 'LLM 处理中')
})
test('merge does not keep live when history is downloadable', () => {
const history: HistoryItem = {
taskId: 1,
resultId: 100,
fileReady: true,
fileStatus: 'SUCCESS',
fileProgressPercent: 100,
downloadUrl: 'https://dl.example/a.xlsx',
}
const merged = mergeHistoryItemPreservingLiveProgress(history, liveWithProgress)
assert.equal(merged.fileProgressPercent, 100, '可下载时保留 history 的终态进度')
assert.equal(merged.downloadUrl, 'https://dl.example/a.xlsx')
assert.equal(merged.fileReady, true)
})
test('merge without live returns history as-is', () => {
const merged = mergeHistoryItemPreservingLiveProgress(historyMissingProgress, undefined)
assert.equal(merged, historyMissingProgress, '无 live 时原样返回 history')
assert.equal(merged.fileProgressPercent, undefined)
})
test('merge partial fields keeps only present live values', () => {
const live: HistoryItem = { taskId: 1, resultId: 100, fileStatus: 'RUNNING', fileProgressPercent: 30 }
const merged = mergeHistoryItemPreservingLiveProgress(historyMissingProgress, live)
assert.equal(merged.fileProgressPercent, 30)
assert.equal(merged.fileProgressCurrent, undefined, 'live 缺失的字段不写入')
assert.equal(merged.fileProgressTotal, undefined)
assert.equal(merged.fileStatus, 'RUNNING')
})
test('merge ignores meaningless progress values', () => {
assert.equal(hasMeaningfulProgress('fileProgressPercent', 0), false, '0% 不算有效进度')
assert.equal(hasMeaningfulProgress('fileProgressPercent', -1), false)
assert.equal(hasMeaningfulProgress('fileProgressPercent', 5), true)
assert.equal(hasMeaningfulProgress('fileProgressCurrent', 0), true, '0 是合法计数')
assert.equal(hasMeaningfulProgress('fileProgressMessage', null), false)
assert.equal(hasMeaningfulProgress('fileProgressMessage', undefined), false)
const live: HistoryItem = { taskId: 1, resultId: 100, fileProgressPercent: 0 }
const merged = mergeHistoryItemPreservingLiveProgress(historyMissingProgress, live)
assert.equal(merged.fileProgressPercent, undefined, '0% 不覆盖 history')
})
test('merge preserves fileReady flag from live', () => {
const live: HistoryItem = { taskId: 1, resultId: 100, fileReady: true, fileProgressPercent: 100 }
const merged = mergeHistoryItemPreservingLiveProgress(historyMissingProgress, live)
assert.equal(merged.fileReady, true)
assert.equal(merged.fileProgressPercent, 100)
})
test('merge is idempotent', () => {
const once = mergeHistoryItemPreservingLiveProgress(historyMissingProgress, liveWithProgress)
const twice = mergeHistoryItemPreservingLiveProgress(once, liveWithProgress)
assert.deepEqual(twice, once, '重复合并结果一致')
})
test('file progress keys cover the live progress contract', () => {
assert.deepEqual(
[...FILE_PROGRESS_KEYS].sort(),
[
'fileProgressCurrent',
'fileProgressMessage',
'fileProgressPercent',
'fileProgressTotal',
'fileReady',
'fileStatus',
].sort(),
)
})
test('canDownloadItem requires result and ready state', () => {
assert.equal(canDownloadItem({ resultId: 1, fileReady: true }), true)
assert.equal(canDownloadItem({ resultId: 1, downloadUrl: 'https://x' }), true)
assert.equal(canDownloadItem({ resultId: 1 }), false)
assert.equal(canDownloadItem({ fileReady: true }), false, '无 resultId 不可下载')
assert.equal(canDownloadItem(null), false)
assert.equal(canDownloadItem(undefined), false)
})