Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
3 changes: 3 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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' });

Expand Down
7 changes: 7 additions & 0 deletions src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}
}
17 changes: 17 additions & 0 deletions test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } });
Expand Down
16 changes: 16 additions & 0 deletions test/tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
});
Loading