From 3c52724a7057a24174089aa68fe4e6bd7317dda3 Mon Sep 17 00:00:00 2001 From: Kristian Date: Sun, 4 Oct 2026 15:05:26 +0200 Subject: [PATCH] Add GET /tasks/count, the number of tasks and how many are done SAMPLE-4 asks for an endpoint that says how many tasks there are. TaskList.count() returns { total, done }, and the server sends it as the body of GET /tasks/count. The route is matched before the id parsing, which would otherwise answer 404 for the segment 'count'. Tests cover the method and the endpoint, and the README's table and the route list in server.ts name the new endpoint. Co-Authored-By: Claude Opus 5.5 --- README.md | 1 + src/server.ts | 3 +++ src/tasks.ts | 7 +++++++ test/server.test.ts | 17 +++++++++++++++++ test/tasks.test.ts | 16 ++++++++++++++++ 5 files changed, 44 insertions(+) diff --git a/README.md b/README.md index eeda85e..79997e0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ and in an adopter's first run. It is small on purpose, so a job is cheap and fas | GET | `/health` | Says the server is up | | GET | `/tasks` | Lists every task | | POST | `/tasks` | Adds a task, body `{"title": "..."}` | +| GET | `/tasks/count` | Says how many tasks there are, and how many are done | | GET | `/tasks/:id` | Returns one task | | POST | `/tasks/:id/complete` | Marks a task done | | DELETE | `/tasks/:id` | Removes a task | diff --git a/src/server.ts b/src/server.ts index fce3baf..024a1cb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,6 +7,7 @@ import { TaskList, ValidationError } from './tasks.ts'; * GET /health is the server up * GET /tasks every task * POST /tasks add a task, body {"title": "..."} + * GET /tasks/count how many tasks, and how many are done * GET /tasks/:id one task * POST /tasks/:id/complete mark a task done * DELETE /tasks/:id remove a task @@ -35,6 +36,8 @@ async function handle(tasks: TaskList, request: IncomingMessage, response: Serve return send(response, 405, { error: 'method not allowed' }); } + if (parts.length === 2 && parts[1] === 'count' && method === 'GET') return send(response, 200, tasks.count()); + const id = Number(parts[1]); if (!Number.isInteger(id) || id < 1) return send(response, 404, { error: 'not found' }); diff --git a/src/tasks.ts b/src/tasks.ts index 491c652..e9e84c5 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -36,4 +36,11 @@ export class TaskList { remove(id: number): boolean { return this.tasks.delete(id); } + + /** How many tasks there are, and how many of them are done. */ + count(): { total: number; done: number } { + let done = 0; + for (const task of this.tasks.values()) if (task.done) done++; + return { total: this.tasks.size, done }; + } } diff --git a/test/server.test.ts b/test/server.test.ts index 8203b79..8aaa404 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -40,6 +40,23 @@ describe('the HTTP API', () => { assert.equal((await call('GET', `/tasks/${id}`)).status, 404); }); + it('counts the tasks, and the ones that are done', async () => { + const start = await call('GET', '/tasks/count'); + assert.equal(start.status, 200); + const { total, done } = start.json as { total: number; done: number }; + + const first = (await call('POST', '/tasks', { title: 'Count me' })).json!.id as number; + const second = (await call('POST', '/tasks', { title: 'Count me too' })).json!.id as number; + assert.deepEqual(await call('GET', '/tasks/count'), { status: 200, json: { total: total + 2, done } }); + + await call('POST', `/tasks/${first}/complete`); + assert.deepEqual(await call('GET', '/tasks/count'), { status: 200, json: { total: total + 2, done: done + 1 } }); + + await call('DELETE', `/tasks/${first}`); + await call('DELETE', `/tasks/${second}`); + assert.deepEqual(await call('GET', '/tasks/count'), { status: 200, json: { total, done } }); + }); + it('refuses a task without a title, and a body that is not JSON', async () => { assert.deepEqual(await call('POST', '/tasks', { title: '' }), { status: 400, json: { error: 'title must be text' } }); assert.deepEqual(await call('POST', '/tasks', 'not json'), { status: 400, json: { error: 'the body must be JSON' } }); diff --git a/test/tasks.test.ts b/test/tasks.test.ts index dd5bb48..85c072e 100644 --- a/test/tasks.test.ts +++ b/test/tasks.test.ts @@ -33,4 +33,20 @@ describe('the task list', () => { assert.equal(tasks.remove(id), false); assert.equal(tasks.get(id), undefined); }); + + it('counts the tasks, and the ones that are done', () => { + const tasks = new TaskList(); + assert.deepEqual(tasks.count(), { total: 0, done: 0 }); + + const { id } = tasks.add('Write the spec'); + tasks.add('Review it'); + tasks.add('Ship it'); + assert.deepEqual(tasks.count(), { total: 3, done: 0 }); + + tasks.complete(id); + assert.deepEqual(tasks.count(), { total: 3, done: 1 }); + + tasks.remove(id); + assert.deepEqual(tasks.count(), { total: 2, done: 0 }); + }); });