fix(api): 避免恢复后二次确认重复执行已完成平台
This commit is contained in:
parent
fba6dd5272
commit
fff35a157c
@ -317,6 +317,97 @@ describe("API server", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("does not rerun completed platforms when confirming a newly recovered platform", async () => {
|
||||
const app = createServer();
|
||||
await app.ready();
|
||||
|
||||
const createdTask = await createTask(app, "iPhone 15 Pro");
|
||||
const firstCandidatesResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/tasks/${createdTask.taskId}/candidates`
|
||||
});
|
||||
const firstCandidates = firstCandidatesResponse.json().candidates;
|
||||
|
||||
const firstConfirmResponse = await app.inject({
|
||||
method: "POST",
|
||||
url: `/api/tasks/${createdTask.taskId}/confirm`,
|
||||
payload: {
|
||||
selections: [
|
||||
{
|
||||
platform: "tmall",
|
||||
candidateIds: [firstCandidates.tmall[0].candidateId]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(firstConfirmResponse.statusCode).toBe(200);
|
||||
expect(firstConfirmResponse.json().task.taskStatus).toBe("Completed");
|
||||
expect(firstConfirmResponse.json().task.reportVersions).toEqual([1]);
|
||||
|
||||
const attemptsAfterFirstConfirm = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/tasks/${createdTask.taskId}/strategy-attempts`
|
||||
});
|
||||
const firstTmallExecutionAttempts = attemptsAfterFirstConfirm
|
||||
.json()
|
||||
.attempts.filter(
|
||||
(attempt: { platform: string; capability: string }) =>
|
||||
attempt.platform === "tmall" &&
|
||||
(attempt.capability === "detail" || attempt.capability === "reviews")
|
||||
);
|
||||
expect(firstTmallExecutionAttempts).toHaveLength(2);
|
||||
|
||||
await preparePlatform(app, "jd");
|
||||
const retryResponse = await app.inject({
|
||||
method: "POST",
|
||||
url: `/api/tasks/${createdTask.taskId}/platforms/jd/retry`
|
||||
});
|
||||
|
||||
expect(retryResponse.statusCode).toBe(200);
|
||||
|
||||
const recoveredCandidatesResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/tasks/${createdTask.taskId}/candidates`
|
||||
});
|
||||
const recoveredCandidates = recoveredCandidatesResponse.json().candidates;
|
||||
|
||||
const secondConfirmResponse = await app.inject({
|
||||
method: "POST",
|
||||
url: `/api/tasks/${createdTask.taskId}/confirm`,
|
||||
payload: {
|
||||
selections: [
|
||||
{
|
||||
platform: "jd",
|
||||
candidateIds: [recoveredCandidates.jd[0].candidateId]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
expect(secondConfirmResponse.statusCode).toBe(200);
|
||||
expect(secondConfirmResponse.json().task.taskStatus).toBe("Completed");
|
||||
expect(secondConfirmResponse.json().task.reportVersions).toEqual([1, 2]);
|
||||
|
||||
const attemptsAfterSecondConfirm = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/tasks/${createdTask.taskId}/strategy-attempts`
|
||||
});
|
||||
const executionAttempts = attemptsAfterSecondConfirm.json().attempts.filter(
|
||||
(attempt: { platform: string; capability: string }) =>
|
||||
attempt.capability === "detail" || attempt.capability === "reviews"
|
||||
);
|
||||
|
||||
expect(
|
||||
executionAttempts.filter((attempt: { platform: string }) => attempt.platform === "tmall")
|
||||
).toHaveLength(2);
|
||||
expect(
|
||||
executionAttempts.filter((attempt: { platform: string }) => attempt.platform === "jd")
|
||||
).toHaveLength(2);
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("records recovery audit entries and retry metrics for recovered platforms", async () => {
|
||||
const app = createServer();
|
||||
await app.ready();
|
||||
|
||||
@ -46,6 +46,16 @@ function createPlatformCandidatesRecord(): Record<PlatformId, CandidateRecord[]>
|
||||
};
|
||||
}
|
||||
|
||||
function hasSameCandidateSelection(current: string[], next: string[]): boolean {
|
||||
if (current.length !== next.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentSorted = [...current].sort();
|
||||
const nextSorted = [...next].sort();
|
||||
return currentSorted.every((candidateId, index) => candidateId === nextSorted[index]);
|
||||
}
|
||||
|
||||
function createPlatformRunMetricsRecord(
|
||||
taskId: string,
|
||||
timestamp: string
|
||||
@ -452,9 +462,16 @@ export class InMemoryTaskStore {
|
||||
const selectedCandidateIds = selectionMap.get(run.platform) ?? [];
|
||||
|
||||
if (selectedCandidateIds.length > 0) {
|
||||
run.status = "Selected";
|
||||
const selectionChanged = !hasSameCandidateSelection(
|
||||
run.selectedCandidateIds,
|
||||
selectedCandidateIds
|
||||
);
|
||||
run.selectedCandidateIds = selectedCandidateIds;
|
||||
run.lastUpdatedAt = nowIso();
|
||||
|
||||
if (run.status !== "Completed" || selectionChanged) {
|
||||
run.status = "Selected";
|
||||
run.lastUpdatedAt = nowIso();
|
||||
}
|
||||
} else if (run.status === "AwaitingSelection") {
|
||||
run.status = "Skipped";
|
||||
run.selectedCandidateIds = [];
|
||||
@ -466,16 +483,25 @@ export class InMemoryTaskStore {
|
||||
task.updatedAt = nowIso();
|
||||
this.pushEvent(task, "task.confirmed", "候选确认已提交。");
|
||||
|
||||
const selectedRuns = task.platformRuns.filter(
|
||||
const confirmedRuns = task.platformRuns.filter(
|
||||
(run) => run.selectedCandidateIds.length > 0
|
||||
);
|
||||
|
||||
if (selectedRuns.length === 0) {
|
||||
if (confirmedRuns.length === 0) {
|
||||
task.taskStatus = "NoSelection";
|
||||
this.pushEvent(task, "task.no_selection", "用户未确认任何商品链接,任务结束。");
|
||||
return task;
|
||||
}
|
||||
|
||||
const selectedRuns = task.platformRuns.filter((run) => run.status === "Selected");
|
||||
|
||||
if (selectedRuns.length === 0) {
|
||||
task.taskStatus = deriveTaskStatusFromConfirmedPlatforms(task.platformRuns);
|
||||
task.updatedAt = nowIso();
|
||||
this.publishReportIfNeeded(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
task.taskStatus = "Running";
|
||||
task.taskStage = "session_check";
|
||||
this.pushEvent(task, "task.running", "系统开始执行抓取前校验。");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user