Files
order_site/apps/backend/src/config/runtime-validation.test.ts
T

282 lines
6.7 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import {
RuntimeConfigValidationError,
assertRuntimeConfigValid,
validateRuntimeConfig,
} from './runtime-validation.js'
import { deepMerge } from './runtime-merge.js'
import type { RuntimeConfig } from '../types/runtime-config.js'
test('validateRuntimeConfig allows missing admin credentials outside production', () => {
const issues = validateRuntimeConfig(
createRuntimeConfig({
admin: {
sessionSecret: '',
defaultUsers: [],
},
}),
{
env: { NODE_ENV: 'development' },
},
)
assert.deepEqual(issues, [])
})
test('validateRuntimeConfig requires admin credentials in production', () => {
const issues = validateRuntimeConfig(
createRuntimeConfig({
admin: {
sessionSecret: '',
defaultUsers: [],
},
}),
{
env: { NODE_ENV: 'production' },
},
)
assert.deepEqual(
issues.map((issue) => issue.path),
['admin.sessionSecret', 'admin.defaultUsers'],
)
})
test('validateRuntimeConfig rejects unsafe production placeholders', () => {
const issues = validateRuntimeConfig(
createRuntimeConfig({
admin: {
sessionSecret: 'change-me-long-random-session-secret',
defaultUsers: [
{
username: 'admin',
password: 'change-me-admin-password',
role: 'admin',
},
],
},
}),
{
env: { NODE_ENV: 'production' },
},
)
assert.deepEqual(
issues.map((issue) => issue.path),
['admin.sessionSecret', 'admin.defaultUsers[0].password'],
)
})
test('validateRuntimeConfig accepts complete production config', () => {
const issues = validateRuntimeConfig(createRuntimeConfig(), {
env: { NODE_ENV: 'production' },
})
assert.deepEqual(issues, [])
})
test('validateRuntimeConfig rejects wildcard CORS in production', () => {
const issues = validateRuntimeConfig(createRuntimeConfig({ cors: { allowedOrigins: ['*'] } }), {
env: { NODE_ENV: 'production' },
})
assert.deepEqual(
issues.map((issue) => issue.path),
['cors.allowedOrigins'],
)
})
test('validateRuntimeConfig catches invalid numeric and url values', () => {
const issues = validateRuntimeConfig(
createRuntimeConfig({
server: {
port: 70000,
},
database: {
maxConnections: 0,
},
orders: {
claimBaseUrl: 'not-a-url',
tokenTtlHours: -1,
},
}),
{
env: { NODE_ENV: 'development' },
},
)
assert.deepEqual(
issues.map((issue) => issue.path),
['server.port', 'database.maxConnections', 'orders.tokenTtlHours', 'orders.claimBaseUrl'],
)
})
test('validateRuntimeConfig accepts zero claim token TTL for permanent links', () => {
const issues = validateRuntimeConfig(
createRuntimeConfig({
orders: {
tokenTtlHours: 0,
},
}),
{ env: { NODE_ENV: 'development' } },
)
assert.deepEqual(issues, [])
})
test('validateRuntimeConfig rejects unknown semi-auto connector mode', () => {
const issues = validateRuntimeConfig(
createRuntimeConfig({
semiAuto: {
defaultConnectorMode: 'remote',
},
}),
{ env: { NODE_ENV: 'development' } },
)
assert.deepEqual(
issues.map((issue) => issue.path),
['semiAuto.defaultConnectorMode'],
)
})
test('assertRuntimeConfigValid throws a readable validation error', () => {
assert.throws(
() => {
assertRuntimeConfigValid(
createRuntimeConfig({
server: {
port: 0,
},
}),
)
},
(error) => {
assert.equal(error instanceof RuntimeConfigValidationError, true)
assert.match((error as Error).message, /运行时配置校验失败/)
assert.match((error as Error).message, /server\.port/)
return true
},
)
})
function createRuntimeConfig(overrides: Record<string, unknown> = {}): RuntimeConfig {
return deepMerge(
{
server: {
port: 3000,
},
data: {
root: '/tmp/order-site',
},
logging: {
level: 'info',
integrationLevel: 'warn',
},
retention: {
costExceptionDays: 60,
},
database: {
url: 'postgres://postgres:postgres@postgres:5432/order_site',
ssl: false,
maxConnections: 10,
},
storage: {
mode: 'minio',
endpoint: 'http://minio:9000',
bucket: 'order-site',
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin',
region: 'us-east-1',
maxUploadSizeMb: 10,
oss: {
endpoint: '',
bucket: '',
accessKeyId: '',
secretAccessKey: '',
region: 'oss-cn-hangzhou',
},
},
orders: {
claimBaseUrl: 'https://order.example.test/#/claim',
tokenTtlHours: 24,
},
semiAuto: {
defaultConnectorMode: 'embedded',
connectorBaseUrl: '',
connectorApiKey: '',
connectorTimeoutMs: 15000,
huya: {
bindActId: 9271,
outerActId: '17096',
roomPid: '1199650619883',
},
},
admin: {
sessionSecret: '0123456789abcdef0123456789abcdef',
sessionIdleTtlHours: 24,
sessionAbsoluteTtlHours: 168,
defaultUsers: [
{
username: 'admin',
password: 'Qp7xT2mN9vR4',
role: 'admin',
},
],
},
platforms: {
ninetyone: {
userId: '',
secret: '',
version: '1.0',
shopId: '91kaquan',
shopName: '91卡券',
timestampToleranceSeconds: 600,
cardsEncoding: 'aes-256-ecb-base64',
},
kuaishouIndustry: {
appKey: '',
appSecret: '',
signSecret: '',
messageSecret: '',
provider: 'kuaishou-industry',
platform: 'kuaishou',
shopId: 'kuaishou-industry',
shopName: '快手行业电子凭证',
version: '1',
},
affiliateDash: {
enabled: true,
baseUrl: '',
appKey: '',
appSecret: '',
callbackSecret: '',
timeoutMs: 10000,
notifyUrl: '',
timestampToleranceSeconds: 300,
preferredMatchEnabled: true,
skuMapping: {},
},
},
worker: {
sms: {
provider: 'mock',
accessKeyId: '',
accessKeySecret: '',
signName: '',
templateCode: '',
codeTtlSeconds: 300,
cooldownSeconds: 60,
dailyLimit: 10,
},
orderTimelineVisible: false,
sessionIdleTtlHours: 168,
sessionAbsoluteTtlHours: 720,
},
},
overrides,
) as RuntimeConfig
}