Files
crawler-plugin/frontend-vue/tests/page-e2e-core-path.test.ts
T

175 lines
6.8 KiB
TypeScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { runPageE2ECorePath, type PageE2EDeps } from '../src/shared/page-e2e-core-path.ts'
const terminal = (status: string) => status === 'SUCCESS' || status === 'FAILED'
function makeDeps(over: Partial<PageE2EDeps> = {}): PageE2EDeps & { calls: string[] } {
const calls: string[] = []
return {
calls,
parse: async () => {
calls.push('parse')
return 101
},
createTask: async (taskId: number) => {
calls.push(`create:${taskId}`)
},
poll: async (taskId: number) => {
calls.push(`poll:${taskId}`)
return 'SUCCESS'
},
refreshHistory: async () => {
calls.push('refresh')
},
...over,
}
}
test('test_task_093_collect_asin_e2e_normal_default_path', async () => {
const deps = makeDeps()
const result = await runPageE2ECorePath(deps)
assert.equal(result.taskId, 101)
assert.equal(result.status, 'SUCCESS')
assert.deepEqual(result.completedSteps, ['parse', 'create-task', 'poll', 'refresh-history'])
assert.equal(result.attempts, 1)
assert.deepEqual(deps.calls, ['parse', 'create:101', 'poll:101', 'refresh'])
})
test('test_task_093_collect_asin_e2e_normal_multiple_items', async () => {
// 三个页面核心路径批量执行:结果不丢失且各页面顺序稳定
const pages = ['similar-asin', 'shop-data-crawl', 'collect-data']
const depss = pages.map((page, i) =>
makeDeps({
parse: async () => {
depss[i].calls.push(`parse:${page}`)
return 200 + i
},
poll: async () => {
depss[i].calls.push(`poll:${page}`)
return 'SUCCESS'
},
}),
)
const results = await Promise.all(pages.map((_, i) => runPageE2ECorePath(depss[i])))
assert.equal(results.length, 3)
assert.deepEqual(
results.map((r) => r.taskId),
[200, 201, 202],
)
for (let i = 0; i < 3; i++) {
assert.equal(results[i].status, 'SUCCESS')
assert.deepEqual(results[i].completedSteps, ['parse', 'create-task', 'poll', 'refresh-history'])
assert.deepEqual(depss[i].calls, [`parse:${pages[i]}`, `create:${200 + i}`, `poll:${pages[i]}`, 'refresh'])
}
})
test('test_task_093_collect_asin_e2e_normal_repeated_operation_is_idempotent', async () => {
const deps = makeDeps()
const first = await runPageE2ECorePath(deps)
const second = await runPageE2ECorePath(deps)
assert.equal(first.taskId, second.taskId)
assert.deepEqual(first.completedSteps, second.completedSteps)
// 重复执行不产生重复状态:调用计数线性增长,无残留
assert.equal(deps.calls.filter((c) => c === 'parse').length, 2)
assert.equal(deps.calls.filter((c) => c === 'refresh').length, 2)
assert.equal(deps.calls.length, 8)
})
test('test_task_093_collect_asin_e2e_boundary_empty_input', async () => {
// parse 无可处理数据:安全跳过建任务/轮询/刷新,不创建无效资源
const deps = makeDeps({ parse: async () => 0 })
const result = await runPageE2ECorePath(deps)
assert.equal(result.taskId, 0)
assert.deepEqual(result.completedSteps, ['parse'])
assert.deepEqual(deps.calls, [], 'parse 返回 0 时不再触发任何调用')
// 空步骤序列等价处理:不执行任何步骤
const empty = makeDeps({ parse: async () => 0 })
assert.deepEqual((await runPageE2ECorePath(empty)).completedSteps, ['parse'])
})
test('test_task_093_collect_asin_e2e_boundary_single_item', async () => {
// 单任务:一次轮询即终态,不依赖批量路径
const deps = makeDeps()
const result = await runPageE2ECorePath(deps, { maxPollAttempts: 3 })
assert.equal(result.taskId, 101)
assert.equal(result.attempts, 1)
assert.equal(result.status, 'SUCCESS')
assert.equal(deps.calls.filter((c) => c.startsWith('poll')).length, 1)
})
test('test_task_093_collect_asin_e2e_boundary_limit_and_overflow', async () => {
// 轮询次数有界:超过 maxPollAttempts 降级返回当前状态,不发生无界轮询
let pollCount = 0
const deps = makeDeps({
poll: async () => {
pollCount += 1
return 'RUNNING'
},
})
const result = await runPageE2ECorePath(deps, { maxPollAttempts: 4 })
assert.equal(result.status, 'RUNNING', '超过上限降级返回,不抛错')
assert.equal(result.attempts, 4)
assert.equal(pollCount, 4)
assert.deepEqual(result.completedSteps, ['parse', 'create-task', 'poll', 'poll', 'poll', 'poll'])
assert.ok(!result.completedSteps.includes('refresh-history'), '终态未达成不刷新历史')
})
test('test_task_093_collect_asin_e2e_invalid_input_rejected', async () => {
await assert.rejects(() => runPageE2ECorePath(null as never), /deps 必须是对象/)
await assert.rejects(() => runPageE2ECorePath({} as never), /parse 必须是函数/)
await assert.rejects(
() => runPageE2ECorePath({ parse: async () => 1 } as never),
/createTask 必须是函数/,
)
const missingPoll = makeDeps()
delete (missingPoll as Partial<PageE2EDeps>).poll
await assert.rejects(() => runPageE2ECorePath(missingPoll as never), /poll 必须是函数/)
const deps = makeDeps()
await assert.rejects(() => runPageE2ECorePath(deps, { maxPollAttempts: 0 }), /maxPollAttempts 必须为正数/)
await assert.rejects(() => runPageE2ECorePath(deps, { maxPollAttempts: -1 }), /maxPollAttempts 必须为正数/)
// parse 返回非法 taskId
const badTask = makeDeps({ parse: async () => -5 })
await assert.rejects(() => runPageE2ECorePath(badTask), /parse 必须返回正整数 taskId/)
})
test('test_task_093_collect_asin_e2e_dependency_failure_releases_resources', async () => {
// 断网:poll 抛错 → 执行终止、cleanup 释放资源;恢复后同一 deps 重跑成功
let broken = true
let cleaned = 0
const deps = makeDeps({
poll: async (taskId: number) => {
if (broken) throw new Error('Network Error')
return 'SUCCESS'
},
cleanup: async () => {
cleaned += 1
},
})
await assert.rejects(() => runPageE2ECorePath(deps), /Network Error/)
assert.equal(cleaned, 1, '失败后 cleanup 释放资源')
broken = false
const result = await runPageE2ECorePath(deps)
assert.equal(result.status, 'SUCCESS')
assert.equal(cleaned, 2, '成功路径同样清理')
assert.equal(result.taskId, 101)
// 中途失败(createTask 抛错)→ 剩余步骤跳过 + cleanup 执行
let createFailed = true
const deps2 = makeDeps({
createTask: async (taskId: number) => {
if (createFailed) throw new Error('create down')
deps2.calls.push(`create:${taskId}`)
},
cleanup: async () => {
cleaned += 1
},
})
await assert.rejects(() => runPageE2ECorePath(deps2), /create down/)
assert.equal(cleaned, 3)
assert.deepEqual(deps2.calls, ['parse'], 'create 失败后不再 poll/refresh')
createFailed = false
const retry = await runPageE2ECorePath(deps2)
assert.equal(retry.status, 'SUCCESS')
assert.equal(cleaned, 4)
})