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: { accessCookieName: 'genarrative_access_session', accessCookieTtlSeconds: 7200, accessCookieSecure: false, accessCookieSameSite: 'Lax', accessCookiePath: '/', 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', '20260413_009_custom_world_sessions', '20260414_010_custom_world_gallery_metadata', '20260416_011_profile_dashboard_tables', '20260416_012_user_browse_history', '20260417_013_custom_world_profile_soft_delete', '20260419_014_profile_save_archives', '20260419_015_runtime_settings_platform_theme', ], ); 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', 'custom_world_sessions', 'profile_dashboard_state', 'profile_played_worlds', 'profile_save_archives', 'profile_wallet_ledger', 'save_snapshots', 'runtime_settings', 'user_browse_history', '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', 'custom_world_sessions', 'profile_dashboard_state', 'profile_played_worlds', 'profile_save_archives', 'profile_wallet_ledger', 'runtime_settings', 'save_snapshots', 'schema_migrations', 'sms_auth_events', 'user_browse_history', '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, ); });