forked from wangziqi/gongxue-base
371 lines
11 KiB
JavaScript
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)`);
|