← back to Dw Sku Integrity

test/apply-plan-gen.test.mjs

173 lines

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
  SELF_COPY_CLASSES,
  FORBIDDEN_CLASSES,
  MFR_SELF_COPY_CLASSES,
  sqlEscape,
  selectSelfCopyRows,
  buildPlans,
  renderApplySql,
  renderUndoSql,
  writePlans,
  keyOf,
} from '../apply-plan-gen.mjs';

// Fixture plan: a mix of self-copy (with + without candidate), a rescrape row,
// a mint-residue row, two collision rows (which start with "SELF_COPY"!), and a
// candidate carrying an apostrophe. Vendors chosen to prove per-vendor split.
const PLAN = [
  { vendor: 'Fabricut', sku: 'DWFC-1', class: 'SELF_COPY_DW', candidate: 'DWFC-1' },
  { vendor: 'Fabricut', sku: 'DWFC-2', class: 'SELF_COPY_DW_PROVEN_NATIVE', candidate: 'DWFC-2' },
  { vendor: 'Carnegie', sku: 'DWAG-9', class: 'SELF_COPY_SOURCE', candidate: 'DWAG-9' },
  { vendor: 'Cork Co', sku: 'CORK-3', class: 'SELF_COPY_CORK', candidate: 'Cork-3' }, // valid code-shape
  { vendor: 'Fabricut', sku: 'DWFC-NULL', class: 'SELF_COPY_DW', candidate: null },     // no candidate → skip
  { vendor: 'Fabricut', sku: 'DWFC-R', class: 'MINT_RESIDUE_RESCRAPE', candidate: null },
  { vendor: 'Fabricut', sku: 'DWFC-RR', class: 'RESCRAPE', candidate: 'DWFC-RR' },
  { vendor: 'Fabricut', sku: 'DWFC-C1', class: 'SELF_COPY_COLLISION', candidate: 'DWFC-C1' },
  { vendor: 'Fabricut', sku: 'DWFC-C2', class: 'SELF_COPY_DW_COLLISION', candidate: 'DWFC-C2' },
  { vendor: 'Fabricut', sku: 'DWFC-S', class: 'STAGING_LINK', candidate: 'DWFC-S' },
];

// In-memory blank-key map (mirrors loadBlankKeyMap's shape). DWFC-1 has TWO DB
// rows sharing the sku (both blank) to prove one guarded statement per id.
function fixtureMap() {
  const m = new Map();
  m.set(keyOf('DWFC-1', 'Fabricut'), [
    { id: 101, shopify_id: 'gid/1', handle: 'fab-1-a' },
    { id: 102, shopify_id: 'gid/2', handle: 'fab-1-b' },
  ]);
  m.set(keyOf('DWFC-2', 'Fabricut'), [{ id: 103, shopify_id: 'gid/3', handle: 'fab-2' }]);
  m.set(keyOf('DWAG-9', 'Carnegie'), [{ id: 201, shopify_id: 'gid/4', handle: 'carn-9' }]);
  m.set(keyOf('CORK-3', 'Cork Co'), [{ id: 301, shopify_id: 'gid/5', handle: 'cork-3' }]);
  // DWFC-NULL intentionally present in DB but plan row has no candidate -> still skipped.
  m.set(keyOf('DWFC-NULL', 'Fabricut'), [{ id: 999, shopify_id: 'gid/9', handle: 'null-row' }]);
  return m;
}

test('(a) only self-copy classes WITH a candidate produce plan entries', () => {
  const kept = selectSelfCopyRows(PLAN);
  const keptClasses = kept.map((r) => r.class);
  assert.deepEqual(new Set(keptClasses), SELF_COPY_CLASSES.size === 4
    ? new Set(['SELF_COPY_DW', 'SELF_COPY_DW_PROVEN_NATIVE', 'SELF_COPY_SOURCE', 'SELF_COPY_CORK'])
    : new Set(keptClasses));
  // 4 self-copy rows have candidates (DWFC-1, DWFC-2, DWAG-9, CORK-3); the null one is dropped.
  assert.equal(kept.length, 4);
  assert.ok(!kept.some((r) => r.sku === 'DWFC-NULL'));
});

