| name | generate-run-tests |
| description | Generate and run unit tests for HorizonRAG (Vitest). Use when adding or changing server route logic, validation, SQL-helper wrappers, or React api.js/components, and you need fast, parameterized SQL-safe unit tests. Covers happy path plus validation/error cases, mocks the pg query helper and network, and never touches real secrets or the live database. |
generate-run-tests — unit tests with Vitest
Generate and run fast, isolated unit tests for new or changed logic. Tests must
not hit the real database, Azure OpenAI, or read infra/.env.local. Mock the
boundaries instead.
When to use
- You added/changed an Express route or its validation in
app/server/src/routes/.
- You added/changed a wrapper in
app/web/src/api.js or a React component.
- You changed any helper with branching logic.
Setup (once per package, only if missing)
Check package.json for a test script. If absent, add Vitest:
# server
cd app/server; npm install -D vitest
# web (jsdom for component/DOM tests)
cd app/web; npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom
Add to the package's package.json scripts:
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
For web, ensure vite.config.js has a test env:
export default defineConfig({
plugins: [react()],
test: { environment: 'jsdom', globals: true },
});
Conventions
- Put tests next to the source as
*.test.js / *.test.jsx, or under __tests__/.
- Never import real credentials or call the live DB/OpenAI. Mock
../db.js
(the query helper) and fetch.
- Cover at minimum: one happy path and one validation/error path (empty
body/question, bad k, 404, etc.) — these mirror the contract in
Template.md §6.
- Assert on status codes and the
{ error: "..." } shape, not on stack traces.
- Keep tests small and independent; no shared mutable state between cases.
Pattern — server route (mock the query helper)
import { describe, it, expect, vi, beforeEach } from 'vitest';
vi.mock('../db.js', () => ({ query: vi.fn() }));
import { query } from '../db.js';
function makeRes() {
const res = {};
res.status = vi.fn(() => res);
res.json = vi.fn(() => res);
res.end = vi.fn(() => res);
return res;
}
describe('POST /documents', () => {
beforeEach(() => vi.clearAllMocks());
it('400s when body is empty', async () => {
const res = makeRes();
await createDocumentHandler({ body: { body: ' ' } }, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({ error: 'body is required' });
expect(query).not.toHaveBeenCalled();
});
it('201s with the created row on valid input', async () => {
query.mockResolvedValue({ rows: [{ id: 1, body: 'hi', embedded: true }] });
const res = makeRes();
await createDocumentHandler({ body: { body: 'hi' } }, res);
expect(res.status).toHaveBeenCalledWith(201);
expect(query.mock.calls[0][1]).toEqual(['hi', null, null, null]);
});
});
If a handler is defined inline on the router, export the handler function (or a
small createDocument(req,res) helper) so it can be unit-tested directly. Keep
the refactor minimal and in-style.
Pattern — client api.js (mock fetch)
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { api } from './api.js';
beforeEach(() => { global.fetch = vi.fn(); });
it('throws the server error message on non-ok', async () => {
global.fetch.mockResolvedValue({
ok: false, status: 400,
json: async () => ({ error: 'body is required' }),
});
await expect(api.createDocument({})).rejects.toThrow('body is required');
});
Run
cd app/server; npm test # or: cd app/web; npm test
Done when
- New/changed logic has at least a happy-path and an error-path test.
npm test passes in the affected package(s).
- No test reads secrets, hits the live DB/OpenAI, or interpolates user input into SQL.