forked from wangziqi/gongxue-base
196 lines
8.5 KiB
JavaScript
196 lines
8.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const repoRoot = process.cwd();
|
|
const scriptPath = path.join(repoRoot, 'scripts', 'production-readiness-check.js');
|
|
|
|
function runReadiness(envContent) {
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-readiness-'));
|
|
const envFile = path.join(tempDir, '.env');
|
|
fs.writeFileSync(envFile, envContent, 'utf8');
|
|
|
|
const result = spawnSync(process.execPath, [scriptPath, '--env-file', envFile, '--skip-db', '--json'], {
|
|
cwd: repoRoot,
|
|
encoding: 'utf8',
|
|
env: {
|
|
PATH: process.env.PATH || '',
|
|
Path: process.env.Path || '',
|
|
SystemRoot: process.env.SystemRoot || '',
|
|
ComSpec: process.env.ComSpec || '',
|
|
TEMP: process.env.TEMP || os.tmpdir(),
|
|
TMP: process.env.TMP || os.tmpdir(),
|
|
},
|
|
});
|
|
|
|
const payload = JSON.parse(result.stdout || '{}');
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
return { ...result, payload };
|
|
}
|
|
|
|
const unsafe = runReadiness(`
|
|
NODE_ENV=development
|
|
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
|
AUTH_SMS_PROVIDER=mock
|
|
`);
|
|
|
|
assert.notEqual(unsafe.status, 0, 'unsafe production readiness should fail');
|
|
assert.ok(unsafe.payload.summary?.blocker > 0, 'unsafe readiness should report blockers');
|
|
assert.ok(
|
|
unsafe.payload.checks?.some(item => item.id === 'env.node_env' && item.status === 'blocker'),
|
|
'unsafe readiness should block non-production NODE_ENV',
|
|
);
|
|
assert.ok(
|
|
unsafe.payload.checks?.some(item => item.id === 'env.auth_sms_provider' && item.status === 'blocker'),
|
|
'unsafe readiness should block mock SMS provider',
|
|
);
|
|
|
|
const strongSecretA = 's3cure-prod-code-pepper-2026-06-29-abcdef';
|
|
const strongSecretB = 's3cure-prod-session-secret-2026-06-29-ghijkl';
|
|
const strongSecretC = 's3cure-platform-admin-key-2026-06-29-mnopqr';
|
|
|
|
const safe = runReadiness(`
|
|
NODE_ENV=production
|
|
DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku
|
|
CORS_ORIGIN=https://student.gongxue100.com,https://tenant-admin.gongxue100.com,https://platform-admin.gongxue100.com
|
|
AUTH_SMS_PROVIDER=aliyun
|
|
AUTH_CODE_PEPPER=${strongSecretA}
|
|
AUTH_SESSION_SECRET=${strongSecretB}
|
|
AUTH_JWT_JWKS_URL=https://auth.gongxue100.com/auth/v1/.well-known/jwks.json
|
|
AUTH_JWT_ISSUER=https://auth.gongxue100.com/auth/v1
|
|
ALLOW_LEGACY_AUTH_HEADERS=false
|
|
ALLOW_PLATFORM_ADMIN_KEY=false
|
|
PLATFORM_ADMIN_API_KEY=${strongSecretC}
|
|
MAX_JSON_BODY_BYTES=1048576
|
|
MAX_IMPORT_JSON_BODY_BYTES=10485760
|
|
STORAGE_DEFAULT_PROVIDER=aliyun_oss
|
|
STORAGE_DEFAULT_BUCKET=tiku-assets
|
|
STORAGE_REQUIRE_TENANT_PREFIX=true
|
|
STORAGE_ALLOWED_MIME_TYPES=application/pdf,image/png,image/jpeg,video/mp4,text/plain,text/csv
|
|
ALIYUN_OSS_REGION=cn-hangzhou
|
|
ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
|
|
ALIYUN_OSS_ACCESS_KEY_ID=LTAI_READINESS_TEST_ONLY
|
|
ALIYUN_OSS_ACCESS_KEY_SECRET=aliyun-readiness-secret-placeholder
|
|
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.gongxue100.com/api/scan
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=s3cure-asset-scanner-token-2026-06-29-stuvwx
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_TIMEOUT_MS=10000
|
|
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
|
|
WORKER_CRM_ALLOW_INSECURE_LOCALHOST=false
|
|
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
|
|
WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
|
|
WORKER_CRM_BATCH_SIZE=20
|
|
WORKER_COMMERCE_BATCH_SIZE=20
|
|
WORKER_ASSET_BATCH_SIZE=50
|
|
WORKER_IMPORT_BATCH_SIZE=5
|
|
WORKER_PUBLIC_BANK_SYNC_BATCH_SIZE=5
|
|
`);
|
|
|
|
assert.equal(safe.status, 0, `safe readiness should pass without blockers: ${safe.stdout} ${safe.stderr}`);
|
|
assert.equal(safe.payload.summary?.blocker, 0, 'safe readiness should have no blockers');
|
|
assert.ok(
|
|
safe.payload.checks?.some(item => item.id === 'db.skipped' && item.status === 'warn'),
|
|
'env-only readiness should explicitly warn that DB checks are skipped',
|
|
);
|
|
|
|
const missingJwksIssuer = runReadiness(`
|
|
NODE_ENV=production
|
|
DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku
|
|
CORS_ORIGIN=https://student.gongxue100.com
|
|
AUTH_SMS_PROVIDER=aliyun
|
|
AUTH_CODE_PEPPER=${strongSecretA}
|
|
AUTH_SESSION_SECRET=${strongSecretB}
|
|
AUTH_JWT_JWKS_URL=https://auth.gongxue100.com/auth/v1/.well-known/jwks.json
|
|
ALLOW_LEGACY_AUTH_HEADERS=false
|
|
ALLOW_PLATFORM_ADMIN_KEY=false
|
|
PLATFORM_ADMIN_API_KEY=${strongSecretC}
|
|
STORAGE_DEFAULT_PROVIDER=aliyun_oss
|
|
STORAGE_DEFAULT_BUCKET=tiku-assets
|
|
STORAGE_REQUIRE_TENANT_PREFIX=true
|
|
ALIYUN_OSS_REGION=cn-hangzhou
|
|
ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
|
|
ALIYUN_OSS_ACCESS_KEY_ID=LTAI_READINESS_TEST_ONLY
|
|
ALIYUN_OSS_ACCESS_KEY_SECRET=aliyun-readiness-secret-placeholder
|
|
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.gongxue100.com/api/scan
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=s3cure-asset-scanner-token-2026-06-29-stuvwx
|
|
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
|
|
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
|
|
WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
|
|
`);
|
|
|
|
assert.notEqual(missingJwksIssuer.status, 0, 'JWKS readiness without issuer should fail');
|
|
assert.ok(
|
|
missingJwksIssuer.payload.checks?.some(item => item.id === 'env.auth_jwt_issuer' && item.status === 'blocker'),
|
|
'JWKS readiness should block missing AUTH_JWT_ISSUER',
|
|
);
|
|
|
|
const unsafePlatformAuditNotificationLocalhost = runReadiness(`
|
|
NODE_ENV=production
|
|
DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku
|
|
CORS_ORIGIN=https://student.gongxue100.com
|
|
AUTH_SMS_PROVIDER=aliyun
|
|
AUTH_CODE_PEPPER=${strongSecretA}
|
|
AUTH_SESSION_SECRET=${strongSecretB}
|
|
AUTH_JWT_JWKS_URL=https://auth.gongxue100.com/auth/v1/.well-known/jwks.json
|
|
AUTH_JWT_ISSUER=https://auth.gongxue100.com/auth/v1
|
|
ALLOW_LEGACY_AUTH_HEADERS=false
|
|
ALLOW_PLATFORM_ADMIN_KEY=false
|
|
PLATFORM_ADMIN_API_KEY=${strongSecretC}
|
|
STORAGE_DEFAULT_PROVIDER=aliyun_oss
|
|
STORAGE_DEFAULT_BUCKET=tiku-assets
|
|
STORAGE_REQUIRE_TENANT_PREFIX=true
|
|
ALIYUN_OSS_REGION=cn-hangzhou
|
|
ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
|
|
ALIYUN_OSS_ACCESS_KEY_ID=LTAI_READINESS_TEST_ONLY
|
|
ALIYUN_OSS_ACCESS_KEY_SECRET=aliyun-readiness-secret-placeholder
|
|
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.gongxue100.com/api/scan
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=s3cure-asset-scanner-token-2026-06-29-stuvwx
|
|
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
|
|
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=true
|
|
`);
|
|
|
|
assert.notEqual(unsafePlatformAuditNotificationLocalhost.status, 0, 'platform audit notification localhost readiness should fail');
|
|
assert.ok(
|
|
unsafePlatformAuditNotificationLocalhost.payload.checks?.some(item => item.id === 'env.worker_platform_audit_notification_insecure_localhost' && item.status === 'blocker'),
|
|
'readiness should block platform audit notification localhost mode in production',
|
|
);
|
|
|
|
const unsafePlatformDunningNotificationLocalhost = runReadiness(`
|
|
NODE_ENV=production
|
|
DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku
|
|
CORS_ORIGIN=https://student.gongxue100.com
|
|
AUTH_SMS_PROVIDER=aliyun
|
|
AUTH_CODE_PEPPER=${strongSecretA}
|
|
AUTH_SESSION_SECRET=${strongSecretB}
|
|
AUTH_JWT_JWKS_URL=https://auth.gongxue100.com/auth/v1/.well-known/jwks.json
|
|
AUTH_JWT_ISSUER=https://auth.gongxue100.com/auth/v1
|
|
ALLOW_LEGACY_AUTH_HEADERS=false
|
|
ALLOW_PLATFORM_ADMIN_KEY=false
|
|
PLATFORM_ADMIN_API_KEY=${strongSecretC}
|
|
STORAGE_DEFAULT_PROVIDER=aliyun_oss
|
|
STORAGE_DEFAULT_BUCKET=tiku-assets
|
|
STORAGE_REQUIRE_TENANT_PREFIX=true
|
|
ALIYUN_OSS_REGION=cn-hangzhou
|
|
ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
|
|
ALIYUN_OSS_ACCESS_KEY_ID=LTAI_READINESS_TEST_ONLY
|
|
ALIYUN_OSS_ACCESS_KEY_SECRET=aliyun-readiness-secret-placeholder
|
|
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.gongxue100.com/api/scan
|
|
WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=s3cure-asset-scanner-token-2026-06-29-stuvwx
|
|
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
|
|
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
|
|
WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=true
|
|
`);
|
|
|
|
assert.notEqual(unsafePlatformDunningNotificationLocalhost.status, 0, 'platform dunning notification localhost readiness should fail');
|
|
assert.ok(
|
|
unsafePlatformDunningNotificationLocalhost.payload.checks?.some(item => item.id === 'env.worker_platform_dunning_notification_insecure_localhost' && item.status === 'blocker'),
|
|
'readiness should block platform dunning notification localhost mode in production',
|
|
);
|
|
|
|
console.log('[PASS] production readiness check script');
|