test('(b) residue / rescrape / collision / staging rows produce NO SQL', () => {
  const { byVendor } = buildPlans(PLAN, fixtureMap());
  const allEntries = [...byVendor.values()].flat();
  const apply = allEntries.map((e) => e.candidate).join('\n') + '\n' +
    renderApplySql(allEntries);
  for (const forbidden of ['DWFC-R', 'DWFC-RR', 'DWFC-C1', 'DWFC-C2', 'DWFC-S']) {
    assert.ok(!apply.includes(forbidden), `forbidden candidate ${forbidden} leaked into apply set`);
  }
  // DWFC-1 → 2 statements (2 ids), DWFC-2/DWAG-9/CORK-3 → 1 each = 5 total.
  assert.equal(allEntries.length, 5);
});

test('(c) every generated UPDATE carries the blank-guard', () => {
  const { byVendor } = buildPlans(PLAN, fixtureMap());
  for (const entries of byVendor.values()) {
    const sql = renderApplySql(entries);
    for (const line of sql.split('\n')) {
      if (!line.startsWith('UPDATE')) continue;
      assert.ok(
        line.includes("(dw_sku IS NULL OR btrim(dw_sku)='')"),
        `missing blank-guard: ${line}`
      );
    }
  }
});

test('(d) undo.sql reverses apply.sql, keyed on shopify_id (stable cross-machine)', () => {
  const entries = [{ id: 101, shopify_id: 'gid://shopify/Product/77', handle: 'h', candidate: 'DWFC-1' }];
  const apply = renderApplySql(entries);
  const undo = renderUndoSql(entries);
  assert.ok(apply.includes("SET dw_sku='DWFC-1' WHERE shopify_id='gid://shopify/Product/77' AND (dw_sku IS NULL OR btrim(dw_sku)='')"));
  assert.ok(undo.includes("SET dw_sku=NULL WHERE shopify_id='gid://shopify/Product/77' AND dw_sku='DWFC-1'"));
  // MUST NOT key on the local serial id (meaningless on Kamatera).
  assert.ok(!apply.includes('WHERE id='), 'apply must not key on local id');
});

test('(e) apostrophe candidates are SQL-escaped', () => {
  const entries = [{ id: 301, shopify_id: 'g', handle: 'h', candidate: "O'CORK-3" }];
  const apply = renderApplySql(entries);
  const undo = renderUndoSql(entries);
  assert.equal(sqlEscape("O'CORK-3"), "O''CORK-3");
  assert.ok(apply.includes("dw_sku='O''CORK-3'"), 'apply must double the apostrophe');
  assert.ok(undo.includes("dw_sku='O''CORK-3'"), 'undo must double the apostrophe');
});

test('(f) candidate that is a title / garbage shape is rejected, never written', () => {
  const dirty = [
    { vendor: 'Cole & Son', sku: 'Bouncing Bubbles Mural - Cream', class: 'SELF_COPY_SOURCE', candidate: 'Bouncing Bubbles Mural - Cream' },
    { vendor: 'X', sku: 'DWKK-152210-Sold Per Bolt (20.5in x 33ft)', class: 'SELF_COPY_SOURCE', candidate: 'DWKK-152210-Sold Per Bolt (20.5in x 33ft)' },
    { vendor: 'Good', sku: 'AS784911-0', class: 'SELF_COPY_SOURCE', candidate: 'AS784911-0' }, // valid code-shape
  ];
  const kept = selectSelfCopyRows(dirty);
  assert.equal(kept.length, 1);
  assert.equal(kept[0].candidate, 'AS784911-0');
  assert.equal(kept.rejectedShape.length, 2);
});

