GuDingZiChan/backend/scripts/__tests__/deployPrecheckSafety.test.js

98 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const test = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const deployPath = path.resolve(__dirname, '../../../deploy.sh')
const precheckPath = path.resolve(
__dirname,
'../../prisma/migrations/20260714_asset_permissions_disposal_dingtalk/precheck.sql'
)
const deployScript = fs.readFileSync(deployPath, 'utf8')
const precheckSql = fs.readFileSync(precheckPath, 'utf8')
test('标准部署在 build/up 前必须通过报废迁移预检', () => {
const gateCall = deployScript.lastIndexOf('\nrun_disposal_migration_precheck\n')
const buildCall = deployScript.indexOf('"${COMPOSE_CMD[@]}" build')
const upCall = deployScript.indexOf('"${COMPOSE_CMD[@]}" up -d --remove-orphans')
assert.ok(gateCall > 0, '部署脚本必须调用迁移前预检')
assert.ok(buildCall > gateCall, '预检必须早于镜像构建')
assert.ok(upCall > gateCall, '预检必须早于容器更新/迁移')
assert.match(
deployScript,
/backend\/prisma\/migrations\/20260714_asset_permissions_disposal_dingtalk\/precheck\.sql/
)
})
test('预检只连接固定资产 MySQL,不在命令行暴露密码', () => {
assert.match(deployScript, /MYSQL_CONTAINER="equipment-mysql"/)
assert.match(deployScript, /--defaults-extra-file="\$defaults_file"/)
assert.doesNotMatch(deployScript, /mysql\s+[^\n]*--password=/)
assert.doesNotMatch(deployScript, /shebeiguanli|\/opt\/shebeiguanli/)
assert.doesNotMatch(
deployScript,
/LEGACY_CONTAINERS|record_legacy_container_statuses|verify_legacy_container_statuses/
)
})
test('无法确认或存在未核对报废单时默认阻断,仅允许本次显式确认', () => {
assert.match(precheckSql, /SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;/)
assert.match(precheckSql, /START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY;/)
assert.match(precheckSql, /'ACTIVE_DISPOSAL_COUNT'/)
assert.match(precheckSql, /'REVIEW_ROW'/)
assert.match(precheckSql, /COMMIT;/)
assert.match(
deployScript,
/snapshot_output="\$\([\s\S]*run_equipment_mysql --batch --skip-column-names --raw < "\$DISPOSAL_PRECHECK_SQL"[\s\S]*\)"/
)
assert.match(deployScript, /printf '%s\\n' "\$review_rows" \| sha256sum/)
assert.match(
deployScript,
/CONFIRM_ACTIVE_DISPOSALS_REVIEWED" != "\$CURRENT_DISPOSAL_FINGERPRINT"/
)
assert.match(deployScript, /已默认阻止部署/)
})
test('未完成报废资产树存在环或深度达到 100 时禁止 override', () => {
const anomalyBlock = deployScript.indexOf('if [ "$tree_anomaly_count" -gt 0 ]')
const overrideCheck = deployScript.indexOf(
'if [ "$CONFIRM_ACTIVE_DISPOSALS_REVIEWED" != "$CURRENT_DISPOSAL_FINGERPRINT" ]'
)
assert.match(precheckSql, /WHERE `cycle_detected` = 1 OR `depth` >= 100/)
assert.ok(anomalyBlock > 0, '必须对资产树异常数量做硬阻断')
assert.ok(overrideCheck > anomalyBlock, '资产树异常阻断必须早于指纹 override')
assert.match(deployScript, /不允许通过确认指纹绕过/)
})
test('人工展示与指纹来自同一次数据库一致性快照', () => {
const functionBody = deployScript.slice(
deployScript.indexOf('calculate_active_disposal_fingerprint()'),
deployScript.indexOf('\nrun_disposal_migration_precheck()')
)
const mysqlCalls = functionBody.match(/run_equipment_mysql/g) || []
assert.equal(mysqlCalls.length, 1, '一次指纹计算只能打开一个数据库会话')
assert.match(functionBody, /review_rows=.*snapshot_output/)
assert.match(functionBody, /printf '%s\\n' "\$review_rows"/)
assert.doesNotMatch(
deployScript.slice(
deployScript.indexOf('run_disposal_migration_precheck()'),
deployScript.indexOf('\nrecheck_disposal_migration_precheck()')
),
/run_equipment_mysql\s+<\s+"\$DISPOSAL_PRECHECK_SQL"/
)
})
test('同名迁移同时存在完成和未完成记录时必须优先判为 incomplete', () => {
const stateQuery = deployScript.slice(
deployScript.indexOf('SELECT CASE'),
deployScript.indexOf('END\nFROM _prisma_migrations')
)
assert.ok(
stateQuery.indexOf("THEN 'incomplete'") < stateQuery.indexOf("THEN 'applied'"),
'未完成记录必须优先阻断,不能被旧的已完成记录掩盖'
)
})