import { test } from 'node:test' import assert from 'node:assert/strict' import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { resolve } from 'node:path' import { printings, resolutions, resolveCardAssetRequest, scanCardCatalog } from './card-catalog.mjs' async function createCard(root, options = {}) { const folderName = options.folderName ?? 'BP-002-david' const directory = resolve(root, folderName) await mkdir(directory, { recursive: true }) await writeFile(resolve(directory, 'card.json'), JSON.stringify({ cardId: 'BP-002', folderName, title: 'David', rarity: 'Blessed' })) await writeFile(resolve(directory, 'manifest.json'), JSON.stringify({ stage: options.stage ?? 'approved', cardId: 'BP-002', folderName, revision: 'accepted-revision', })) for (const [resolutionName, dimensions] of Object.entries(resolutions)) { const files = {} for (const printing of printings) { const target = resolve(directory, resolutionName, printing) await mkdir(target, { recursive: true }) for (const filename of ['card.png', 'finish-mask.png', ...(['normal', 'borderless'].includes(printing) ? ['text-mask.png'] : [])]) { const key = `${printing}/${filename}` files[key] = { sha256: `${resolutionName}-${printing}-${filename}`, dimensions } await writeFile(resolve(target, filename), key) } } await writeFile(resolve(directory, resolutionName, 'manifest.json'), JSON.stringify({ dimensions, files })) } return directory } test('catalog exposes every accepted resolution and printing from artifact manifests', async (t) => { const root = await mkdtemp(resolve(tmpdir(), 'accepted-card-catalog-')) t.after(() => rm(root, { recursive: true })) await createCard(root) const catalog = await scanCardCatalog(root) assert.deepEqual(catalog.errors, []) assert.equal(catalog.schemaVersion, 2) assert.equal(catalog.cards.length, 1) const card = catalog.cards[0] assert.equal(card.cardId, 'BP-002') assert.equal(card.title, 'David') assert.equal(card.acceptedRevision, 'accepted-revision') assert.equal(card.variants.high.normal.width, 2000) assert.match(card.variants.high.normal.artwork, /^\/__card_asset\/BP-002-david\/high\/normal\/card\.png\?v=/) assert.match(card.variants.low.normal.textMask, /text-mask\.png\?v=/) assert.equal(card.variants.low.textless.textMask, undefined) }) test('catalog rejects unapproved and identity-mismatched packages without hiding errors', async (t) => { const root = await mkdtemp(resolve(tmpdir(), 'invalid-accepted-card-catalog-')) t.after(() => rm(root, { recursive: true })) await createCard(root, { folderName: 'BP-002-draft', stage: 'candidate' }) const bad = await createCard(root, { folderName: 'BP-002-wrong-folder' }) await writeFile(resolve(bad, 'manifest.json'), JSON.stringify({ stage: 'approved', cardId: 'BP-999', folderName: 'BP-002-wrong-folder', revision: 'x', })) const catalog = await scanCardCatalog(root) assert.deepEqual(catalog.cards, []) assert.ok(catalog.errors.some(error => error.includes('not approved'))) assert.ok(catalog.errors.some(error => error.includes('identity differs'))) }) test('artifact asset routes allow only real runtime files inside the accepted root', async (t) => { const root = await mkdtemp(resolve(tmpdir(), 'accepted-card-route-')) t.after(() => rm(root, { recursive: true })) await createCard(root) const path = await resolveCardAssetRequest('/__card_asset/BP-002-david/high/normal/card.png', root) assert.equal(path, resolve(root, 'BP-002-david/high/normal/card.png')) assert.equal(await resolveCardAssetRequest('/__card_asset/BP-002-david/high/normal/manifest.json', root), undefined) assert.equal(await resolveCardAssetRequest('/__card_asset/..%2Foutside/high/normal/card.png', root), undefined) const outside = resolve(root, '..', `outside-${Date.now()}.png`) await writeFile(outside, 'outside') t.after(() => rm(outside)) await rm(resolve(root, 'BP-002-david/high/normal/card.png')) await symlink(outside, resolve(root, 'BP-002-david/high/normal/card.png')) assert.equal(await resolveCardAssetRequest('/__card_asset/BP-002-david/high/normal/card.png', root), undefined) })