test('(g) a self-copy row missing shopify_id is excluded + counted, never emitted', () => {
  const plan = [{ vendor: 'V', sku: 'DWV-1', class: 'SELF_COPY_DW', candidate: 'DWV-1' }];
  const map = new Map();
  map.set(keyOf('DWV-1', 'V'), [
    { id: 1, shopify_id: '', handle: 'no-gid' },              // excluded
    { id: 2, shopify_id: 'gid://shopify/Product/2', handle: 'ok' }, // kept
  ]);
  const plans = buildPlans(plan, map);
  assert.equal(plans.noShopifyId, 1);
  const entries = [...plans.byVendor.values()].flat();
  assert.equal(entries.length, 1);
  assert.equal(entries[0].shopify_id, 'gid://shopify/Product/2');
});

test('writePlans emits per-vendor artifacts + SUMMARY with correct totals', () => {
  const out = join(mkdtempSync(join(tmpdir(), 'tk10896-apply-')), 'apply-plans');
  const plans = buildPlans(PLAN, fixtureMap());
  const summary = writePlans(plans, out);
  assert.equal(summary.grand_total_statements, 5);
  assert.equal(summary.vendors, 3); // Fabricut, Carnegie, Cork Co
  assert.equal(summary.self_copy_plan_rows, 4);
  const s = JSON.parse(readFileSync(join(out, 'SUMMARY.json'), 'utf8'));
  assert.match(s.note, /NOTHING was executed/);
  // restore-map old value is null (honest: pre-apply was blank; undo -> NULL) + keyed on shopify_id
  const rm = JSON.parse(readFileSync(join(out, 'fabricut', 'restore-map.json'), 'utf8'));
  assert.ok(rm.every((r) => r.old === null && r.column === 'dw_sku' && typeof r.shopify_id === 'string'));
  // header present + gated
  const applySql = readFileSync(join(out, 'fabricut', 'apply.sql'), 'utf8');
  assert.match(applySql, /GATED -- canonical Kamatera dw_unified write/);
  assert.match(applySql, /Do NOT run automatically/);
  assert.match(applySql, /Recovers existing code \(no mint\)/);
});

// ---- mfr_sku allowlist HARD GATE (DTD verdict A, TK-10900) ------------------

test('GATE: a fabricated-mfr self-copy for a non-allowlisted vendor THROWS (cannot launder)', () => {
  const bad = [{ vendor: 'Maharam', sku: '', class: 'SELF_COPY_MFR', candidate: 'MH-300110-055' }];
  assert.throws(() => selectSelfCopyRows(bad), /NOT on MFR_SKU_REAL_ALLOWLIST/);
  const badCol = [{ vendor: 'CMO Paris', sku: '', class: 'SELF_COPY_MFR_COLLISION', candidate: 'CMO_1' }];
  assert.throws(() => selectSelfCopyRows(badCol), /NOT on MFR_SKU_REAL_ALLOWLIST/);
});

test('GATE: an allowlisted-vendor mfr self-copy does NOT throw, but this sku-keyed generator still emits no SQL for it', () => {
  const ok = [{ vendor: 'Carnegie', sku: '', class: 'SELF_COPY_MFR', candidate: '47081' }];
  const kept = selectSelfCopyRows(ok); // no throw
  assert.equal(kept.length, 0);        // mfr recovery has its own generator
});

test('GATE: mfr classes are forbidden here and MFR_SELF_COPY_CLASSES is exact', () => {
  assert.deepEqual([...MFR_SELF_COPY_CLASSES].sort(), ['SELF_COPY_MFR', 'SELF_COPY_MFR_COLLISION']);
  assert.ok(FORBIDDEN_CLASSES.has('SELF_COPY_MFR'));
  assert.ok(FORBIDDEN_CLASSES.has('SELF_COPY_MFR_COLLISION'));
  assert.ok(FORBIDDEN_CLASSES.has('MFR_FABRICATED_RESCRAPE'));
});