Files
knowledge-governance/web/src/qaAllRecords.test.ts
T
姚章浩 90901a4458
build / build (push) Successful in 1m1s
feat(qa-ops): 默认本周快捷查询并全量展示固定字段
新增本周直接查询按钮,所选范围自动分批读取并校验总数、重复与游标完整性;删除手动加载更多,固定展示完整字段,空列不隐藏,不伪造译文或评估。

验证:全量、固定列及本周入口回归先红后绿;完整make verify通过,前端273通过;真实只读源浏览器默认及快捷查询均639条全部唯一展示,14必需字段按序,右侧字段可达、手机无外溢、JS异常0。无新增迁移或源库写入。
2026-10-18 16:32:16 +08:00

89 lines
3.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { fetchAllQARecords } from './qaOps'
const row = (id: string) => ({
id,
status: 'succeeded',
question: '明确合成问题',
answer: '明确合成答案',
asked_at: '2026-10-14T03:00:00Z',
time_source: 'request_message.created_at',
asker: '合成夹具',
trigger_person: '',
department: '',
region: '',
references: [],
media: [],
source_coverage: { verdict: '', reason_code: '', evaluator_version: '' },
feedback: { asker_value: '', up: 0, down: 0 },
coverage: { state: 'pending', rule_version: 'fixture-rule' },
})
const page = (ids: string[], total: number, cursor = '') => ({
items: ids.map(row),
columns: [],
total,
has_more: Boolean(cursor),
next_cursor: cursor,
as_of: '2026-10-14T04:00:00Z',
})
const query = { start: '2026-10-11T16:00:00Z', end: '2026-10-18T08:00:00Z', keyword: '%_原文', cursor: 'stale-cursor' }
const respond = (data: unknown) => new Response(JSON.stringify({ data }), { status: 200 })
afterEach(() => vi.unstubAllGlobals())
describe('all QA records without manual pagination', () => {
it('rejects incomplete, duplicate, drifting and cycling batches', async () => {
const cases = [
[page(['a'], 2)],
[page(['a'], 2, 'n'), page(['a'], 2)],
[page(['a'], 2, 'n'), page(['b'], 3)],
[page(['a'], 3, 'n'), page(['b'], 3, 'n')],
]
for (const pages of cases) {
const fetcher = vi.fn()
pages.forEach((data) => fetcher.mockResolvedValueOnce(respond(data)))
vi.stubGlobal('fetch', fetcher)
await expect(fetchAllQARecords(query)).rejects.toThrow()
}
})
it('surfaces a later source failure instead of returning the first batch', async () => {
const fetcher = vi
.fn()
.mockResolvedValueOnce(respond(page(['a'], 2, 'n')))
.mockResolvedValueOnce(new Response(JSON.stringify({ error: '明确合成源不可用' }), { status: 503 }))
vi.stubGlobal('fetch', fetcher)
await expect(fetchAllQARecords(query)).rejects.toThrow('明确合成源不可用')
})
it('accepts a genuinely empty complete scope', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(respond(page([], 0))))
expect((await fetchAllQARecords(query)).items).toEqual([])
})
it('does not start or continue requests after cancellation', async () => {
const controller = new AbortController()
controller.abort()
const fetcher = vi.fn().mockResolvedValue(respond(page([], 0)))
vi.stubGlobal('fetch', fetcher)
await expect(fetchAllQARecords(query, controller.signal)).rejects.toMatchObject({ name: 'AbortError' })
expect(fetcher).not.toHaveBeenCalled()
})
it('reads every transport batch and verifies final count before returning', async () => {
const fetcher = vi
.fn()
.mockResolvedValueOnce(respond(page(['a', 'b'], 3, 'next')))
.mockResolvedValueOnce(respond(page(['c'], 3)))
vi.stubGlobal('fetch', fetcher)
const progress = vi.fn()
const result = await fetchAllQARecords(query, undefined, progress)
expect(result.items.map((r) => r.id)).toEqual(['a', 'b', 'c'])
expect(result.has_more).toBe(false)
expect(result.total).toBe(3)
expect(progress.mock.calls).toEqual([
[2, 3],
[3, 3],
])
const urls = fetcher.mock.calls.map((call) => new URL(String(call[0]), 'http://localhost'))
expect(urls.map((url) => url.searchParams.get('cursor'))).toEqual(['', 'next'])
expect(
urls.every((url) => url.searchParams.get('limit') === '100' && url.searchParams.get('keyword') === query.keyword)
).toBe(true)
})
})