175 lines
6.8 KiB
TypeScript
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)
|
|
})
|