282 lines
6.7 KiB
TypeScript
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
|
|
}
|