Merge pull request 'dev' (#115) from dev into main
Reviewed-on: #115
This commit was merged in pull request #115.
This commit is contained in:
@@ -207,10 +207,92 @@ describe("CategoriesService", () => {
|
|||||||
expect(service.getChildren).toHaveBeenCalledWith("c1");
|
expect(service.getChildren).toHaveBeenCalledWith("c1");
|
||||||
expect(partsCatalogsService.fetchParts).not.toHaveBeenCalled();
|
expect(partsCatalogsService.fetchParts).not.toHaveBeenCalled();
|
||||||
expect(result.parts).toEqual([]);
|
expect(result.parts).toEqual([]);
|
||||||
expect(result.children).toEqual(discovered);
|
expect((result as { children?: unknown }).children).toEqual(discovered);
|
||||||
// The wrapper attaches the server-resolved ancestor trail (id+name only).
|
// The wrapper attaches the server-resolved ancestor trail (id+name only).
|
||||||
expect(result.ancestors).toEqual([{ id: "root", name: "Kök" }]);
|
expect(result.ancestors).toEqual([{ id: "root", name: "Kök" }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression guard for the PSA "0 parça" bug: VIN-decoded Peugeot/Citroën
|
||||||
|
// scope categories carry a json-vin-main-groups.action linkPath (and the
|
||||||
|
// next level json-vin-illustrations.action). These are PARENTS — they must
|
||||||
|
// route to getChildren, not fall through to the leaf path and render empty.
|
||||||
|
// The dispatch must stay in sync with PL24Service.isPsaVin*Path; this test
|
||||||
|
// fails loudly if it drifts again.
|
||||||
|
it("routes a PSA VIN scope (json-vin-main-groups.action) to children, not an empty leaf", async () => {
|
||||||
|
const category = {
|
||||||
|
id: "psa1",
|
||||||
|
name: "mekanik",
|
||||||
|
nameOriginal: "mekanik",
|
||||||
|
parentId: null,
|
||||||
|
vehicleId: "v1",
|
||||||
|
source: "pl24",
|
||||||
|
linkPath: "/psa/peugeot_parts/json-vin-main-groups.action?scope=_FCT0001&vin=VF3ABCDE",
|
||||||
|
linkWid: null,
|
||||||
|
hasSubgroups: null,
|
||||||
|
hasParts: null,
|
||||||
|
};
|
||||||
|
let selectCall = 0;
|
||||||
|
const db = {
|
||||||
|
select: vi.fn().mockImplementation(() => {
|
||||||
|
selectCall++;
|
||||||
|
const captured = selectCall;
|
||||||
|
const c: Record<string, any> = {};
|
||||||
|
c.from = vi.fn().mockReturnValue(c);
|
||||||
|
c.where = vi.fn().mockImplementation(() => (captured === 2 ? [] : c));
|
||||||
|
c.limit = vi.fn().mockReturnValue([category]);
|
||||||
|
return c;
|
||||||
|
}),
|
||||||
|
execute: vi.fn().mockResolvedValue([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { service } = createService(db);
|
||||||
|
const discovered = [{ id: "mg1", name: "motor", children: undefined }];
|
||||||
|
vi.spyOn(service, "getChildren").mockResolvedValue(discovered as any);
|
||||||
|
|
||||||
|
const result = await service.getCategoryWithParts("psa1");
|
||||||
|
|
||||||
|
expect(service.getChildren).toHaveBeenCalledWith("psa1");
|
||||||
|
expect(result.parts).toEqual([]);
|
||||||
|
expect((result as { children?: unknown }).children).toEqual(discovered);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("surfaces loadError (not an empty grid) when a PSA VIN main-group drill comes back empty", async () => {
|
||||||
|
const category = {
|
||||||
|
id: "psa2",
|
||||||
|
name: "motor",
|
||||||
|
nameOriginal: "motor",
|
||||||
|
parentId: "psa1",
|
||||||
|
vehicleId: "v1",
|
||||||
|
source: "pl24",
|
||||||
|
linkPath: "/psa/peugeot_parts/json-vin-illustrations.action?mainGroup=P8&vin=VF3ABCDE",
|
||||||
|
linkWid: null,
|
||||||
|
hasSubgroups: null,
|
||||||
|
hasParts: null,
|
||||||
|
};
|
||||||
|
let selectCall = 0;
|
||||||
|
const db = {
|
||||||
|
select: vi.fn().mockImplementation(() => {
|
||||||
|
selectCall++;
|
||||||
|
const captured = selectCall;
|
||||||
|
const c: Record<string, any> = {};
|
||||||
|
c.from = vi.fn().mockReturnValue(c);
|
||||||
|
c.where = vi.fn().mockImplementation(() => (captured === 2 ? [] : c));
|
||||||
|
c.limit = vi.fn().mockReturnValue([category]);
|
||||||
|
return c;
|
||||||
|
}),
|
||||||
|
execute: vi.fn().mockResolvedValue([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { service } = createService(db);
|
||||||
|
// Drill comes back empty (transient upstream failure).
|
||||||
|
vi.spyOn(service, "getChildren").mockResolvedValue([] as any);
|
||||||
|
|
||||||
|
const result = await service.getCategoryWithParts("psa2");
|
||||||
|
|
||||||
|
expect(service.getChildren).toHaveBeenCalledWith("psa2");
|
||||||
|
expect((result as { loadError?: boolean }).loadError).toBe(true);
|
||||||
|
expect((result as { children?: unknown }).children).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getAncestors", () => {
|
describe("getAncestors", () => {
|
||||||
|
|||||||
@@ -918,15 +918,29 @@ export class CategoriesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PSA parent nodes — always fetch children on-demand:
|
// PSA parent nodes — always fetch children on-demand:
|
||||||
// • psa:: scope paths (top-level scopes)
|
// • psa:: scope paths (catalog-browse top-level scopes)
|
||||||
// • json-illustrations.action paths (mid-level main groups → illustration lists)
|
// • json-illustrations.action (catalog-browse main group → illustrations)
|
||||||
|
// • json-vin-main-groups.action (VIN-indexed scope → main groups)
|
||||||
|
// • json-vin-illustrations.action (VIN-indexed main group → illustrations)
|
||||||
|
// The two VIN-indexed levels were MISSING here, so VIN-decoded Peugeot/Citroën
|
||||||
|
// scope categories ("mekanik", "kaporta", "ekipmanlar"…) fell through every
|
||||||
|
// parent branch to the leaf path, fetched no parts (the main-groups endpoint
|
||||||
|
// returns groups, not parts) and rendered an empty panel — the single biggest
|
||||||
|
// "0 parça" complaint. The drill itself works: getChildren →
|
||||||
|
// fetchSubGroupsByPath → fetchVinMainGroups / fetchVinIllustrations. These four
|
||||||
|
// predicates must stay in sync with PL24Service.isPsaPath / isPsaIllusPath /
|
||||||
|
// isPsaVinMainGroupsPath / isPsaVinIllusPath; the leaf level
|
||||||
|
// (vin-image-board.action / image-board.action) correctly stays a leaf below.
|
||||||
|
const psaLink = category.linkPath ?? "";
|
||||||
const isPsaParent =
|
const isPsaParent =
|
||||||
category.linkPath?.startsWith("psa::") ||
|
psaLink.startsWith("psa::") ||
|
||||||
(category.linkPath?.includes("/psa/") &&
|
(psaLink.includes("/psa/") &&
|
||||||
category.linkPath?.includes("json-illustrations.action"));
|
(psaLink.includes("json-illustrations.action") ||
|
||||||
|
psaLink.includes("json-vin-main-groups.action") ||
|
||||||
|
psaLink.includes("json-vin-illustrations.action")));
|
||||||
if (isPsaParent && category.vehicleId) {
|
if (isPsaParent && category.vehicleId) {
|
||||||
const psaChildren = await this.getChildren(categoryId);
|
const psaChildren = await this.getChildren(categoryId);
|
||||||
return {
|
const base = {
|
||||||
id: category.id,
|
id: category.id,
|
||||||
name: category.name,
|
name: category.name,
|
||||||
description: category.nameOriginal || null,
|
description: category.nameOriginal || null,
|
||||||
@@ -934,8 +948,14 @@ export class CategoriesService {
|
|||||||
parts: [],
|
parts: [],
|
||||||
schemaPics: [],
|
schemaPics: [],
|
||||||
hotspots: [],
|
hotspots: [],
|
||||||
children: psaChildren,
|
|
||||||
};
|
};
|
||||||
|
// A PSA scope / main group is never a parts leaf. If the on-demand drill
|
||||||
|
// comes back empty the upstream snapshot failed transiently — surface a
|
||||||
|
// retryable load error (matching the pl24/emex group-node branches below)
|
||||||
|
// instead of a misleading empty subcategory grid.
|
||||||
|
return psaChildren.length > 0
|
||||||
|
? { ...base, children: psaChildren }
|
||||||
|
: { ...base, loadError: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// parts-catalogs parent group — the groups2 API marked this node as having
|
// parts-catalogs parent group — the groups2 API marked this node as having
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ async function bootstrap() {
|
|||||||
],
|
],
|
||||||
objectSrc: ["'none'"],
|
objectSrc: ["'none'"],
|
||||||
frameSrc: ["https://challenges.cloudflare.com", "https://destek.sase.tr"],
|
frameSrc: ["https://challenges.cloudflare.com", "https://destek.sase.tr"],
|
||||||
|
// The Meta Pixel (fbevents 2.9.x) dispatches ALL events from a Web Worker
|
||||||
|
// created off a blob: URL. With no worker-src, the browser falls back to
|
||||||
|
// script-src — which lacks blob: — so the worker is CSP-blocked and NO
|
||||||
|
// pixel events fire (PageView, CompleteRegistration, everything were 0
|
||||||
|
// until this). Also unblocks Remotion's blob worker on the demos.
|
||||||
|
workerSrc: ["'self'", "blob:"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user