Files
Genarrative/server-node/src/db.test.ts

186 lines
5.5 KiB
TypeScript

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,
);
});