Merge pull request 'fix(carcatonline): groups2 404 = boş dal, tarama devam eder' (#275) from dev into main

This commit was merged in pull request #275.
This commit is contained in:
2026-09-26 21:06:42 +03:00
3 changed files with 50 additions and 2 deletions

View File

@@ -117,6 +117,37 @@ describe("crawlGroupTree", () => {
expect(nodes.find((n) => n.id === "g2")?.illustration).toBe("47-010");
});
it("treats a 404 'No groups found' subgroup fetch as an empty branch and keeps crawling", async () => {
const client = {
carsParameters: vi.fn(),
cars: vi.fn(),
groups: vi.fn(async (_c: string, _car: string, groupId?: string) => {
if (!groupId) {
return [
{ id: "dead", name: "Dead", hasSubgroups: true, hasParts: false },
{ id: "ok", name: "Ok", hasSubgroups: true, hasParts: false },
] as CarcatGroup[];
}
if (groupId === "dead") throw Object.assign(new Error("No groups found"), { status: 404 });
return [{ id: "ok1", name: "Leaf", hasSubgroups: false, hasParts: true }] as CarcatGroup[];
}),
};
const cache = new Map<string, CarcatGroup[]>();
const cachingHooks = {
beforeCall: vi.fn().mockResolvedValue(undefined),
cache: {
get: async (k: string) => cache.get(k) ?? null,
set: async (k: string, g: CarcatGroup[]) => {
cache.set(k, g);
},
},
};
const { nodes, calls } = await crawlGroupTree(client, cachingHooks, "pl_skoda", "car");
expect(calls).toBe(3);
expect(nodes.map((n) => n.id)).toEqual(["dead", "ok", "ok1"]);
expect(cache.get("pl_skoda:car:dead")).toEqual([]);
});
it("aborts when the window closes mid-crawl", async () => {
const client = {
carsParameters: vi.fn(),

View File

@@ -141,7 +141,15 @@ export async function crawlGroupTree(
let groups = (await hooks.cache?.get(cacheKey)) ?? null;
if (!groups) {
await hooks.beforeCall();
groups = await client.groups(catalogId, carId, groupId ?? undefined);
try {
groups = await client.groups(catalogId, carId, groupId ?? undefined);
} catch (err) {
// A node flagged hasSubgroups can still answer 404 "No groups found"
// (seen on pl_skoda). That is a dead branch, not a failure: record it
// as empty (and cache it) so the rest of the tree still lands.
if ((err as { status?: number }).status !== 404) throw err;
groups = [];
}
calls += 1;
await hooks.cache?.set(cacheKey, groups);
}

View File

@@ -234,6 +234,8 @@ async function fillVehicle(
.where(eq(catalogVehicles.id, catalogVehicleId));
};
/** What we already resolved, kept on a failure record so a retry can resume. */
let progress: Partial<CarcatMetadata> = {};
try {
// 1. brand → catalog candidates → model
if (isNonVehicleModel(cv.model)) {
@@ -287,8 +289,10 @@ async function fillVehicle(
parameters: (prev.parameters ?? []).map((p) => ({ ...p, name: p.key })),
}
: null;
progress = { ...match };
const car =
resumable ?? (await resolveRepresentativeCar(client, hooks, match.catalogId, match.modelId));
if (car) progress = { ...progress, carId: car.carId };
if (car && !resumable) {
await setMeta({
status: "matched",
@@ -359,7 +363,12 @@ async function fillVehicle(
if (err instanceof CarcatonlineWindowClosedError)
await reschedule(err.retryAfterMs + 60_000, "window closed mid-crawl");
const message = err instanceof Error ? err.message : String(err);
await setMeta({ status: "failed", error: message.slice(0, 300), at: now().toISOString() });
await setMeta({
status: "failed",
...progress,
error: message.slice(0, 300),
at: now().toISOString(),
});
log.warn(`[carcatonline] ${catalogVehicleId} failed: ${message}`);
throw err;
}