Fix: don't mask DataForSEO 40501 validation errors as empty results (#346)
* Fix: don't mask DataForSEO 40501 validation errors as empty results
isNoResultsTask classified any task with status_code 40501 as a
successful empty result, but 40501 is not unique to "No Search
Results" — DataForSEO also returns it for validation rejections
like "Invalid Field: 'target'.".
After the Business Listings and Q&A endpoints opted into
treatNoResultsAsEmpty, invalid-field 40501 responses (reachable via
the MCP search_local_businesses categories input, which accepts
arbitrary strings) were silently masked as empty successes — billed
and tracked, but hiding the real provider validation error.
Match on the status message ("no search results") instead of the
ambiguous code so charged validation failures flow through to
DataforseoChargedTaskError with the diagnostic message, while genuine
no-results responses still return as empty successes. Fixes all call
sites uniformly (Business, SERP live, SERP task_get polling).
* Format isNoResultsTask per prettier
This commit is contained in:
parent
9b6c5640cd
commit
e39de22f96
@ -148,4 +148,29 @@ describe("assertOk", () => {
|
|||||||
),
|
),
|
||||||
).toBe(task);
|
).toBe(task);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("still surfaces a charged 40501 'Invalid Field' failure even with treatNoResultsAsEmpty", () => {
|
||||||
|
// 40501 is not unique to no-results — it also covers validation rejections,
|
||||||
|
// which are real charged failures we must not mask as empty results.
|
||||||
|
const task = {
|
||||||
|
status_code: 40501,
|
||||||
|
status_message: "Invalid Field: 'categories'.",
|
||||||
|
path: ["v3", "business_data", "business_listings", "search", "live"],
|
||||||
|
cost: 0.02,
|
||||||
|
result_count: 0,
|
||||||
|
data: { categories: ["not_a_real_category"] },
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
assertOk(
|
||||||
|
{ status_code: 20000, tasks: [task] },
|
||||||
|
{ treatNoResultsAsEmpty: true },
|
||||||
|
);
|
||||||
|
throw new Error("expected assertOk to throw");
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(DataforseoChargedTaskError);
|
||||||
|
if (error instanceof DataforseoChargedTaskError) {
|
||||||
|
expect(error.billing).toEqual({ path: task.path, costUsd: 0.02 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -102,11 +102,15 @@ function describeInvalidField(
|
|||||||
return `${message} (sent ${field}=${JSON.stringify(value)})`;
|
return `${message} (sent ${field}=${JSON.stringify(value)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DataForSEO's "No Search Results" (40501) — a successful empty result, not a failure. */
|
/**
|
||||||
|
* DataForSEO's "No Search Results" (40501) — a successful empty result, not a
|
||||||
|
* failure. Match on the status message, not the code alone: 40501 also covers
|
||||||
|
* validation rejections like "Invalid Field: 'target'.", which are real charged
|
||||||
|
* failures we must surface rather than mask as empty results.
|
||||||
|
*/
|
||||||
export function isNoResultsTask(task: DataforseoTaskLike): boolean {
|
export function isNoResultsTask(task: DataforseoTaskLike): boolean {
|
||||||
return (
|
return (
|
||||||
task.status_code === 40501 ||
|
task.status_message?.toLowerCase().includes("no search results") ?? false
|
||||||
(task.status_message?.toLowerCase().includes("no search results") ?? false)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user