CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl-labs/api-testing-first-steps

Get test coverage on an Express/Node API fast — the first 5 tests that catch

94

1.26x
Quality

90%

Does it follow best practices?

Impact

100%

1.26x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

task.mdevals/scenario-5/

Add First Tests to a Maintenance-Heavy Support Ticket API

Problem/Feature Description

You're onboarding at a company maintaining a Node/Express support ticket management system. The API has been running for over a year with no tests. The team wants to add a test suite but there's limited time — they need you to focus on the routes most likely to contain bugs, not to cover everything.

Your tech lead has pulled the git commit history and shared the output with you (see Input Files below). They also noted that the /api/tickets route handles ticket creation and status transitions, which directly affects billing since closed tickets stop the SLA clock. The /api/agents and /api/config endpoints were set up at launch and have barely been touched.

Your task is to set up testing infrastructure and write the first focused test suite. Document your reasoning — the team wants to understand why you picked the routes you did.

Output Specification

Produce the following files in your working directory:

  • package.json — updated with testing dependencies and all required scripts
  • vitest.config.ts — vitest configuration
  • src/server.ts — updated server file (exportable for testing)
  • src/db.ts — database module with test isolation
  • __tests__/api.test.ts — focused test file
  • testing-strategy.md — explanation of which routes were chosen and why, what was left out, and the testing approach used

Input Files

The following files are provided as inputs. Extract them before beginning.

=============== FILE: git-log.txt =============== commit a9f3b12 2024-11-14 src/routes/tickets.ts commit 88e2c41 2024-11-12 src/routes/tickets.ts commit 7d4a09e 2024-11-10 src/routes/tickets.ts src/middleware/auth.ts commit 6e1bc34 2024-11-08 src/routes/tickets.ts commit 5c9d2af 2024-11-05 src/routes/tickets.ts src/routes/comments.ts commit 4b8e1cd 2024-11-03 src/routes/comments.ts commit 3a7f0bc 2024-10-31 src/routes/tickets.ts commit 2d6e9ab 2024-10-28 src/routes/comments.ts src/routes/tickets.ts commit 1e5d8ba 2024-10-25 src/routes/tickets.ts commit 0f4c7a9 2024-10-22 src/routes/comments.ts commit 9e3b6f8 2024-09-15 src/routes/agents.ts src/routes/config.ts commit 8d2a5e7 2024-08-01 src/routes/agents.ts commit 7c1f4d6 2024-07-12 src/routes/config.ts src/routes/agents.ts

=============== FILE: src/server.ts =============== import express from 'express'; import Database from 'better-sqlite3';

const db = new Database('./support.db');

db.exec( CREATE TABLE IF NOT EXISTS tickets ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, status TEXT DEFAULT 'open' ); CREATE TABLE IF NOT EXISTS comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticket_id INTEGER NOT NULL, body TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS agents ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL ););

const app = express(); app.use(express.json());

// Tickets — high churn, handles SLA-critical status app.get('/api/tickets', (req, res) => { const rows = db.prepare('SELECT * FROM tickets').all(); res.json({ data: rows }); });

app.post('/api/tickets', (req, res) => { const { title } = req.body; if (!title) return res.status(400).json({ error: { message: 'title is required' } }); const result = db.prepare('INSERT INTO tickets (title) VALUES (?)').run(title); const ticket = db.prepare('SELECT * FROM tickets WHERE id = ?').get(result.lastInsertRowid); res.status(201).json({ data: ticket }); });

app.patch('/api/tickets/:id/status', (req, res) => { const { status } = req.body; const valid = ['open', 'in_progress', 'closed']; if (!valid.includes(status)) { return res.status(400).json({ error: { message: status must be one of: ${valid.join(', ')} } }); } const result = db.prepare('UPDATE tickets SET status = ? WHERE id = ?').run(status, req.params.id); if (result.changes === 0) return res.status(404).json({ error: { message: 'Ticket not found' } }); const ticket = db.prepare('SELECT * FROM tickets WHERE id = ?').get(req.params.id); res.json({ data: ticket }); });

// Comments — moderate churn app.post('/api/comments', (req, res) => { const { ticket_id, body } = req.body; if (!ticket_id || !body) return res.status(400).json({ error: { message: 'ticket_id and body are required' } }); const result = db.prepare('INSERT INTO comments (ticket_id, body) VALUES (?, ?)').run(ticket_id, body); const comment = db.prepare('SELECT * FROM comments WHERE id = ?').get(result.lastInsertRowid); res.status(201).json({ data: comment }); });

// Agents — rarely changes, low risk app.get('/api/agents', (req, res) => { const rows = db.prepare('SELECT * FROM agents').all(); res.json({ data: rows }); });

// Config — static, never changes app.get('/api/config', (req, res) => { res.json({ data: { version: '1.4.2', max_tickets_per_agent: 50 } }); });

const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log(Support API on port ${PORT}));

=============== FILE: package.json =============== { "name": "support-api", "version": "1.0.0", "type": "module", "scripts": { "start": "node src/server.ts" }, "dependencies": { "express": "^4.18.2", "better-sqlite3": "^9.4.3" }, "devDependencies": { "@types/express": "^4.17.21", "@types/better-sqlite3": "^7.6.8" } }

evals

tile.json