From eade116afc510d3648c57850ccf555017e256710 Mon Sep 17 00:00:00 2001 From: Hyder Alkasimi Date: Sun, 27 Sep 2026 21:27:03 -0500 Subject: [PATCH] Add completed task filtering Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- sample-app/README.md | 3 +-- sample-app/src/app.test.ts | 20 ++++++++++++++++++++ sample-app/src/tasks/routes.ts | 11 ++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/sample-app/README.md b/sample-app/README.md index 1d5a4fe..ad296a2 100644 --- a/sample-app/README.md +++ b/sample-app/README.md @@ -22,7 +22,7 @@ npm start # run the compiled server | Method | Path | Description | | --- | --- | --- | | `GET` | `/health` | Liveness check → `{ "status": "ok" }` | -| `GET` | `/tasks` | List all tasks | +| `GET` | `/tasks` | List all tasks; use `?completed=true` or `?completed=false` to filter | | `POST` | `/tasks` | Create a task from `{ "title": "..." }` | | `GET` | `/tasks/:id` | Get one task (404 if missing) | | `PATCH` | `/tasks/:id` | Set `{ "completed": true \| false }` | @@ -36,7 +36,6 @@ These are the hooks for the [exercises](../exercises/README.md). Don't "fix" the 1. `POST /tasks` does **no input validation**. 2. There is **no `DELETE /tasks/:id`** (and `TaskStore.remove()` is missing). 3. `setCompleted` never refreshes `updatedAt` — a subtle bug. -4. `GET /tasks` has **no `?completed=` filter**. ## Layout diff --git a/sample-app/src/app.test.ts b/sample-app/src/app.test.ts index d7263fd..9f1803b 100644 --- a/sample-app/src/app.test.ts +++ b/sample-app/src/app.test.ts @@ -35,6 +35,26 @@ describe('Task API', () => { expect(patched.body.completed).toBe(true); }); + it('lists only completed tasks when filtered', async () => { + const app = createApp(); + const completed = await request(app).post('/tasks').send({ title: 'Done' }); + await request(app).post('/tasks').send({ title: 'Not done' }); + await request(app).patch(`/tasks/${completed.body.id}`).send({ completed: true }); + + const res = await request(app).get('/tasks?completed=true'); + + expect(res.status).toBe(200); + expect(res.body).toHaveLength(1); + expect(res.body[0].title).toBe('Done'); + }); + + it('rejects an invalid completed filter', async () => { + const res = await request(createApp()).get('/tasks?completed=maybe'); + + expect(res.status).toBe(400); + expect(res.body.error).toBe("completed must be 'true' or 'false'"); + }); + it('returns 404 for an unknown task', async () => { const res = await request(createApp()).get('/tasks/does-not-exist'); expect(res.status).toBe(404); diff --git a/sample-app/src/tasks/routes.ts b/sample-app/src/tasks/routes.ts index 9d4f799..73a6917 100644 --- a/sample-app/src/tasks/routes.ts +++ b/sample-app/src/tasks/routes.ts @@ -4,9 +4,14 @@ import type { TaskStore } from './store.js'; export function createTaskRouter(store: TaskStore): Router { const router = Router(); - // Exercise 4: support filtering with GET /tasks?completed=true|false - router.get('/', (_req, res) => { - res.json(store.list()); + router.get('/', (req, res) => { + const { completed } = req.query; + if (completed === undefined) return res.json(store.list()); + if (completed !== 'true' && completed !== 'false') { + return res.status(400).json({ error: "completed must be 'true' or 'false'" }); + } + const want = completed === 'true'; + res.json(store.list().filter((task) => task.completed === want)); }); router.post('/', (req, res) => {