Root cause: ffmpeg is absent in the prod worker, so vinpin.ocr cropScale silently returns the full 1600x900 frame — the tiny centered Rpartstore launch-error modal is illegible, so a DOWN Rpartstore is misread as a spinner and thrashes the seat. - Dockerfile: install ffmpeg so cropScale actually crops+3x-upscales (restores all clipped OCR: modal-crop error detect, Fiat modal, Renault header). - ensureRpartstore: gate the "already open" short-circuit on a CONTENT token (rpartstoreLoaded), not rpartstoreOpen which false-matches the flyout/title word "Rpartstore"; click the flyout entry when the submenu is up over the grid; keep a late open-window branch so a spinner/vehicle-page window still counts as present. - closeRpartstoreTab: never click windowClose(1298,14) (it hits the language selector and wedges the grid); gate the retry on a content token; Escape after. - cleanTeardown: dismiss any blocking modal BEFORE "Çıkış yap" so logout is a clean RDS log-off (not a dirty channel disconnect); recover a Disconnected dialog via disconnectedClose(953,505) + relaunch VINPIN app for a fresh grid. - Rpartstore-down cooldown (10m): a launch-error / repeated load-failure routes Renault decodes straight to Dialogys (skip reopening a down catalog); a confirmed load clears it. - ensureBrandGrid: dismiss a wedging launch-error modal + short-retry instead of burning the 84s dead-wait. - parseRenaultHeader: ignore the status-bar license-expiry date when reading the model year; sessionAlive matches RDST01/RDST02 (seat load-balances). - Keeps never-throw, VINPIN_DECODE_BUDGET_MS, sessionPoisoned, the acquire cap, spinner-guard, relogin-cap and the Fiat/Dialogys fallbacks intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
4.7 KiB
TypeScript
111 lines
4.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
isUsableParse,
|
||
isUsableRenaultParse,
|
||
parseRenaultHeader,
|
||
parseVinpinModal,
|
||
} from "./vinpin.parser";
|
||
|
||
const SAMPLE =
|
||
"MVS, найденные по vin NM435600006H43436 6J - TIPO - EGEA (2015-2021) 356G37001162 - 3V HIGH PLUS 1.6 120CV D5 CM E6 GS TR Двигатель: 8165300 Автомобиль: 279476 Prod. date: 2/8/2017 Vin: NM435600006H43436";
|
||
|
||
describe("parseVinpinModal", () => {
|
||
it("parses the real ePER modal sample (TIPO-EGEA / 356G37001162 / 2017)", () => {
|
||
const p = parseVinpinModal(SAMPLE);
|
||
expect(p.model).toBe("TIPO-EGEA");
|
||
expect(p.sincom).toBe("356G37001162");
|
||
expect(p.modelYear).toBe("2017"); // from Prod. date, not the (2015-2021) range
|
||
expect(p.engine).toBe("8165300");
|
||
expect(p.trim).toContain("HIGH PLUS");
|
||
expect(isUsableParse(p)).toBe(true);
|
||
});
|
||
|
||
it("does not mistake the VIN or short engine number for a SINCOM code", () => {
|
||
const p = parseVinpinModal(SAMPLE);
|
||
expect(p.sincom).not.toContain("NM4356");
|
||
expect(p.sincom).not.toBe("8165300");
|
||
});
|
||
|
||
it("falls back to the year-range start when no production date is present", () => {
|
||
const p = parseVinpinModal("9F - LINEA (2007-2017) 323A11005000 - 1.3 MJET");
|
||
expect(p.model).toBe("LINEA");
|
||
expect(p.sincom).toBe("323A11005000");
|
||
expect(p.modelYear).toBe("2007");
|
||
});
|
||
|
||
it("returns all-null for empty/garbage input and reports unusable", () => {
|
||
const p = parseVinpinModal("");
|
||
expect(p.model).toBeNull();
|
||
expect(p.sincom).toBeNull();
|
||
expect(isUsableParse(p)).toBe(false);
|
||
expect(isUsableParse(parseVinpinModal(null))).toBe(false);
|
||
});
|
||
|
||
it("rejects a stale-breadcrumb model with no SINCOM (vehicle-not-found false positive)", () => {
|
||
// Real garbled OCR of a failed NM4131 decode: the "не найден" alert is
|
||
// mangled ("He HaaeHs") so it slips the not-found check, and the screen
|
||
// still shows the operator's last catalog ("FIAT » TIPO - EGEA"). A model
|
||
// token is present but there is NO SINCOM → must be treated as unusable so
|
||
// it is not false-mapped onto the ancient "TIPO 1987-1993" catalog.
|
||
const p = parseVinpinModal(
|
||
"Fiat Dealer Spare Parts FIAT TIPO - EGBA 500 HYBRID TIPO-EGEA MCA (2020) DOBLO FREEMONT LINEA IDEA To yKasaHHeIM N3paMETpaM 3BTOMOBHMN He HaaeHs",
|
||
);
|
||
expect(p.model).not.toBeNull(); // breadcrumb model still extracted…
|
||
expect(p.sincom).toBeNull(); // …but no SINCOM
|
||
expect(isUsableParse(p)).toBe(false); // → not a real decode
|
||
});
|
||
});
|
||
|
||
describe("parseRenaultHeader", () => {
|
||
it("parses an Rpartstore RENAULT header (model + brand)", () => {
|
||
const p = parseRenaultHeader("RENAULT KADJAR (RFE) Şasi: VF1RFE00653633190");
|
||
expect(p.model).toBe("KADJAR");
|
||
expect(p.brand).toBe("RENAULT");
|
||
expect(isUsableRenaultParse(p)).toBe(true);
|
||
});
|
||
|
||
it("reads DACIA as the brand when the header carries it", () => {
|
||
const p = parseRenaultHeader("DACIA DUSTER (HSA) Şasi: VF1HJD40865644941");
|
||
expect(p.model).toBe("DUSTER");
|
||
expect(p.brand).toBe("DACIA");
|
||
});
|
||
|
||
it("extracts the base model and ignores the generation numeral (CLIO IV)", () => {
|
||
const p = parseRenaultHeader("RENAULT CLIO IV (R0G) Şasi: VF15R0G0H49335605");
|
||
expect(p.model).toBe("CLIO");
|
||
expect(p.brand).toBe("RENAULT");
|
||
});
|
||
|
||
it("handles a Dialogys header with no explicit brand (defaults RENAULT)", () => {
|
||
const p = parseRenaultHeader("Clio II (X65), 1.5 dCi");
|
||
expect(p.model).toBe("CLIO");
|
||
expect(p.brand).toBe("RENAULT");
|
||
});
|
||
|
||
it("captures a model year when the header carries one", () => {
|
||
const p = parseRenaultHeader("RENAULT LATITUDE (2011-2015)");
|
||
expect(p.model).toBe("LATITUDE");
|
||
expect(p.modelYear).toBe("2011");
|
||
});
|
||
|
||
it("does NOT mis-grab the status-bar license-expiry date as the model year", () => {
|
||
// The full-frame OCR fallback sweeps in the Rpartstore/Dialogys status bar,
|
||
// whose license EXPIRY date ("…14.07.2026") used to be read as the model year.
|
||
const p = parseRenaultHeader("RENAULT KADJAR (RFE) Şasi: VF1RFE00653633190 Lisans: 14.07.2026");
|
||
expect(p.model).toBe("KADJAR");
|
||
expect(p.modelYear).toBeNull(); // NOT "2026" from the expiry date
|
||
});
|
||
|
||
it("still prefers a genuine generation range even when an expiry date is present", () => {
|
||
const p = parseRenaultHeader("RENAULT LATITUDE (2011-2015) Lisans: 14.07.2026");
|
||
expect(p.modelYear).toBe("2011"); // the (2011-2015) range wins over the date
|
||
});
|
||
|
||
it("returns no model / unusable for headers without a known token", () => {
|
||
const p = parseRenaultHeader("İlişikli araç bulunamadı");
|
||
expect(p.model).toBeNull();
|
||
expect(isUsableRenaultParse(p)).toBe(false);
|
||
expect(isUsableRenaultParse(parseRenaultHeader(""))).toBe(false);
|
||
});
|
||
});
|