1
This commit is contained in:
163
server-node/src/db.test.ts
Normal file
163
server-node/src/db.test.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import type { AppConfig } from './config.js';
|
||||
import { createDatabase, listAppliedMigrations } from './db.js';
|
||||
|
||||
function createTestConfig(databaseUrl: string): AppConfig {
|
||||
const projectRoot = path.resolve(process.cwd(), '..');
|
||||
|
||||
return {
|
||||
nodeEnv: 'test',
|
||||
projectRoot,
|
||||
publicDir: path.join(projectRoot, 'public'),
|
||||
logsDir: path.join(projectRoot, 'server-node', 'logs'),
|
||||
dataDir: path.join(projectRoot, 'server-node', 'data'),
|
||||
rawEnv: {},
|
||||
databaseUrl,
|
||||
serverAddr: ':0',
|
||||
logLevel: 'silent',
|
||||
editorApiEnabled: true,
|
||||
assetsApiEnabled: true,
|
||||
jwtSecret: 'test-secret',
|
||||
jwtExpiresIn: '7d',
|
||||
jwtIssuer: 'genarrative-server-node-test',
|
||||
llm: {
|
||||
baseUrl: 'https://example.invalid',
|
||||
apiKey: '',
|
||||
model: 'test-model',
|
||||
},
|
||||
dashScope: {
|
||||
baseUrl: 'https://example.invalid',
|
||||
apiKey: '',
|
||||
imageModel: 'test-image-model',
|
||||
requestTimeoutMs: 1000,
|
||||
},
|
||||
smsAuth: {
|
||||
enabled: true,
|
||||
provider: 'mock',
|
||||
endpoint: 'dypnsapi.aliyuncs.com',
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
signName: 'Test Sign',
|
||||
templateCode: '100001',
|
||||
templateParamKey: 'code',
|
||||
countryCode: '86',
|
||||
schemeName: '',
|
||||
codeLength: 6,
|
||||
codeType: 1,
|
||||
validTimeSeconds: 300,
|
||||
intervalSeconds: 60,
|
||||
duplicatePolicy: 1,
|
||||
caseAuthPolicy: 1,
|
||||
returnVerifyCode: false,
|
||||
mockVerifyCode: '123456',
|
||||
maxSendPerPhonePerDay: 20,
|
||||
maxSendPerIpPerHour: 30,
|
||||
maxVerifyFailuresPerPhonePerHour: 12,
|
||||
maxVerifyFailuresPerIpPerHour: 24,
|
||||
captchaTtlSeconds: 180,
|
||||
captchaTriggerVerifyFailuresPerPhone: 3,
|
||||
captchaTriggerVerifyFailuresPerIp: 5,
|
||||
blockPhoneFailureThreshold: 6,
|
||||
blockIpFailureThreshold: 10,
|
||||
blockPhoneDurationMinutes: 30,
|
||||
blockIpDurationMinutes: 30,
|
||||
},
|
||||
wechatAuth: {
|
||||
enabled: true,
|
||||
provider: 'mock',
|
||||
appId: '',
|
||||
appSecret: '',
|
||||
authorizeEndpoint: 'https://open.weixin.qq.com/connect/qrconnect',
|
||||
accessTokenEndpoint: 'https://api.weixin.qq.com/sns/oauth2/access_token',
|
||||
userInfoEndpoint: 'https://api.weixin.qq.com/sns/userinfo',
|
||||
callbackPath: '/api/auth/wechat/callback',
|
||||
defaultRedirectPath: '/',
|
||||
mockUserId: 'mock_wechat_user',
|
||||
mockUnionId: 'mock_wechat_union',
|
||||
mockDisplayName: '微信旅人',
|
||||
mockAvatarUrl: '',
|
||||
},
|
||||
authSession: {
|
||||
refreshCookieName: 'genarrative_refresh_session',
|
||||
refreshSessionTtlDays: 30,
|
||||
refreshCookieSecure: false,
|
||||
refreshCookieSameSite: 'Lax',
|
||||
refreshCookiePath: '/api/auth',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('createDatabase applies runtime baseline migrations for pg-mem', async () => {
|
||||
const db = await createDatabase(
|
||||
createTestConfig('pg-mem://genarrative-db-test'),
|
||||
);
|
||||
|
||||
try {
|
||||
const migrations = await listAppliedMigrations(db);
|
||||
assert.deepEqual(
|
||||
migrations.map((migration) => migration.id),
|
||||
[
|
||||
'20260408_001_runtime_postgres_baseline',
|
||||
'20260408_002_allow_null_current_story_snapshot',
|
||||
'20260409_003_phone_auth_user_extensions',
|
||||
'20260409_004_auth_identities_and_account_status',
|
||||
'20260409_005_user_sessions',
|
||||
'20260409_006_auth_audit_logs',
|
||||
'20260409_007_sms_auth_events',
|
||||
'20260409_008_auth_risk_blocks',
|
||||
],
|
||||
);
|
||||
|
||||
const tablesResult = await db.query<{ tableName: string }>(
|
||||
`SELECT table_name AS "tableName"
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name IN (
|
||||
'schema_migrations',
|
||||
'users',
|
||||
'auth_identities',
|
||||
'auth_audit_logs',
|
||||
'auth_risk_blocks',
|
||||
'sms_auth_events',
|
||||
'user_sessions',
|
||||
'save_snapshots',
|
||||
'runtime_settings',
|
||||
'custom_world_profiles'
|
||||
)
|
||||
ORDER BY table_name`,
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
tablesResult.rows.map((row) => row.tableName),
|
||||
[
|
||||
'auth_audit_logs',
|
||||
'auth_identities',
|
||||
'auth_risk_blocks',
|
||||
'custom_world_profiles',
|
||||
'runtime_settings',
|
||||
'save_snapshots',
|
||||
'schema_migrations',
|
||||
'sms_auth_events',
|
||||
'user_sessions',
|
||||
'users',
|
||||
],
|
||||
);
|
||||
} finally {
|
||||
await db.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('createDatabase rejects non-postgresql database urls', async () => {
|
||||
await assert.rejects(
|
||||
() =>
|
||||
createDatabase(
|
||||
createTestConfig(
|
||||
'mysql://root:root@127.0.0.1:3306/genarrative',
|
||||
),
|
||||
),
|
||||
/DATABASE_URL 只支持 PostgreSQL 连接串或 pg-mem 测试连接/u,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user