import { test } from "node:test"; import assert from "node:assert/strict"; import { openDb } from "../src/db.js"; import { createProduct } from "../src/domain/product.js"; import { assignShotRef, unassignShotRef, listShotRefs, resolveShotRefs, originKindForSources, ShotRefError, } from "../src/domain/shotRefs.js"; import { newId, ID_PREFIX } from "../src/domain/ids.js"; import type { Db } from "../src/db.js"; function freshDb() { return openDb(":memory:"); } /** Źródło wprost w DB — testy przypisań nie potrzebują prawdziwego pliku. */ function insertSource( db: Db, productId: string, role: string, opts: { sourceUrl?: string } = {} ): string { const id = newId(ID_PREFIX.source); db.prepare( `INSERT INTO source_assets (id, product_id, role, sha256, mime, bytes, original_name, path, source_url, fetched_at, created_at) VALUES (?, ?, ?, ?, 'image/jpeg', 1, ?, ?, ?, ?, ?)` ).run( id, productId, role, `sha-${id}`, `${id}.jpg`, `sources/${id}.jpg`, opts.sourceUrl ?? null, opts.sourceUrl ? "2026-01-01T00:00:00.000Z" : null, new Date().toISOString() ); return id; } test("shot_refs: przypisanie primary + listing + resolve", () => { const db = freshDb(); const p = createProduct(db, { slug: "x", name: "X", kind: "corner-sofa" }); const s1 = insertSource(db, p.id, "product_reference"); const s2 = insertSource(db, p.id, "product_reference"); const r1 = assignShotRef(db, { productId: p.id, shotRole: "packshot_34", sourceId: s1, refKind: "primary", actor: "t" }); assert.equal(r1.ref_kind, "primary"); assignShotRef(db, { productId: p.id, shotRole: "packshot_34", sourceId: s2, refKind: "supporting", actor: "t" }); const list = listShotRefs(db, p.id); assert.equal(list.length, 2); const res = resolveShotRefs(db, p.id, "packshot_34"); assert.equal(res.primaryId, s1); assert.deepEqual(res.sourceIds, [s1, s2]); // primary pierwsze, potem supporting assert.equal(res.refKinds[s2], "supporting"); db.close(); }); test("shot_refs: nowe primary degraduje stare do supporting — dokładnie jedno primary", () => { const db = freshDb(); const p = createProduct(db, { slug: "x", name: "X", kind: "corner-sofa" }); const s1 = insertSource(db, p.id, "product_reference"); const s2 = insertSource(db, p.id, "product_reference"); assignShotRef(db, { productId: p.id, shotRole: "side", sourceId: s1, refKind: "primary", actor: "t" }); assignShotRef(db, { productId: p.id, shotRole: "side", sourceId: s2, refKind: "primary", actor: "t" }); const res = resolveShotRefs(db, p.id, "side"); assert.equal(res.primaryId, s2); assert.deepEqual(res.sourceIds.sort(), [s1, s2].sort()); const primaries = listShotRefs(db, p.id).filter((r) => r.ref_kind === "primary"); assert.equal(primaries.length, 1); // surowe naruszenie indeksu też jest niemożliwe assert.throws(() => db.prepare("UPDATE shot_refs SET ref_kind = 'primary' WHERE source_id = ?").run(s1) ); // przypisanie supporting na istniejącym primary jest no-op const noop = assignShotRef(db, { productId: p.id, shotRole: "side", sourceId: s2, refKind: "supporting", actor: "t" }); assert.equal(noop.ref_kind, "primary"); db.close(); }); test("shot_refs: walidacja — obcy produkt i niedozwolona rola źródła", () => { const db = freshDb(); const p = createProduct(db, { slug: "x", name: "X", kind: "corner-sofa" }); const q = createProduct(db, { slug: "y", name: "Y", kind: "corner-sofa" }); const alien = insertSource(db, q.id, "product_reference"); const style = insertSource(db, p.id, "style_reference"); const detached = insertSource(db, p.id, "product_reference"); db.prepare("UPDATE source_assets SET product_id = NULL WHERE id = ?").run(detached); assert.throws( () => assignShotRef(db, { productId: p.id, shotRole: "hero", sourceId: alien, refKind: "primary", actor: "t" }), ShotRefError ); assert.throws( () => assignShotRef(db, { productId: p.id, shotRole: "hero", sourceId: style, refKind: "primary", actor: "t" }), ShotRefError ); assert.throws( () => assignShotRef(db, { productId: p.id, shotRole: "hero", sourceId: detached, refKind: "primary", actor: "t" }), ShotRefError ); assert.throws( () => assignShotRef(db, { productId: p.id, shotRole: "hero", sourceId: "src_brak", refKind: "primary", actor: "t" }), ShotRefError ); db.close(); }); test("shot_refs: vendor_reference jest referencją, resolve i odpięcie działają", () => { const db = freshDb(); const p = createProduct(db, { slug: "x", name: "X", kind: "corner-sofa" }); const v = insertSource(db, p.id, "vendor_reference", { sourceUrl: "https://bobochicparis.com/pl/x" }); const r = assignShotRef(db, { productId: p.id, shotRole: "sleep", sourceId: v, refKind: "primary", actor: "t" }); assert.equal(r.ref_kind, "primary"); assert.equal(resolveShotRefs(db, p.id, "sleep").primaryId, v); unassignShotRef(db, { id: r.id, productId: p.id, actor: "t" }); assert.equal(resolveShotRefs(db, p.id, "sleep").primaryId, null); assert.throws( () => unassignShotRef(db, { id: r.id, productId: p.id, actor: "t" }), ShotRefError ); db.close(); }); test("shot_refs: originKind — hall | vendor | mixed | unknown", () => { const db = freshDb(); const p = createProduct(db, { slug: "x", name: "X", kind: "corner-sofa" }); const hall = insertSource(db, p.id, "product_reference"); const vendor = insertSource(db, p.id, "vendor_reference", { sourceUrl: "https://bobochicparis.com/x" }); assert.equal(originKindForSources(db, [hall]), "hall"); assert.equal(originKindForSources(db, [vendor]), "vendor"); assert.equal(originKindForSources(db, [hall, vendor]), "mixed"); assert.equal(originKindForSources(db, []), "unknown"); db.close(); });