Files
qipai/scripts/check-m10-a-notifications.mjs
T

43 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/2026081110_m10a_notification_center.${suffix}.sql`)));
const migration = read('database/migrations/2026081110_m10a_notification_center.up.sql');
for (const pattern of ['qipai_notification_templates', 'qipai_notification_routes',
'qipai_notification_subscriptions', 'qipai_notification_deliveries',
'qipai_notification_attempts', 'uq_qipai_notification_delivery_idempotency',
'scope_store_id BIGINT UNSIGNED GENERATED ALWAYS AS (COALESCE(store_id, 0)) STORED',
'NOTIFICATION_ATTEMPT_IMMUTABLE', 'notification.read', 'notification.manage'])
assert.ok(migration.includes(pattern), `notification migration is missing ${pattern}`);
const service = read('backend/src/notifications/notification-service.ts');
for (const pattern of ['materializeOutboxEvent', 'enqueuePendingOutbox', 'handleTask',
'manualRetry', 'WECHAT_SUBSCRIBE', 'hasSubscription', 'inQuietHours',
'setSubscription', 'listInbox', 'markInboxRead', "trigger === 'MANUAL'",
'HttpNotificationAdapter', 'createNotificationAdaptersFromEnvironment',
'sensitiveKey', 'notification.dispatch', 'outbox.publish'])
assert.ok(service.includes(pattern), `notification service is missing ${pattern}`);
assert.match(read('backend/src/tasks/worker.ts'), /notification\.dispatch[\s\S]*outbox\.publish/);
assert.match(read('backend/src/products/product-order-service.ts'), /INSERT IGNORE INTO qipai_outbox_events/);
for (const source of ['backend/src/orders/pricing-repository.ts',
'backend/src/orders/order-state-repository.ts', 'backend/src/payments/payment-repository.ts',
'backend/src/payments/wechat-payment-service.ts',
'backend/src/cleaning/cleaning-task-repository.ts',
'backend/src/devices/iot-message-service.ts']) {
assert.match(read(source), /qipai_outbox_events/, `${source} must publish notification events`);
}
const routes = read('backend/src/routes/notifications.ts');
for (const pattern of ['/admin-api', '/app-api/management', '/notifications/templates',
'/notifications/routes', '/notifications/deliveries', '/retry',
'/app-api/notifications/subscriptions', '/app-api/notifications/inbox']) assert.ok(routes.includes(pattern));
const panel = read('admin/src/components/NotificationCenterPanel.vue');
for (const pattern of ['事件路由、模板与投递追踪', '人工补发', '新增通知模板',
'新增通知路由', '静默开始/结束']) assert.ok(panel.includes(pattern));
assert.match(read('admin/src/navigation.mjs'), /menuKey: 'notifications'/);
assert.match(read('admin/src/router.ts'), /NotificationCenterPanel\.vue/);
const mysql = read('backend/tests/mysql-migration-roundtrip.test.mjs');
for (const pattern of ['assertNotificationCenter', 'must-not-leak', 'explicit authorization',
'simulated webhook outage', "triggerType, operator_id AS operatorId",
'NOTIFICATION_ATTEMPT_IMMUTABLE']) assert.ok(mysql.includes(pattern));
console.log('PASS: M10-A notification templates, routing, consent, outbox fan-out, retry and operations gates are present.');