fix: add global error handler + try/catch in recover & email-change routes (BUG-112)
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 19m57s

This commit is contained in:
OpenClaw Subagent 2026-03-17 17:10:36 +01:00
parent 2dfb0ac784
commit a3bba8f0d5
6 changed files with 469 additions and 166 deletions

View file

@ -171,3 +171,97 @@ describe("POST /v1/email-change/verify", () => {
);
});
});
describe("POST /v1/email-change - Database failure handling", () => {
it("returns 500 when validateApiKey DB query fails", async () => {
const { queryWithRetry } = await import("../services/db.js");
vi.mocked(queryWithRetry).mockRejectedValue(new Error("Connection pool exhausted"));
const res = await request(app).post("/v1/email-change").send({
apiKey: "df_pro_xxx",
newEmail: "new@example.com"
});
expect(res.status).toBe(500);
expect(res.body).toEqual({ error: "Internal server error" });
});
it("returns 500 when email existence check fails", async () => {
const { queryWithRetry } = await import("../services/db.js");
let callCount = 0;
vi.mocked(queryWithRetry).mockImplementation((async (sql: string) => {
callCount++;
// First call (validateApiKey) succeeds
if (callCount === 1 && sql.includes("SELECT") && sql.includes("key =")) {
return { rows: [{ key: "df_pro_xxx", email: "old@example.com", tier: "pro" }], rowCount: 1 };
}
// Second call (email check) fails
throw new Error("DB connection lost");
}) as any);
const res = await request(app).post("/v1/email-change").send({
apiKey: "df_pro_xxx",
newEmail: "new@example.com"
});
expect(res.status).toBe(500);
expect(res.body).toEqual({ error: "Internal server error" });
});
it("returns 500 when createPendingVerification fails", async () => {
const { createPendingVerification } = await import("../services/verification.js");
vi.mocked(createPendingVerification).mockRejectedValue(new Error("DB insert failed"));
const res = await request(app).post("/v1/email-change").send({
apiKey: "df_pro_xxx",
newEmail: "new@example.com"
});
expect(res.status).toBe(500);
expect(res.body).toEqual({ error: "Internal server error" });
});
});
describe("POST /v1/email-change/verify - Database failure handling", () => {
it("returns 500 when validateApiKey DB query fails", async () => {
const { queryWithRetry } = await import("../services/db.js");
vi.mocked(queryWithRetry).mockRejectedValue(new Error("Connection timeout"));
const res = await request(app).post("/v1/email-change/verify").send({
apiKey: "df_pro_xxx",
newEmail: "new@example.com",
code: "123456"
});
expect(res.status).toBe(500);
expect(res.body).toEqual({ error: "Internal server error" });
});
it("returns 500 when UPDATE query fails", async () => {
const { queryWithRetry } = await import("../services/db.js");
let callCount = 0;
vi.mocked(queryWithRetry).mockImplementation((async (sql: string) => {
callCount++;
// First call (validateApiKey) succeeds
if (callCount === 1 && sql.includes("SELECT") && sql.includes("key =")) {
return { rows: [{ key: "df_pro_xxx", email: "old@example.com", tier: "pro" }], rowCount: 1 };
}
// Second call (UPDATE) fails
throw new Error("UPDATE failed - constraint violation");
}) as any);
const res = await request(app).post("/v1/email-change/verify").send({
apiKey: "df_pro_xxx",
newEmail: "new@example.com",
code: "123456"
});
expect(res.status).toBe(500);
expect(res.body).toEqual({ error: "Internal server error" });
});
});