import { test } from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { parseBobochicProduct, extractBobochicImages, BobochicParseError, parseUrlFragmentAttrs } from "../src/import/bobochic.js"; const fixtureHtml = fs.readFileSync( path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures", "bobochic-product.html"), "utf8", ); const PRODUCT_URL = "https://bobochicparis.com/fr/canapes-d-angle-convertibles/18302-canape-d-angle-convertible-fuji-tissu-lisse.html#/7-couleur-beige/244-type_d_angle-angle_droit"; const fixture3AttrHtml = fs.readFileSync( path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures", "bobochic-product-3attr.html"), "utf8", ); const PRODUCT_3ATTR_URL_NO_FRAGMENT = "https://bobochicparis.com/fr/canapes-d-angle-convertibles/18789-canape-d-angle-l-convertible-coffre-everest-tissu-chine.html"; const PRODUCT_3ATTR_URL_WITH_FRAGMENT = `${PRODUCT_3ATTR_URL_NO_FRAGMENT}#/8-couleur-blanc/244-type_d_angle-angle_droit/864-lot-5_coussins`; const fixtureColorHtml = fs.readFileSync( path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures", "bobochic-sofa-color.html"), "utf8", ); const SOFA_URL_NO_FRAGMENT = "https://bobochicparis.com/fr/canapes-droits-convertibles/17001-canape-droit-convertible-coffre-arsene-tissu-velours.html"; test("bobochic parser: wymiary, cechy, warianty i kolekcja z realnego snapshotu", () => { const p = parseBobochicProduct(fixtureHtml, PRODUCT_URL); assert.equal(p.collection, "FUJI"); assert.equal(p.productRef, "18302"); assert.match(p.name, /FUJI/); const par = p.params; assert.equal(par.widthCm, 292); assert.equal(par.depthCm, 160); assert.equal(par.heightCm, 88); assert.equal(par.heightWithoutCushionsCm, 79); assert.equal(par.sleepWidthCm, 154); assert.equal(par.sleepLengthCm, 230); assert.equal(par.seatHeightCm, 44); assert.equal(par.seatDepthCm, 0); // klucz bez "avec coussins" nie istnieje w cechach assert.equal(par.meridianSeatDepthWithCushionsCm, 113); assert.equal(par.martindale, 45000); assert.equal(par.antiBouloches, true); assert.equal(par.deperlant, false); assert.equal(par.places, 3); assert.equal(par.feetCount, 14); assert.equal(par.coffre, true); assert.equal(par.convertibleOui, true); assert.equal(par.reversible, false); assert.equal(p.packages.length, 2); assert.deepEqual(p.packages[0], { index: 1, lengthCm: 167, widthCm: 66, heightCm: 89, weightKg: 58 }); assert.equal(p.gtin, "5905490067180"); assert.equal(p.priceEur, 1699); assert.equal(p.color, "Beige"); assert.deepEqual(p.sides, ["P", "L"].sort()); }); test("bobochic parser: fragment URL wskazuje wariant; nieznany fragment = guessed", () => { const p = parseBobochicProduct(fixtureHtml, PRODUCT_URL); assert.equal(p.resolvedVariant?.gtin, "5905490067180"); assert.equal(p.resolvedVariant?.typeAngle, "Angle droit"); assert.equal(p.resolvedVariant?.guessed, undefined); const other = parseBobochicProduct(fixtureHtml, PRODUCT_URL.split("#")[0]! + "#/9-couleur-gris/245-type_d_angle-angle_gauche"); assert.equal(other.resolvedVariant?.guessed, true); assert.equal(other.resolvedVariant?.gtin, "5905490067180"); assert.deepEqual(parseUrlFragmentAttrs(PRODUCT_URL), { couleur: "beige", type_d_angle: "angle_droit" }); }); test("bobochic parser: brak danych produktu i kategoria nietapicerowana to błąd, nie fallback", () => { assert.throws( () => parseBobochicProduct("sklep, ale bez produktu", PRODUCT_URL), (e: unknown) => e instanceof BobochicParseError && e.code === "no_product_data", ); assert.throws( () => parseBobochicProduct(fixtureHtml, "https://bobochicparis.com/fr/tapis/999-tapis-deco.html"), (e: unknown) => e instanceof BobochicParseError && e.code === "unsupported_category", ); assert.throws(() => parseBobochicProduct("", PRODUCT_URL), BobochicParseError); }); test("bobochic galeria: zdjęcia rozstrzygniętego wariantu — oryginał CDN + fallback thickbox", () => { const p = parseBobochicProduct(fixtureHtml, PRODUCT_URL); const g = extractBobochicImages(fixtureHtml, p); assert.equal(g.scope, "combination"); assert.equal(g.combinationId, "47876"); assert.deepEqual(g.images.map((i) => i.imageId), ["287998", "287999", "288000"]); assert.deepEqual(g.images.map((i) => i.position), [1, 2, 3]); assert.equal(g.images[0]!.url, "https://cdn.bobochicparis.com/287998/canape-d-angle-convertible-fuji-tissu-lisse.jpg"); assert.equal(g.images[0]!.fallbackUrl, "https://cdn.bobochicparis.com/287998-thickbox_default/canape-d-angle-convertible-fuji-tissu-lisse.jpg"); // inny wariant (angle_gauche) → inna kombinacja i inna galeria const p2 = parseBobochicProduct(fixtureHtml, PRODUCT_URL.split("#")[0]! + "#/7-couleur-beige/245-type_d_angle-angle_gauche"); const g2 = extractBobochicImages(fixtureHtml, p2); assert.equal(g2.combinationId, "47877"); assert.deepEqual(g2.images.map((i) => i.imageId), ["288020", "288021"]); }); test("bobochic galeria: brak mapy kombinacji → fallback na okładki strony", () => { const p = parseBobochicProduct(fixtureHtml, PRODUCT_URL); const noJs = fixtureHtml.replace(/