feat(FN-285): fix EMEX parallel decode race — always await second promise
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Commits merged: - fix(FN-285): fix EMEX parallel decode race — always await second promise Files changed: apps/api/src/vehicles/vehicles.service.spec.ts | 63 ++++++++++++++++++++++++++ apps/api/src/vehicles/vehicles.service.ts | 5 +- 2 files changed, 66 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-285
This commit is contained in:
@@ -375,6 +375,69 @@ describe("VehiclesService", () => {
|
||||
expect(result).toEqual(savedVehicle);
|
||||
expect(vinApiService.decodeVin).toHaveBeenCalledWith("WBAPH5C55BA123456");
|
||||
});
|
||||
|
||||
it("should use EMEX when it returns a vehicle (parallel decode)", async () => {
|
||||
vi.mocked(isValidVin).mockReturnValue(true);
|
||||
|
||||
const emexVehicle = {
|
||||
brand: "BMW",
|
||||
model: "320i",
|
||||
year: 2020,
|
||||
engineCode: "N20",
|
||||
engineType: null,
|
||||
engineVolume: null,
|
||||
transmission: "Auto",
|
||||
bodyType: "Sedan",
|
||||
driveType: null,
|
||||
raw: {},
|
||||
};
|
||||
|
||||
let selectCall = 0;
|
||||
let insertCall = 0;
|
||||
const savedVehicle = { id: "v-emex", vin: "WBAPH5C55BA123456", source: "emex" };
|
||||
const db = {
|
||||
select: vi.fn().mockImplementation(() => {
|
||||
selectCall++;
|
||||
const chain: Record<string, any> = {};
|
||||
chain.from = vi.fn().mockReturnValue(chain);
|
||||
chain.innerJoin = vi.fn().mockReturnValue(chain);
|
||||
chain.where = vi.fn().mockReturnValue(chain);
|
||||
chain.limit = vi.fn().mockImplementation(() => {
|
||||
if (selectCall === 1) return []; // no cache
|
||||
if (selectCall === 2) return [{ id: "b1", name: "BMW" }]; // brand
|
||||
if (selectCall === 3) return [{ brandCount: 0 }]; // subscription (unlimited)
|
||||
return [];
|
||||
});
|
||||
return chain;
|
||||
}),
|
||||
insert: vi.fn().mockImplementation(() => {
|
||||
insertCall++;
|
||||
const insertChain: Record<string, any> = {};
|
||||
insertChain.values = vi.fn().mockReturnValue(insertChain);
|
||||
insertChain.returning = vi.fn().mockImplementation(() => {
|
||||
if (insertCall === 1) return [savedVehicle]; // vehicles upsert
|
||||
return [];
|
||||
});
|
||||
insertChain.onConflictDoNothing = vi.fn().mockReturnValue(insertChain);
|
||||
insertChain.onConflictDoUpdate = vi.fn().mockReturnValue(insertChain);
|
||||
return insertChain;
|
||||
}),
|
||||
};
|
||||
|
||||
const { service, partsCatalogsService, emexService } = createService(db);
|
||||
// PCAT returns 0 cars — not definitive, falls through to EMEX check
|
||||
partsCatalogsService.decodeVin.mockResolvedValue(null);
|
||||
// EMEX returns a single vehicle with a known brand
|
||||
emexService.decodeVinOrCandidates.mockResolvedValue({
|
||||
type: "vehicle",
|
||||
vehicle: emexVehicle,
|
||||
});
|
||||
|
||||
const result = await service.decodeVin("WBAPH5C55BA123456", "u1");
|
||||
expect(result).toEqual(savedVehicle);
|
||||
expect(partsCatalogsService.decodeVin).toHaveBeenCalled();
|
||||
expect(emexService.decodeVinOrCandidates).toHaveBeenCalledWith("WBAPH5C55BA123456");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHistory", () => {
|
||||
|
||||
@@ -558,9 +558,10 @@ export class VehiclesService {
|
||||
|
||||
if (!earlyPcatDefinitive && !earlyEmexDefinitive) {
|
||||
// First completer wasn't definitive — wait for the other one.
|
||||
if (earlyWinner.kind === "pcat" && !emexResolved) {
|
||||
// Always await the other promise (if already resolved, returns immediately).
|
||||
if (earlyWinner.kind === "pcat") {
|
||||
emexResult = await emexTimedPromise;
|
||||
} else if (earlyWinner.kind === "emex" && !pcatResolved) {
|
||||
} else if (earlyWinner.kind === "emex") {
|
||||
pcatResult = await pcatPromise;
|
||||
}
|
||||
if (signal?.aborted) return null;
|
||||
|
||||
Reference in New Issue
Block a user