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') }) })