Files
sase.tr/apps/api/src/integrations/vinpin/vinpin.parser.spec.ts
Semih Yesilyurt 281c54a423 fix(vinpin): Renault-decode robustness for a DOWN Rpartstore (ffmpeg + flyout/close/logout/cooldown)
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>
2026-07-15 08:32:39 +03:00

111 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});