flnxi-Wu/scripts/__tests__/premium-module-source.test.ts
2026-06-03 18:41:52 +08:00

293 lines
16 KiB
TypeScript

import fs from 'fs'
import path from 'path'
describe('premium module source packages', () => {
const moduleIds = ['paid-core']
it('所有授权模块都应可作为独立模块镜像构建', () => {
for (const moduleId of moduleIds) {
const moduleDir = path.join(process.cwd(), 'premium-modules', moduleId)
const dockerfile = fs.readFileSync(path.join(moduleDir, 'Dockerfile'), 'utf8')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
expect(fs.existsSync(path.join(moduleDir, 'VERSION.txt'))).toBe(true)
expect(fs.existsSync(path.join(moduleDir, 'package.json'))).toBe(true)
expect(fs.existsSync(path.join(moduleDir, 'template-validation.mjs'))).toBe(true)
expect(dockerfile).toContain('HEALTHCHECK')
expect(dockerfile).toContain('EXPOSE 8787')
expect(dockerfile).toContain('template-validation.mjs')
expect(server).toContain(`const moduleId = '${moduleId}'`)
expect(server).toContain("url.pathname === '/health'")
expect(server).not.toContain("status: 'success'")
}
})
it('paid-core 未接真实穿透 provider 时必须明确返回未配置', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
expect(server).toContain("status: 'provider_not_configured'")
expect(server).toContain("url.pathname === '/tunnel/start'")
expect(server).toContain("url.pathname === '/tunnel/status'")
expect(server).toContain("url.pathname === '/tunnel/stop'")
expect(server).not.toContain("status: 'success'")
})
it('paid-core 应承接 API 访问功能门禁', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
expect(server).toContain("'apiAccess'")
expect(server).toContain("'apiAccess:use'")
expect(server).toContain("'apiAccess:read'")
})
it('paid-core 应承接自定义模板字段保存前校验', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/validate-fields'")
expect(validation).toContain('validateAndFormatUploadTemplateFields')
expect(validation).toContain('maxFields: 80')
})
it('paid-core 应承接自定义模板保存请求标准化', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/normalize-save'")
expect(server).toContain('normalizeUploadTemplateSaveRequest')
expect(server).toContain("action: 'customTemplates:normalizeSave'")
expect(validation).toContain('normalizeUploadTemplateSaveRequest')
expect(validation).toContain('ALLOWED_TEMPLATE_STATUSES')
expect(validation).toContain('ALLOWED_MATERIAL_KINDS')
for (const field of [
'name',
'description',
'status',
'materialType',
'materialKind',
'fieldGroups',
'displayConfig',
'fields',
]) {
expect(validation).toContain(`'${field}'`)
}
})
it('paid-core 应承接自定义模板删除规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/plan-delete'")
expect(server).toContain("action: 'customTemplates:planDelete'")
expect(validation).toContain('planUploadTemplateDeleteRequest')
expect(validation).toContain('TEMPLATE_IN_USE')
})
it('paid-core 应承接自定义模板更新规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/plan-update'")
expect(server).toContain("action: 'customTemplates:planUpdate'")
expect(validation).toContain('planUploadTemplateUpdateRequest')
expect(validation).toContain('VERSION_CONFLICT')
expect(validation).toContain('FIELD_NOT_IN_TEMPLATE')
expect(validation).toContain('TEMPLATE_STRUCTURE_LOCKED')
})
it('paid-core 应承接自定义模板素材字段提交校验', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/materials/validate-template-fields'")
expect(server).toContain("action: 'customTemplates:validateSubmittedFields'")
expect(validation).toContain('validateSubmittedTemplateFieldsRequest')
expect(validation).toContain('UNKNOWN_TEMPLATE_FIELDS')
expect(validation).toContain('TEMPLATE_REQUIRED_FIELDS_MISSING')
expect(validation).toContain('TEMPLATE_FIELD_VALIDATION_FAILED')
})
it('paid-core 应承接自定义模板素材 metadata 准备和大小检查', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/materials/prepare-metadata'")
expect(server).toContain("action: 'customTemplates:prepareMaterialMetadata'")
expect(validation).toContain('prepareMaterialMetadataRequest')
expect(validation).toContain('STANDARD_MATERIAL_FIELDS')
expect(validation).toContain('METADATA_TOO_LARGE')
})
it('paid-core 应承接自定义模板迁移 metadata 准备和备份计算', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/prepare-migration-metadata'")
expect(server).toContain("action: 'customTemplates:prepareMigrationMetadata'")
expect(validation).toContain('prepareTemplateMigrationMetadataRequest')
expect(validation).toContain('materialFieldValues')
expect(validation).toContain('templateMigrationBackups')
expect(validation).toContain('droppedTemplateFields')
expect(validation).toContain('droppedStandardFields')
expect(validation).toContain('MIGRATION_BACKUP_STANDARD_FIELDS')
expect(validation).toContain('CLEARABLE_NON_TARGET_FIELD_DEFAULTS')
expect(validation).toContain('AUDIO_MATERIAL_FIELDS')
expect(validation).toContain('normalizeOptionalMaterialColumnValue')
expect(validation).toContain('TARGET_TEMPLATE_FIELDS_UNRESOLVED')
expect(validation).toContain('METADATA_TOO_LARGE')
})
it('paid-core 应承接自定义模板迁移预览和备份字段影响统计', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/build-migration-preview'")
expect(server).toContain("action: 'customTemplates:buildMigrationPreview'")
expect(validation).toContain('buildTemplateMigrationPreviewRequest')
expect(validation).toContain('STANDARD_FIELD_LABELS')
expect(validation).toContain('buildPreviewFieldSummary')
expect(validation).toContain('addPreviewFieldImpact')
expect(validation).toContain('migrationBackup')
expect(validation).toContain('migrationPreview')
})
it('paid-core 应承接自定义模板迁移批处理规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/plan-migration-batch'")
expect(server).toContain("action: 'customTemplates:planMigrationBatch'")
expect(validation).toContain('planTemplateMigrationBatchRequest')
expect(validation).toContain('DEFAULT_MIGRATION_SAFE_LIMIT')
expect(validation).toContain('MAX_MIGRATION_FORCE_LIMIT')
expect(validation).toContain('MIGRATION_CONFIRM_REQUIRED')
})
it('paid-core 应承接自定义模板公开列表和素材类型投影', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/project-public-options'")
expect(server).toContain("action: 'customTemplates:projectPublicOptions'")
expect(validation).toContain('projectPublicTemplateOptionsRequest')
expect(validation).toContain('projectTemplateList')
expect(validation).toContain('projectUploadTemplateList')
expect(validation).toContain('projectMaterialTypes')
expect(validation).toContain('filterWritableTemplateFields')
expect(validation).toContain('stripTemplateMaterialSuffix')
expect(validation).toContain("`template:${template.id}:${templateName}`")
})
it('paid-core 应承接自定义模板字段填写率统计', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/calculate-field-stats'")
expect(server).toContain("action: 'customTemplates:calculateFieldStats'")
expect(validation).toContain('calculateTemplateFieldStatsRequest')
expect(validation).toContain('hasFilledTemplateFieldValue')
expect(validation).toContain('parseExistingMetadata')
expect(validation).toContain('isLowFillRate: fillRate < 30')
expect(validation).toContain('lowFillRateFields')
})
it('paid-core 应承接素材列表自定义字段搜索规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/materials/plan-custom-search'")
expect(server).toContain("action: 'customTemplates:planMaterialCustomSearch'")
expect(validation).toContain('planMaterialCustomSearchRequest')
expect(validation).toContain('normalizeFieldKeyList')
expect(validation).toContain('STANDARD_MATERIAL_FIELDS.has(key)')
})
it('paid-core 应承接素材列表搜索和筛选查询计划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/materials/plan-list-query'")
expect(server).toContain("action: 'customTemplates:planMaterialListQuery'")
expect(validation).toContain('planMaterialListQueryRequest')
expect(validation).toContain('standardSearchFields')
expect(validation).toContain('customSearchFieldKeys')
expect(validation).toContain('standardHomeFilters')
expect(validation).toContain('customHomeFilters')
})
it('paid-core 应承接首页自定义筛选字段统计规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/materials/plan-home-filter-counts'")
expect(server).toContain("action: 'customTemplates:planHomeFilterCounts'")
expect(validation).toContain('planHomeFilterCustomFieldCountsRequest')
expect(validation).toContain('MATERIAL_HOME_FILTER_COLUMN_FIELDS')
expect(validation).toContain('MATERIAL_HOME_FILTER_COLUMN_FIELDS.has(key)')
})
it('paid-core 应承接首页筛选字段统计计划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/materials/plan-home-filter-fields'")
expect(server).toContain("action: 'customTemplates:planHomeFilterFields'")
expect(validation).toContain('planHomeFilterFieldCountsRequest')
expect(validation).toContain('fieldPlans')
expect(validation).toContain("source: MATERIAL_HOME_FILTER_COLUMN_FIELDS.has(key) ? 'column' : 'metadata'")
})
it('paid-core 应承接部门隔离范围规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/departments/plan-scope'")
expect(server).toContain("action: 'departmentIsolation:planScope'")
expect(validation).toContain('planDepartmentScopeRequest')
expect(validation).toContain('buildDepartmentDescendantSet')
expect(validation).toContain('withinScope')
})
it('paid-core 应承接部门结构变更规划', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/departments/plan-structure-change'")
expect(server).toContain("action: 'departmentIsolation:planStructureChange'")
expect(validation).toContain('planDepartmentStructureChangeRequest')
expect(validation).toContain('departmentOps')
expect(validation).toContain('userOps')
expect(validation).toContain('clearManagedDepartmentIn')
})
it('paid-core 应承接自定义模板结构变更风险判断', () => {
const moduleDir = path.join(process.cwd(), 'premium-modules', 'paid-core')
const server = fs.readFileSync(path.join(moduleDir, 'server.mjs'), 'utf8')
const validation = fs.readFileSync(path.join(moduleDir, 'template-validation.mjs'), 'utf8')
expect(server).toContain("url.pathname === '/templates/detect-structure-changes'")
expect(validation).toContain('detectDangerousTemplateChanges')
expect(validation).toContain('fieldOptionsRemoved')
expect(validation).toContain('fieldConditionsChanged')
})
})