280 lines
10 KiB
JavaScript
280 lines
10 KiB
JavaScript
/**
|
|
* Collects a deterministic fixture snapshot from the live testing backend.
|
|
*
|
|
* Usage:
|
|
* npm run rig:collect (collects snapshot fixtures from live backend)
|
|
* npm run rig:reference (captures reference screenshots)
|
|
*
|
|
* Reads ../dashback/.env for GOOGLE_CLIENT_ID (stored as googleClientId in the
|
|
* snapshot) and mints a JWT for user id 1 with the backend's own env + signing
|
|
* library, then GETs every endpoint the dummy backend needs to serve.
|
|
* Writes test/fixtures/snapshot.json (plus images/ for binary thumbs).
|
|
*
|
|
* The live backend must run at http://localhost:4208 (npm run dev in dashback).
|
|
*/
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const FRONT_ROOT = path.resolve(__dirname, '..');
|
|
const BACK_ROOT = path.resolve(FRONT_ROOT, '../dashback');
|
|
const FIXTURE_DIR = path.join(FRONT_ROOT, 'test', 'fixtures');
|
|
const IMAGE_DIR = path.join(FIXTURE_DIR, 'images');
|
|
|
|
const BACKEND = process.env.RIG_BACKEND ?? 'http://localhost:4208';
|
|
|
|
function parseDotEnv(text) {
|
|
const out = {};
|
|
for (const line of text.split('\n')) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const eq = trimmed.indexOf('=');
|
|
if (eq < 0) continue;
|
|
out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim();
|
|
}
|
|
return out;
|
|
}
|
|
|
|
async function mintToken() {
|
|
const env = await import(path.join(BACK_ROOT, 'src/config/env.js')).then(
|
|
(m) => m.default,
|
|
);
|
|
const { default: jwt } = await import(
|
|
path.join(BACK_ROOT, 'node_modules/jsonwebtoken/index.js')
|
|
);
|
|
return {
|
|
token: jwt.sign({ id: 1 }, env.jwtSecret, { expiresIn: '1d' }),
|
|
googleClientId: env.googleClientId ?? null,
|
|
};
|
|
}
|
|
|
|
async function get(token, p, { binary = false } = {}) {
|
|
const res = await fetch(`${BACKEND}${p}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`GET ${p} → ${res.status} ${text.slice(0, 200)}`);
|
|
}
|
|
if (binary) return Buffer.from(await res.arrayBuffer());
|
|
return res.json();
|
|
}
|
|
|
|
async function getOptional(token, p) {
|
|
try {
|
|
return await get(token, p);
|
|
} catch (err) {
|
|
console.warn(`[rig:collect] skip ${p}: ${err.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const KNOWN_SKU = 'ART-000001';
|
|
const KNOWN_DELIVERY = 6;
|
|
const KNOWN_ENTITY = 1;
|
|
|
|
async function main() {
|
|
fs.mkdirSync(FIXTURE_DIR, { recursive: true });
|
|
fs.mkdirSync(IMAGE_DIR, { recursive: true });
|
|
|
|
const snapshotFile = path.join(FIXTURE_DIR, 'snapshot.json');
|
|
try {
|
|
await fetch(`${BACKEND}/api/auth/me`, { signal: AbortSignal.timeout(1500) });
|
|
} catch {
|
|
if (fs.existsSync(snapshotFile)) {
|
|
console.log(`[rig:collect] Live backend (${BACKEND}) not running; using existing ${snapshotFile}`);
|
|
return;
|
|
}
|
|
throw new Error(`Live backend (${BACKEND}) not running and ${snapshotFile} does not exist.`);
|
|
}
|
|
|
|
// Load existing snapshot if available for fallbacks
|
|
let existingSnapshot = null;
|
|
try {
|
|
if (fs.existsSync(snapshotFile)) {
|
|
existingSnapshot = JSON.parse(fs.readFileSync(snapshotFile, 'utf8'));
|
|
}
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
|
|
// GOOGLE_CLIENT_ID comes from the testing backend env (../dashback/.env).
|
|
let dotenvClientId = null;
|
|
try {
|
|
const raw = fs.readFileSync(path.join(BACK_ROOT, '.env'), 'utf8');
|
|
dotenvClientId = parseDotEnv(raw).GOOGLE_CLIENT_ID ?? null;
|
|
} catch {
|
|
console.warn('[rig:collect] ../dashback/.env not readable, using backend env module');
|
|
}
|
|
|
|
const { token, googleClientId } = await mintToken();
|
|
|
|
const me = await get(token, '/api/auth/me');
|
|
const user = me.user;
|
|
const companyId = user.selectedCompanyId;
|
|
|
|
const snapshot = {
|
|
meta: {
|
|
collectedAt: new Date().toISOString(),
|
|
backend: BACKEND,
|
|
companyId,
|
|
googleClientId: dotenvClientId ?? googleClientId,
|
|
},
|
|
auth: {
|
|
me: { user, googleClientId: dotenvClientId ?? googleClientId },
|
|
companies: (await get(token, '/api/company')).companies ?? [],
|
|
},
|
|
company: {},
|
|
catalog: {},
|
|
parties: {},
|
|
documents: {},
|
|
misc: {},
|
|
images: {},
|
|
};
|
|
|
|
// Company detail is fetched per-id via GET /api/company (list) — the
|
|
// frontend's companyApi.getById() filters the list client-side.
|
|
snapshot.company.detail =
|
|
snapshot.auth.companies.find((c) => c.id === companyId) ?? null;
|
|
snapshot.company.settings =
|
|
(await getOptional(token, '/api/company/setting'))?.settings ?? [];
|
|
|
|
// Catalog
|
|
const tree = await get(token, '/api/category/tree');
|
|
snapshot.catalog.tree = tree;
|
|
const categoryIds = (tree.tree ?? []).map((t) => t.id);
|
|
snapshot.catalog.articlesByCategory = {};
|
|
for (const id of categoryIds) {
|
|
const data = await getOptional(token, `/api/category/${id}/articles`);
|
|
snapshot.catalog.articlesByCategory[String(id)] = data?.articles ?? [];
|
|
}
|
|
|
|
const allArticles = Object.values(snapshot.catalog.articlesByCategory).flat();
|
|
const targetSku =
|
|
allArticles.find((a) => a.sku === KNOWN_SKU)?.sku ||
|
|
allArticles[0]?.sku ||
|
|
KNOWN_SKU;
|
|
|
|
snapshot.catalog.articleDetail =
|
|
(await getOptional(token, `/api/article/${encodeURIComponent(targetSku)}`))?.article ??
|
|
existingSnapshot?.catalog?.articleDetail ??
|
|
null;
|
|
snapshot.catalog.articleHistory =
|
|
(await getOptional(token, `/api/article/${encodeURIComponent(targetSku)}/history?limit=50`))?.history ??
|
|
existingSnapshot?.catalog?.articleHistory ??
|
|
[];
|
|
snapshot.catalog.articleSuppliers =
|
|
(await getOptional(token, `/api/article/${encodeURIComponent(targetSku)}/supplier`))?.suppliers ??
|
|
existingSnapshot?.catalog?.articleSuppliers ??
|
|
[];
|
|
snapshot.catalog.brands = (await getOptional(token, '/api/brand'))?.brands ?? [];
|
|
snapshot.catalog.units = (await getOptional(token, '/api/unit'))?.units ?? [];
|
|
snapshot.catalog.warehouses =
|
|
(await getOptional(token, '/api/warehouse'))?.warehouses ?? [];
|
|
snapshot.catalog.attributes =
|
|
(await getOptional(token, '/api/attributename'))?.attributenames ?? [];
|
|
snapshot.catalog.properties =
|
|
(await getOptional(token, '/api/propertyname'))?.propertynames ?? [];
|
|
|
|
// Parties
|
|
snapshot.parties.suppliers =
|
|
(await getOptional(token, '/api/supplier'))?.suppliers ?? [];
|
|
snapshot.parties.entities =
|
|
(await getOptional(token, '/api/entity'))?.entities ?? [];
|
|
snapshot.parties.entityDetail =
|
|
(await getOptional(token, `/api/entity/${KNOWN_ENTITY}`))?.entity ?? null;
|
|
snapshot.parties.entityAddresses =
|
|
(await getOptional(token, `/api/entity/${KNOWN_ENTITY}/address`))?.addresses ?? [];
|
|
snapshot.parties.entityContacts =
|
|
(await getOptional(token, `/api/entity/${KNOWN_ENTITY}/contact`))?.contacts ?? [];
|
|
snapshot.parties.customergroups =
|
|
(await getOptional(token, '/api/customergroup'))?.customergroups ?? [];
|
|
|
|
// Documents
|
|
snapshot.documents.deliveries =
|
|
(await getOptional(token, '/api/delivery'))?.deliveries ?? [];
|
|
snapshot.documents.deliveryDetail =
|
|
(await getOptional(token, `/api/delivery/${KNOWN_DELIVERY}`))?.delivery ?? null;
|
|
snapshot.documents.quotes = (await getOptional(token, '/api/quote'))?.quotes ?? [];
|
|
snapshot.documents.orders = (await getOptional(token, '/api/order'))?.orders ?? [];
|
|
snapshot.documents.numbers =
|
|
(await getOptional(token, '/api/number'))?.numbers ?? [];
|
|
|
|
// Misc
|
|
snapshot.misc.posterminals =
|
|
(await getOptional(token, '/api/posterminal'))?.posterminals ?? [];
|
|
snapshot.misc.printers = (await getOptional(token, '/api/printer'))?.printers ?? [];
|
|
snapshot.misc.labeltemplates =
|
|
(await getOptional(token, '/api/labeltemplate'))?.labeltemplates ?? [];
|
|
snapshot.misc.vfs = (await getOptional(token, '/api/vfs'))?.files ?? [];
|
|
snapshot.misc.globalHistory =
|
|
(await getOptional(token, '/api/history?limit=50'))?.history ??
|
|
(await getOptional(token, '/api/history?limit=50'))?.rows ??
|
|
[];
|
|
snapshot.misc.articleHistoryGrid =
|
|
(await getOptional(token, '/api/article/history?limit=50'))?.history ??
|
|
(await getOptional(token, '/api/article/history?limit=50'))?.rows ??
|
|
[];
|
|
|
|
// Binary thumbs: collect every fullHash referenced by articles/categories/brands
|
|
// so /api/thumb/:hash and /api/image/:hash serve byte-identical images.
|
|
// Backend fullHash is an 8-char alphanumeric string (see dashback pic.js).
|
|
const hashes = new Set();
|
|
const collectHash = (h) => {
|
|
if (typeof h === 'string' && /^[A-Za-z0-9]{8}$/.test(h)) hashes.add(h);
|
|
};
|
|
for (const list of Object.values(snapshot.catalog.articlesByCategory)) {
|
|
for (const a of list) collectHash(a.fullHash);
|
|
}
|
|
for (const t of tree.tree ?? []) collectHash(t.fullHash);
|
|
for (const b of snapshot.catalog.brands) {
|
|
collectHash(b.fullHash);
|
|
for (const p of b.pics ?? []) collectHash(p.fullHash);
|
|
}
|
|
for (const p of snapshot.catalog.articleDetail?.photos ?? []) collectHash(p.fullHash);
|
|
|
|
let imageBytes = 0;
|
|
for (const hash of hashes) {
|
|
const dest = path.join(IMAGE_DIR, `${hash}.avif`);
|
|
if (fs.existsSync(dest) && fs.statSync(dest).size > 0) {
|
|
snapshot.images[hash] = `${hash}.avif`;
|
|
continue;
|
|
}
|
|
try {
|
|
const buf = await get(token, `/api/thumb/${hash}`, { binary: true });
|
|
fs.writeFileSync(dest, buf);
|
|
snapshot.images[hash] = `${hash}.avif`;
|
|
imageBytes += buf.length;
|
|
} catch (err) {
|
|
console.warn(`[rig:collect] thumb ${hash.slice(0, 12)}…: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(
|
|
path.join(FIXTURE_DIR, 'snapshot.json'),
|
|
`${JSON.stringify(snapshot, null, 2)}\n`,
|
|
);
|
|
|
|
const counts = {
|
|
categories: categoryIds.length,
|
|
categoryArticles: Object.values(snapshot.catalog.articlesByCategory).reduce(
|
|
(n, l) => n + l.length,
|
|
0,
|
|
),
|
|
brands: snapshot.catalog.brands.length,
|
|
images: Object.keys(snapshot.images).length,
|
|
deliveries: snapshot.documents.deliveries.length,
|
|
entities: snapshot.parties.entities.length,
|
|
};
|
|
console.log(
|
|
`[rig:collect] snapshot.json + ${counts.images} images ` +
|
|
`(${(imageBytes / 1024).toFixed(0)} KiB): ${JSON.stringify(counts)}`,
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(`[rig:collect] FAILED: ${err.message}`);
|
|
process.exit(1);
|
|
});
|