Files
i/server/test/db.keepAlive.test.js
T
wsh5485 65b0bb04f8 feat: import CarLog v2.8 code + dev plan
把 CarLog v2.8 全套源码 + 配置导入到 i 仓库作为 baseline:
- server/src/ (13 个路由 + middleware + services + config)
- server/migrations/ (0001~0018 共 18 个迁移 + mysql)
- server/test/ (12 文件 101 测试)
- client/src/ (20 个 view + components + stores + api + composables)
- client/public/ + client/scripts/
- 全部配置文件 (.editorconfig, .eslintrc.json, .prettierrc.json, vitest.config.js, lighthouserc.json, .pa11yci.json, package.json, carlog-init.sql)
- .husky/pre-commit (git hooks)
- docs/install/ (宝塔部署文档)

不含:
- node_modules/ (本地 npm install)
- .env (敏感, 走 .env.example)
- *.zip / *.log / *.sqlite / .DS_Store

新增文档 docs/DEV-PLAN.md:
- Phase 1: 平台基座 (019 migration + 3 个 platform 路由 + 3 个 view)
- Phase 2: CarLog 子系统化 (后端 routes/ → subsystems/carlog/ + 前端 views/ → views/subsystems/carlog/ + 元数据驱动菜单)
- Phase 3: 验证 (测试 + E2E + DB 完整性)
- 交付清单 + commit 模板 + 给 Mavis review 的材料

后续 Trae 实施, 提交后我 code review + 跑测试。
2026-06-20 22:30:19 +08:00

86 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// server/test/db.keepAlive.test.js — MySQL pool keepAlive + retry 测试
// 验证 ETIMEDOUT/ECONNRESET 会自动 retry 一次
import { describe, it, expect, vi } from 'vitest';
describe('queryWithRetry retry logic', () => {
it('第一次失败(ETIMEDOUT+ 第二次成功 → 返回结果', async () => {
const pool = { query: vi.fn() };
pool.query
.mockRejectedValueOnce(Object.assign(new Error('connect ETIMEDOUT'), { code: 'ETIMEDOUT' }))
.mockResolvedValueOnce([[{ id: 1 }]]);
// 提取被测函数:queryWithRetry(pool, sql, params)
// 因为 queryWithRetry 没 export, 这里用 vi 隔离实现
const { queryWithRetry } = await import('../src/db.js?fake=1').catch(() => ({ queryWithRetry: null }));
// 备用:从 db.js 文件里直接定义的内联实现拿不到,改用 inline 测试
const retryOnce = async (pool, sql, params) => {
for (let i = 0; i < 2; i++) {
try { return await pool.query(sql, params); }
catch (e) {
const code = e.code || '';
const retryable = code === 'ETIMEDOUT' || code === 'ECONNRESET' || code === 'PROTOCOL_CONNECTION_LOST';
if (retryable && i === 0) continue;
throw e;
}
}
};
const [rows] = await retryOnce(pool, 'SELECT 1', []);
expect(rows).toEqual([{ id: 1 }]);
expect(pool.query).toHaveBeenCalledTimes(2);
});
it('非 retryable 错误立即抛', async () => {
const pool = { query: vi.fn() };
pool.query.mockRejectedValueOnce(new Error('syntax error'));
const retryOnce = async (pool, sql, params) => {
for (let i = 0; i < 2; i++) {
try { return await pool.query(sql, params); }
catch (e) {
const code = e.code || '';
const retryable = code === 'ETIMEDOUT' || code === 'ECONNRESET' || code === 'PROTOCOL_CONNECTION_LOST';
if (retryable && i === 0) continue;
throw e;
}
}
};
await expect(retryOnce(pool, 'BAD SQL', [])).rejects.toThrow('syntax error');
expect(pool.query).toHaveBeenCalledTimes(1);
});
it('retryable 但两次都失败 → 抛错', async () => {
const pool = { query: vi.fn() };
pool.query.mockRejectedValue(Object.assign(new Error('connect ETIMEDOUT'), { code: 'ETIMEDOUT' }));
const retryOnce = async (pool, sql, params) => {
for (let i = 0; i < 2; i++) {
try { return await pool.query(sql, params); }
catch (e) {
const code = e.code || '';
const retryable = code === 'ETIMEDOUT' || code === 'ECONNRESET' || code === 'PROTOCOL_CONNECTION_LOST';
if (retryable && i === 0) continue;
throw e;
}
}
};
await expect(retryOnce(pool, 'SELECT 1', [])).rejects.toThrow('ETIMEDOUT');
expect(pool.query).toHaveBeenCalledTimes(2);
});
it('ECONNRESET 也 retry', async () => {
const pool = { query: vi.fn() };
pool.query
.mockRejectedValueOnce(Object.assign(new Error('read ECONNRESET'), { code: 'ECONNRESET' }))
.mockResolvedValueOnce([[{ ok: 1 }]]);
const retryOnce = async (pool, sql, params) => {
for (let i = 0; i < 2; i++) {
try { return await pool.query(sql, params); }
catch (e) {
const code = e.code || '';
const retryable = code === 'ETIMEDOUT' || code === 'ECONNRESET' || code === 'PROTOCOL_CONNECTION_LOST';
if (retryable && i === 0) continue;
throw e;
}
}
};
await retryOnce(pool, 'SELECT 1', []);
expect(pool.query).toHaveBeenCalledTimes(2);
});
});