90 lines
3.2 KiB
JavaScript
90 lines
3.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
const root = process.cwd();
|
|
const read = (path) => readFileSync(join(root, path), 'utf8');
|
|
|
|
for (const suffix of ['up', 'down', 'verify']) {
|
|
assert.ok(
|
|
existsSync(join(root,
|
|
`database/migrations/2026081107_m09d1_product_inventory_foundation.${suffix}.sql`)),
|
|
`M09-D1 ${suffix} migration is missing`
|
|
);
|
|
}
|
|
|
|
const migration = read('database/migrations/2026081107_m09d1_product_inventory_foundation.up.sql');
|
|
for (const pattern of [
|
|
'qipai_product_categories',
|
|
'qipai_products',
|
|
'qipai_product_skus',
|
|
'qipai_product_store_listings',
|
|
'qipai_product_store_hours',
|
|
'qipai_product_inventory',
|
|
'qipai_product_inventory_requests',
|
|
'qipai_product_inventory_ledger',
|
|
'delivery_enabled',
|
|
'storage_enabled',
|
|
'crosses_midnight',
|
|
'manual_paused_at',
|
|
'loss_quantity',
|
|
'loss_delta',
|
|
'uq_qipai_product_inventory_request',
|
|
'ADD COLUMN checksum',
|
|
'uq_qipai_product_inventory_ledger_request',
|
|
'uq_qipai_product_inventory_ledger_version',
|
|
'PRODUCT_INVENTORY_LEDGER_IMMUTABLE',
|
|
'product.catalog.read',
|
|
'inventory.adjust'
|
|
]) assert.ok(migration.includes(pattern), `migration is missing ${pattern}`);
|
|
assert.match(migration, /sale_price_cents INT UNSIGNED/);
|
|
assert.match(migration, /available_quantity BIGINT UNSIGNED/);
|
|
assert.match(migration, /operation IN \([\s\S]*'INBOUND'[\s\S]*'STOCKTAKE'[\s\S]*'LOSS'/);
|
|
|
|
const catalog = read('backend/src/products/product-catalog-repository.ts');
|
|
for (const pattern of [
|
|
'ProductCatalogRepository',
|
|
'expectedVersion',
|
|
'PRODUCT_CATEGORY_VERSION_CONFLICT',
|
|
'PRODUCT_VERSION_CONFLICT',
|
|
'PRODUCT_SKU_VERSION_CONFLICT',
|
|
'PRODUCT_LISTING_VERSION_CONFLICT',
|
|
'PRODUCT_STORE_SETTINGS_VERSION_CONFLICT',
|
|
'crossesMidnight',
|
|
'manualPaused'
|
|
]) assert.ok(catalog.includes(pattern), `product catalog is missing ${pattern}`);
|
|
assert.match(catalog, /INSERT INTO qipai_audit_logs/);
|
|
|
|
const inventory = read('backend/src/inventory/inventory-service.ts');
|
|
for (const pattern of [
|
|
'InventoryService',
|
|
'lockMany',
|
|
'releaseMany',
|
|
'deductMany',
|
|
'INVENTORY_INSUFFICIENT_AVAILABLE',
|
|
'INVENTORY_INSUFFICIENT_LOCKED',
|
|
'INVENTORY_IDEMPOTENCY_CONFLICT',
|
|
'FOR UPDATE',
|
|
'qipai_product_inventory_ledger',
|
|
'qipai_audit_logs'
|
|
]) assert.ok(inventory.includes(pattern), `inventory service is missing ${pattern}`);
|
|
|
|
const productRoutes = read('backend/src/routes/products.ts');
|
|
const inventoryRoutes = read('backend/src/routes/inventory.ts');
|
|
assert.match(productRoutes, /product-categories/);
|
|
assert.match(productRoutes, /\/admin-api\/products/);
|
|
assert.match(productRoutes, /product-sales-settings/);
|
|
assert.match(inventoryRoutes, /inventory\/stocks/);
|
|
assert.match(inventoryRoutes, /\/adjust/);
|
|
|
|
const mysqlTest = read('backend/tests/mysql-migration-roundtrip.test.mjs');
|
|
for (const pattern of [
|
|
'assertProductInventoryFoundation',
|
|
'INVENTORY_INSUFFICIENT_AVAILABLE',
|
|
'PRODUCT_INVENTORY_LEDGER_IMMUTABLE',
|
|
'concurrent inventory lock',
|
|
'cross-midnight product hours'
|
|
]) assert.ok(mysqlTest.includes(pattern), `live MySQL test is missing ${pattern}`);
|
|
|
|
console.log('PASS: M09-D1 catalog, store-hours, stock concurrency and immutable-ledger gates are present.');
|