Files
gongxue-base/scripts/taro-persona-contract-test.js
2026-07-01 01:59:23 +08:00

371 lines
11 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
const repoRoot = process.cwd();
const taroSrc = path.join(repoRoot, 'apps', 'taro', 'src');
const contracts = [
{
id: 'student.home-launcher',
file: 'pages/student/home/index.tsx',
mustContain: [
'loadStudentDashboard',
'loadCurrentUser',
'/pages/student/catalog/index',
'/pages/student/review/index',
'/pages/student/profile/index',
'/pages/student/assets/index',
],
mustNotContain: ['loadLeaderboard', 'leaderboard', '排行榜'],
},
{
id: 'student.catalog-to-practice',
file: 'pages/student/catalog/index.tsx',
mustContain: [
'loadContentEntries',
'loadContentNodes',
'loadQuestionCollections',
'loadPracticeBlueprints',
'/pages/student/practice/index?',
'collectionId=',
'blueprintId=',
'mode=',
],
},
{
id: 'student.practice-svip-loop',
file: 'pages/student/practice/index.tsx',
mustContain: [
'createPracticeSession',
'loadPracticeSessionDetail',
'submitAnswer',
'submitPracticeSession',
'toggleQuestionFavorite',
'submitFeedback',
'loadCollectionQuestions',
'loadQuestions',
'answerStorageKey',
'indexStorageKey',
'selfJudgedCorrect',
'subAnswers',
'/pages/student/reports/index?practiceSessionId=',
'/pages/student/review/index?type=wrong',
'/pages/student/video/index?questionId=',
],
},
{
id: 'student.review-wrong-favorite',
file: 'pages/student/review/index.tsx',
mustContain: [
'loadWrongQuestionReviewPlan',
'loadFavoriteQuestions',
'resolveWrongQuestion',
'wrong_review',
'favorite_review',
'/pages/student/practice/index?mode=',
],
},
{
id: 'student.checkout-svip-order',
file: 'pages/student/checkout/index.tsx',
mustContain: [
'loadSvipPlans',
'claimCoupon',
'createOrder',
'createPayment',
'loadOrderStatus',
'Taro.requestPayment',
'/pages/student/order-detail/index?orderNo=',
],
},
{
id: 'student.profile-center',
file: 'pages/student/profile/index.tsx',
mustContain: [
'loadProfile',
'loadEntitlements',
'loadLearningStats',
'loadPracticeHistory',
'checkActivationCode',
'redeemActivationCode',
'loadNotifications',
'updateNotificationStatus',
'loadBadges',
'avatarPreset',
'/pages/student/review/index?type=wrong',
'/pages/student/review/index?type=favorite',
'/pages/student/checkout/index',
],
mustNotContain: ['chooseImage', 'uploadFile', 'avatarUrl', 'headimgurl', 'figureurl'],
},
{
id: 'tenant.workbench-permission-modules',
file: 'pages/tenant-admin/workbench/index.tsx',
mustContain: [
'loadTenantPermissions',
'canOpenModule',
'/pages/tenant-admin/dashboard/index',
'/pages/tenant-admin/students/index',
'/pages/tenant-admin/content/index',
'/pages/tenant-admin/marketing/index',
'/pages/tenant-admin/finance/index',
'/pages/tenant-admin/settings/index',
],
},
{
id: 'tenant.students-ops-crm-supervision',
file: 'pages/tenant-admin/students/index.tsx',
mustContain: [
'loadTenantStudents',
'upsertTenantStudent',
'bulkUpsertTenantStudents',
'bulkAssignTenantClassMembers',
'updateTenantStudentStatus',
'upsertTenantStudentNote',
'upsertTenantStudentFollowup',
'previewTenantStudentSupervision',
'generateTenantStudentSupervision',
'upsertTenantStudentSupervisionRule',
'pushTenantStudentsToCrm',
],
mustNotContain: ['avatarUrl', 'headimgurl', 'figureurl', 'primaryRole'],
},
{
id: 'tenant.content-import-public-bank',
file: 'pages/tenant-admin/content/index.tsx',
mustContain: [
'previewContentImport',
'executeContentImport',
'loadImportJobs',
'loadImportJobDetail',
'loadImportIssues',
'loadImportFieldMapping',
'loadImportTemplate',
'runImportPostCheck',
'loadPublicQuestionBanks',
'adoptPublicQuestionBank',
'syncPublicQuestionBank',
'loadPublicQuestionBankConflicts',
'resolvePublicQuestionBankConflict',
'resolvePublicQuestionBankConflicts',
'loadTenantContentNotifications',
],
},
{
id: 'tenant.marketing-commerce-growth',
file: 'pages/tenant-admin/marketing/index.tsx',
mustContain: [
'loadCoupons',
'upsertCoupon',
'loadCouponRedemptions',
'loadCouponReport',
'loadCrmConfig',
'upsertCrmConfig',
'loadCrmQueue',
'loadCrmDeadLetters',
'updateCrmQueueAction',
'loadReferralConversionReport',
'loadCommissionSettings',
'updateCommissionSettings',
'generateCommissionSettlement',
'updateCommissionSettlementStatus',
'createCommissionSettlementProof',
'upsertPointActivityTask',
'upsertPointExchangeItem',
'loadUserNotifications',
],
},
{
id: 'tenant.finance-reconciliation',
file: 'pages/tenant-admin/finance/index.tsx',
mustContain: [
'loadTenantRefunds',
'createTenantRefund',
'updateTenantRefundStatus',
'requestProviderBillDownload',
'loadProviderBillJobs',
'loadReconciliationBatches',
'loadReconciliationItems',
'createReconciliationIssue',
'updateReconciliationIssueStatus',
'loadCommerceOperationAnomalies',
'createAdjustmentVoucher',
'updateAdjustmentVoucherStatus',
'loadAdjustmentVoucherReport',
],
},
{
id: 'tenant.settings-brand-auth-role',
file: 'pages/tenant-admin/settings/index.tsx',
mustContain: [
'loadTenantOverview',
'loadTenantDomains',
'loadPaymentAccounts',
'loadAuthProviders',
'loadThemeTemplates',
'loadTenantTheme',
'previewTenantTheme',
'publishTenantTheme',
'loadTenantPermissions',
'loadRoleTemplates',
'upsertRoleTemplate',
'disableRoleTemplate',
'loadTenantMembers',
'upsertTenantMember',
'disableTenantMember',
],
},
{
id: 'platform.workbench-governance',
file: 'pages/platform-admin/workbench/index.tsx',
mustContain: [
'loadPlatformOverview',
'loadPlatformTenants',
'loadPlatformInvoices',
'loadPlatformQuestionBanks',
'loadPlatformQuestionBankGrants',
'loadPlatformAuditLogs',
'exportPlatformAuditLogs',
'loadPlatformAuditAlerts',
'updatePlatformAuditAlertStatus',
'loadPlatformAuditNotificationChannels',
'loadPlatformDunningNotificationChannels',
'/pages/platform-admin/tenants/index',
'/pages/platform-admin/billing/index',
'/pages/platform-admin/question-banks/index',
'/pages/platform-admin/staff/index',
],
},
{
id: 'platform.tenant-lifecycle',
file: 'pages/platform-admin/tenants/index.tsx',
mustContain: [
'loadPlatformPlans',
'loadPlatformTenants',
'createPlatformTenant',
'loadPlatformTenantDetail',
'updatePlatformTenantStatus',
'upsertPlatformTenantBillingProfile',
'loadPlatformAuditLogs',
],
},
{
id: 'platform.billing-usage-dunning',
file: 'pages/platform-admin/billing/index.tsx',
mustContain: [
'loadPlatformPlans',
'createPlatformSubscription',
'loadPlatformInvoices',
'createPlatformInvoiceFromSubscription',
'loadPlatformSubscriptionInvoiceCandidates',
'createPlatformInvoicesBatchFromSubscriptions',
'loadPlatformUsageOverageInvoiceCandidates',
'createPlatformInvoicesFromUsageOverage',
'confirmPlatformInvoicePayment',
'loadPlatformUsage',
'recordPlatformUsage',
'processPlatformOverdueInvoices',
'loadPlatformInvoiceReminders',
],
},
{
id: 'platform.public-bank-grants',
file: 'pages/platform-admin/question-banks/index.tsx',
mustContain: [
'loadPlatformQuestionBanks',
'loadPlatformQuestionBankGrants',
'upsertPlatformQuestionBankGrant',
'allowedPlanCodes',
'allowedTenantIds',
'all_active_tenants',
],
},
{
id: 'platform.staff-permissions',
file: 'pages/platform-admin/staff/index.tsx',
mustContain: [
'loadPlatformPermissions',
'loadPlatformStaff',
'upsertPlatformStaff',
'updatePlatformStaffStatus',
'authUserId',
'platformPermissions',
'revokeSessions',
],
},
];
const routeContracts = [
{
id: 'student-first-web-launch',
routes: [
'pages/student/home/index',
'pages/student/catalog/index',
'pages/student/practice/index',
'pages/student/review/index',
'pages/student/checkout/index',
'pages/student/order-detail/index',
'pages/student/profile/index',
'pages/student/notifications/index',
],
},
{
id: 'tenant-admin-launch',
routes: [
'pages/tenant-admin/workbench/index',
'pages/tenant-admin/dashboard/index',
'pages/tenant-admin/students/index',
'pages/tenant-admin/content/index',
'pages/tenant-admin/marketing/index',
'pages/tenant-admin/finance/index',
'pages/tenant-admin/settings/index',
],
},
{
id: 'platform-admin-launch',
routes: [
'pages/platform-admin/workbench/index',
'pages/platform-admin/tenants/index',
'pages/platform-admin/billing/index',
'pages/platform-admin/question-banks/index',
'pages/platform-admin/staff/index',
],
},
];
function readText(relativePath) {
return fs.readFileSync(path.join(taroSrc, relativePath), 'utf8').replace(/\r\n/g, '\n');
}
function assertContains(text, token, label) {
assert.ok(text.includes(token), `${label} must contain ${token}`);
}
function assertNotContains(text, token, label) {
assert.ok(!text.includes(token), `${label} must not contain ${token}`);
}
function parseAppRoutes() {
const text = readText('app.config.ts');
const match = text.match(/pages\s*:\s*\[([\s\S]*?)\]/m);
assert.ok(match, 'apps/taro/src/app.config.ts must define pages');
return new Set([...match[1].matchAll(/['"`]([^'"`]+)['"`]/g)].map(item => item[1]));
}
const appRoutes = parseAppRoutes();
for (const contract of routeContracts) {
for (const route of contract.routes) {
assert.ok(appRoutes.has(route), `${contract.id} route must be registered: ${route}`);
assert.ok(fs.existsSync(path.join(taroSrc, `${route}.tsx`)), `${contract.id} route file must exist: ${route}.tsx`);
}
}
for (const contract of contracts) {
const text = readText(contract.file);
for (const token of contract.mustContain || []) assertContains(text, token, contract.id);
for (const token of contract.mustNotContain || []) assertNotContains(text, token, contract.id);
}
console.log(`[PASS] Taro persona contract (${contracts.length} page contracts, ${routeContracts.length} route groups)`);