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'); const routes = read('backend/src/routes/products.ts'); for (const pattern of [ '/app-api/stores/:storeId/product-catalog', 'authenticateAccessToken', 'listStoreCatalog', 'tenantId: auth.session.tenantId' ]) assert.ok(routes.includes(pattern), `customer catalog route is missing ${pattern}`); const routeTest = read('backend/tests/product-route.test.mjs'); assert.match(routeTest, /app-api\/stores\/11\/product-catalog/); assert.match(routeTest, /customerCatalog\.statusCode, 200/); assert.match(routeTest, /statusCode, 401/); const navigation = read('admin/src/navigation.mjs'); const router = read('admin/src/router.ts'); const app = read('admin/src/App.vue'); assert.match(navigation, /menuKey: 'products'/); assert.match(navigation, /path: '\/products'/); for (const capability of [ 'product.catalog.read', 'inventory.read', 'inventory.adjust', 'goods.order.read', 'goods.order.manage', 'goods.storage.read', 'goods.storage.manage' ]) assert.ok(navigation.includes(capability), `product navigation is missing ${capability}`); assert.match(router, /ProductOperationsPanel\.vue/); assert.match(app, /ShoppingBasket/); const api = read('admin/src/api.ts'); for (const pattern of [ 'listProductListings', 'listInventoryStocks', 'mutateInventory', 'listProductOrders', 'getProductOrder', 'runProductOrderAction', 'listProductStorages', 'getProductStorage', 'createOrderProductStorage', 'runProductStorageAction' ]) assert.ok(api.includes(pattern), `admin product API is missing ${pattern}`); const panel = read('admin/src/components/ProductOperationsPanel.vue'); for (const pattern of [ 'canCatalogRead', 'canInventoryRead', 'canInventoryWrite', 'canOrderRead', 'canOrderWrite', 'canStorageRead', 'canStorageWrite', 'inventoryQuantity', 'stocktake', 'orderActions', 'submitRetrieve', 'rotateStorage', 'submitCreateStorage', '一次性领取码' ]) assert.ok(panel.includes(pattern), `product operations panel is missing ${pattern}`); assert.match(panel, /Promise\.all/); assert.match(panel, /:disabled="!canCatalogRead"/); const appJson = JSON.parse(read('miniapp/app.json')); assert.ok(appJson.pages.includes('pages/goods/index')); for (const path of [ 'miniapp/pages/goods/index.js', 'miniapp/pages/goods/index.json', 'miniapp/pages/goods/index.wxml', 'miniapp/pages/goods/index.wxss' ]) assert.ok(existsSync(join(root, path)), `${path} is missing`); const goodsScript = read('miniapp/pages/goods/index.js'); for (const pattern of [ '/product-orders', '/product-storages', '/product-catalog', 'createOrder', 'cancelOrder', 'storeOrder', 'retrieveItem', 'rotateCredential', 'cancelStorage', 'showCredential', 'clearCredential' ]) assert.ok(goodsScript.includes(pattern), `miniapp goods flow is missing ${pattern}`); assert.doesNotMatch(goodsScript, /(?:wx\.)?setStorage(?:Sync)?\s*\(/, 'one-time storage credential must not be persisted locally'); const goodsView = read('miniapp/pages/goods/index.wxml'); for (const pattern of [ '选购', '商品订单', '我的寄存', '领取码仅展示本次', '提交订单', '整单寄存', '更换领取码', '取出流水' ]) assert.ok(goodsView.includes(pattern), `miniapp goods view is missing ${pattern}`); const entryPoints = read('miniapp/pages/store/detail.js') + read('miniapp/pages/store/detail.wxml') + read('miniapp/pages/profile/index.js') + read('miniapp/pages/profile/index.wxml'); assert.match(entryPoints, /pages\/goods\/index/); assert.match(entryPoints, /选购商品/); console.log( 'PASS: M09-D4 authenticated catalog, capability-scoped admin workspace, customer order/storage flow and one-time credential gates are present.' );