Set up Vitest for both server (Node + Supertest) and client (jsdom + React Testing Library). Extract Express app into app.js for testability. Add example tests covering bills validation, bills route CRUD, ThemeContext, and App nav rendering. Update CLAUDE.md with testing docs and requirement to write tests with features. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
134 lines
3.7 KiB
JavaScript
134 lines
3.7 KiB
JavaScript
import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest';
|
|
import request from 'supertest';
|
|
|
|
// Import the real app and db — Pool is lazy, no connection until query()
|
|
const app = require('../app');
|
|
const db = require('../db');
|
|
|
|
// Replace pool.query with a mock — routes reference the same pool object
|
|
const originalQuery = db.pool.query;
|
|
db.pool.query = vi.fn();
|
|
|
|
afterAll(() => {
|
|
db.pool.query = originalQuery;
|
|
});
|
|
|
|
describe('GET /api/bills', () => {
|
|
beforeEach(() => {
|
|
db.pool.query.mockReset();
|
|
});
|
|
|
|
it('returns all bills', async () => {
|
|
const mockBills = [
|
|
{ id: 1, name: 'Electric', amount: 150, due_day: 15, assigned_paycheck: 1 },
|
|
{ id: 2, name: 'Water', amount: 50, due_day: 20, assigned_paycheck: 2 },
|
|
];
|
|
db.pool.query.mockResolvedValue({ rows: mockBills });
|
|
|
|
const res = await request(app).get('/api/bills');
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual(mockBills);
|
|
});
|
|
|
|
it('returns 500 on db error', async () => {
|
|
db.pool.query.mockRejectedValue(new Error('DB error'));
|
|
|
|
const res = await request(app).get('/api/bills');
|
|
|
|
expect(res.status).toBe(500);
|
|
expect(res.body).toEqual({ error: 'Failed to fetch bills' });
|
|
});
|
|
});
|
|
|
|
describe('POST /api/bills', () => {
|
|
beforeEach(() => {
|
|
db.pool.query.mockReset();
|
|
});
|
|
|
|
it('creates a bill with valid data', async () => {
|
|
const newBill = { id: 1, name: 'Internet', amount: 80, due_day: 1, assigned_paycheck: 1, category: 'General', active: true, variable_amount: false };
|
|
db.pool.query.mockResolvedValue({ rows: [newBill] });
|
|
|
|
const res = await request(app)
|
|
.post('/api/bills')
|
|
.send({ name: 'Internet', amount: 80, due_day: 1, assigned_paycheck: 1 });
|
|
|
|
expect(res.status).toBe(201);
|
|
expect(res.body).toEqual(newBill);
|
|
});
|
|
|
|
it('returns 400 for invalid data', async () => {
|
|
const res = await request(app)
|
|
.post('/api/bills')
|
|
.send({ name: '', amount: 80, due_day: 1, assigned_paycheck: 1 });
|
|
|
|
expect(res.status).toBe(400);
|
|
expect(res.body).toEqual({ error: 'name is required' });
|
|
});
|
|
});
|
|
|
|
describe('GET /api/bills/:id', () => {
|
|
beforeEach(() => {
|
|
db.pool.query.mockReset();
|
|
});
|
|
|
|
it('returns a bill by id', async () => {
|
|
const bill = { id: 1, name: 'Electric', amount: 150 };
|
|
db.pool.query.mockResolvedValue({ rows: [bill] });
|
|
|
|
const res = await request(app).get('/api/bills/1');
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual(bill);
|
|
});
|
|
|
|
it('returns 404 when not found', async () => {
|
|
db.pool.query.mockResolvedValue({ rows: [] });
|
|
|
|
const res = await request(app).get('/api/bills/999');
|
|
|
|
expect(res.status).toBe(404);
|
|
expect(res.body).toEqual({ error: 'Bill not found' });
|
|
});
|
|
});
|
|
|
|
describe('DELETE /api/bills/:id', () => {
|
|
beforeEach(() => {
|
|
db.pool.query.mockReset();
|
|
});
|
|
|
|
it('deletes a bill', async () => {
|
|
db.pool.query.mockResolvedValue({ rows: [{ id: 1 }] });
|
|
|
|
const res = await request(app).delete('/api/bills/1');
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual({ deleted: true, id: 1 });
|
|
});
|
|
|
|
it('returns 404 when bill not found', async () => {
|
|
db.pool.query.mockResolvedValue({ rows: [] });
|
|
|
|
const res = await request(app).delete('/api/bills/999');
|
|
|
|
expect(res.status).toBe(404);
|
|
});
|
|
});
|
|
|
|
describe('PATCH /api/bills/:id/toggle', () => {
|
|
beforeEach(() => {
|
|
db.pool.query.mockReset();
|
|
});
|
|
|
|
it('toggles bill active status', async () => {
|
|
const toggled = { id: 1, name: 'Electric', active: false };
|
|
db.pool.query.mockResolvedValue({ rows: [toggled] });
|
|
|
|
const res = await request(app).patch('/api/bills/1/toggle');
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual(toggled);
|
|
});
|
|
});
|