fix: make test suite runnable without DB/Chrome, add tests to CI
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 12m28s

- Refactor index.ts to skip start() when NODE_ENV=test
- Add test setup with mocks for db, keys, browser, verification, email, usage
- Add vitest.config.ts with setup file
- Rewrite tests to work with mocks (42 tests, all passing)
- Add new tests: signup 410, recovery validation, CORS headers, error format, API root
- Add test step to CI pipeline before Docker build
This commit is contained in:
OpenClaw Bot 2026-02-25 07:07:12 +00:00
parent bc698b66b2
commit b95994cc3c
6 changed files with 327 additions and 156 deletions

91
src/__tests__/setup.ts Normal file
View file

@ -0,0 +1,91 @@
import { vi } from "vitest";
// Must be set before any imports
process.env.NODE_ENV = "test";
process.env.API_KEYS = "test-key";
// Mock database
vi.mock("../services/db.js", () => {
const mockPool = {
connect: vi.fn().mockResolvedValue({
query: vi.fn().mockResolvedValue({ rows: [{ version: "PostgreSQL 17.4" }], rowCount: 0 }),
release: vi.fn(),
}),
query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
end: vi.fn().mockResolvedValue(undefined),
on: vi.fn(),
totalCount: 10,
idleCount: 8,
waitingCount: 0,
};
return {
default: mockPool,
pool: mockPool,
initDatabase: vi.fn().mockResolvedValue(undefined),
queryWithRetry: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
connectWithRetry: vi.fn().mockResolvedValue({
query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
release: vi.fn(),
}),
cleanupStaleData: vi.fn().mockResolvedValue({ expiredVerifications: 0, staleKeys: 0, orphanedUsage: 0 }),
isTransientError: vi.fn().mockReturnValue(false),
};
});
// Mock keys service with in-memory store
vi.mock("../services/keys.js", () => {
const keys = [
{ key: "test-key", tier: "pro" as const, email: "test@docfast.dev", createdAt: new Date().toISOString() },
];
return {
loadKeys: vi.fn().mockResolvedValue(undefined),
isValidKey: vi.fn((k: string) => keys.some((e) => e.key === k)),
getKeyInfo: vi.fn((k: string) => keys.find((e) => e.key === k)),
isProKey: vi.fn((k: string) => keys.find((e) => e.key === k)?.tier === "pro"),
getAllKeys: vi.fn(() => [...keys]),
createFreeKey: vi.fn(),
createProKey: vi.fn(),
downgradeByCustomer: vi.fn(),
findKeyByCustomerId: vi.fn(),
updateKeyEmail: vi.fn(),
updateEmailByCustomer: vi.fn(),
};
});
// Mock browser service
vi.mock("../services/browser.js", () => ({
initBrowser: vi.fn().mockResolvedValue(undefined),
closeBrowser: vi.fn().mockResolvedValue(undefined),
renderPdf: vi.fn().mockResolvedValue(Buffer.from("%PDF-1.4 mock pdf content here")),
renderUrlPdf: vi.fn().mockResolvedValue(Buffer.from("%PDF-1.4 mock url pdf content")),
getPoolStats: vi.fn().mockReturnValue({
poolSize: 16,
totalPages: 16,
availablePages: 14,
queueDepth: 0,
pdfCount: 0,
restarting: false,
uptimeMs: 10000,
browsers: [],
}),
}));
// Mock verification service
vi.mock("../services/verification.js", () => ({
verifyToken: vi.fn().mockReturnValue({ status: "invalid" }),
loadVerifications: vi.fn().mockResolvedValue(undefined),
createPendingVerification: vi.fn().mockResolvedValue({ email: "test@test.com", code: "123456" }),
verifyCode: vi.fn().mockResolvedValue({ status: "ok" }),
}));
// Mock email service
vi.mock("../services/email.js", () => ({
sendVerificationEmail: vi.fn().mockResolvedValue(undefined),
}));
// Mock usage middleware
vi.mock("../middleware/usage.js", () => ({
usageMiddleware: vi.fn((_req: any, _res: any, next: any) => next()),
loadUsageData: vi.fn().mockResolvedValue(undefined),
getUsageStats: vi.fn().mockReturnValue({ totalRequests: 0, keys: {} }),
}));