R26-1 R26-2 R26-3: 修复 env 注入并补部署校验与 smoke
这个提交包含在:
@@ -0,0 +1,136 @@
|
||||
require('dotenv/config');
|
||||
|
||||
const { createPrismaClient } = require('../prismaClient');
|
||||
const { getConfig } = require('../config');
|
||||
const {
|
||||
FEATURE_FLAG_KEYS,
|
||||
evaluateFeatureFlag,
|
||||
} = require('../services/featureFlags');
|
||||
const { getWorkspaceSetting, getSystemNavigation } = require('../services/workspaceSettings');
|
||||
const { resolvePermissionsForRoles, hasPermission } = require('../constants/permissions');
|
||||
|
||||
const REQUIRED_ENV_VALUES = {
|
||||
PORTAL_HOME_ENABLED: 'true',
|
||||
ANNOUNCEMENTS_ENABLED: 'true',
|
||||
PRESENCE_BOARD_ENABLED: 'true',
|
||||
WEEKLY_REPORT_ENABLED: 'true',
|
||||
EMPLOYEE_LIFECYCLE_ENABLED: 'true',
|
||||
TODO_BOARD_ENABLED: 'true',
|
||||
PROJECT_DOMAIN_ENABLED: 'true',
|
||||
PROJECT_COMMUNICATION_LOG_ENABLED: 'true',
|
||||
PROJECT_CLOSURE_CHECKLIST_ENABLED: 'true',
|
||||
COOKIE_SECURE: 'true',
|
||||
};
|
||||
|
||||
const REQUIRED_NON_EMPTY_ENV_KEYS = ['APP_ORIGIN'];
|
||||
|
||||
const REQUIRED_FLAGS = [
|
||||
FEATURE_FLAG_KEYS.portalHome,
|
||||
FEATURE_FLAG_KEYS.announcements,
|
||||
FEATURE_FLAG_KEYS.presenceBoard,
|
||||
FEATURE_FLAG_KEYS.weeklyReport,
|
||||
FEATURE_FLAG_KEYS.employeeLifecycle,
|
||||
FEATURE_FLAG_KEYS.todoBoard,
|
||||
FEATURE_FLAG_KEYS.projectDomain,
|
||||
FEATURE_FLAG_KEYS.projectCommunicationLog,
|
||||
FEATURE_FLAG_KEYS.projectClosureChecklist,
|
||||
];
|
||||
|
||||
function assertCondition(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const config = getConfig();
|
||||
const { prisma, pool } = createPrismaClient();
|
||||
await prisma.$connect();
|
||||
|
||||
try {
|
||||
console.log('[R26] 开始 trialrun runtime smoke');
|
||||
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
console.log('[PASS] Prisma 连接正常');
|
||||
|
||||
for (const [key, expected] of Object.entries(REQUIRED_ENV_VALUES)) {
|
||||
const actual = process.env[key];
|
||||
assertCondition(actual === expected, `${key} 期望为 ${expected},实际为 ${actual ?? '<empty>'}`);
|
||||
console.log(`[PASS] ${key}=${actual}`);
|
||||
}
|
||||
|
||||
for (const key of REQUIRED_NON_EMPTY_ENV_KEYS) {
|
||||
const actual = process.env[key];
|
||||
assertCondition(typeof actual === 'string' && actual.trim().length > 0, `${key} 不能为空`);
|
||||
console.log(`[PASS] ${key} 已配置`);
|
||||
}
|
||||
|
||||
const adminEmail = (process.env.TRIALRUN_ADMIN_EMAIL || '[email protected]').trim();
|
||||
const adminEmployee = await prisma.employee.findFirst({
|
||||
where: { email: adminEmail },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
status: true,
|
||||
department_id: true,
|
||||
roles: { select: { role_code: true } },
|
||||
},
|
||||
});
|
||||
|
||||
assertCondition(Boolean(adminEmployee), `未找到 trialrun admin 员工:${adminEmail}`);
|
||||
assertCondition(adminEmployee.status === 'active', `trialrun admin 状态异常:${adminEmployee.status}`);
|
||||
console.log(`[PASS] 已找到 active admin 员工:${adminEmployee.email}`);
|
||||
|
||||
const workspace = await getWorkspaceSetting(prisma);
|
||||
assertCondition(Boolean(workspace), 'WorkspaceSetting 读取失败');
|
||||
console.log(`[PASS] 工作区设置可读,profile=${workspace.profile}`);
|
||||
|
||||
const employeeForGate = {
|
||||
...adminEmployee,
|
||||
permissionCodes: resolvePermissionsForRoles(adminEmployee.roles.map((item) => item.role_code)),
|
||||
roleCodes: adminEmployee.roles.map((item) => item.role_code),
|
||||
};
|
||||
|
||||
for (const flagKey of REQUIRED_FLAGS) {
|
||||
const result = await evaluateFeatureFlag({
|
||||
prisma,
|
||||
config,
|
||||
flagKey,
|
||||
departmentId: employeeForGate.department_id ?? null,
|
||||
strictDepartment: false,
|
||||
});
|
||||
|
||||
assertCondition(
|
||||
result.enabled,
|
||||
`${flagKey} 求值失败:enabled=${result.enabled} reason=${result.reason} envEnabled=${result.envEnabled}`,
|
||||
);
|
||||
console.log(`[PASS] ${flagKey} => enabled (${result.reason})`);
|
||||
}
|
||||
|
||||
const navigation = await getSystemNavigation({
|
||||
prisma,
|
||||
config,
|
||||
employee: employeeForGate,
|
||||
hasPermission: (employee, permissionCode) => hasPermission(employee.permissionCodes ?? [], permissionCode),
|
||||
});
|
||||
|
||||
assertCondition(Array.isArray(navigation.sections?.main), '系统导航主区块生成失败');
|
||||
assertCondition(
|
||||
navigation.sections.main.some((item) => item.key === 'portal'),
|
||||
'系统导航未生成 portal 入口',
|
||||
);
|
||||
console.log(`[PASS] 导航构建成功,main=${navigation.sections.main.length} admin=${navigation.sections.admin.length}`);
|
||||
|
||||
console.log('[R26][PASS] trialrun runtime smoke 通过');
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('[R26][FAIL] trialrun runtime smoke 失败');
|
||||
console.error(error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户