186 lines
5.5 KiB
TypeScript
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,
|
|
);
|
|
});
|