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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ what it changes for someone who uses the API. CI checks that it did.

## Unreleased

- `DELETE /tasks/done` removes every task that is done, and says how many it removed. (#6)
- `PATCH /tasks/:id` renames a task, body `{"title": "..."}`, and answers with the task. (#5) (#7)
- `GET /tasks/count` says how many tasks there are, and how many are done.
- CI checks that a pull request adds a line here that ends with its number. (#4)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and in an adopter's first run. It is small on purpose, so a job is cheap and fas
| 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 |
| DELETE | `/tasks/done` | Removes every task that is done, and says how many |
| GET | `/tasks/:id` | Returns one task |
| PATCH | `/tasks/:id` | Changes a task's title, body `{"title": "..."}` |
| POST | `/tasks/:id/complete` | Marks a task done |
Expand Down
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TaskList, ValidationError } from './tasks.ts';
* GET /tasks every task
* POST /tasks add a task, body {"title": "..."}
* GET /tasks/count how many tasks, and how many are done
* DELETE /tasks/done remove every task that is done, and say how many
* GET /tasks/:id one task
* PATCH /tasks/:id change a task's title, body {"title": "..."}
* POST /tasks/:id/complete mark a task done
Expand Down Expand Up @@ -38,6 +39,7 @@ async function handle(tasks: TaskList, request: IncomingMessage, response: Serve
}

if (parts.length === 2 && parts[1] === 'count' && method === 'GET') return send(response, 200, tasks.count());
if (parts.length === 2 && parts[1] === 'done' && method === 'DELETE') return send(response, 200, { removed: tasks.removeDone() });

const id = Number(parts[1]);
if (!Number.isInteger(id) || id < 1) return send(response, 404, { error: 'not found' });
Expand Down
9 changes: 9 additions & 0 deletions src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export class TaskList {
return this.tasks.delete(id);
}

/** Remove every task that is done. Returns how many it removed. */
removeDone(): number {
let removed = 0;
for (const task of this.tasks.values()) {
if (task.done && this.tasks.delete(task.id)) removed++;
}
return removed;
}

/** How many tasks there are, and how many of them are done. */
count(): { total: number; done: number } {
let done = 0;
Expand Down
26 changes: 26 additions & 0 deletions test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ describe('the HTTP API', () => {
assert.deepEqual(await call('GET', '/tasks/count'), { status: 200, json: { total, done } });
});

it('removes every task that is done, and keeps the rest', async () => {
await call('DELETE', '/tasks/done');
const first = (await call('POST', '/tasks', { title: 'Done one' })).json!.id as number;
const open = (await call('POST', '/tasks', { title: 'Still open' })).json!.id as number;
const third = (await call('POST', '/tasks', { title: 'Done two' })).json!.id as number;
await call('POST', `/tasks/${first}/complete`);
await call('POST', `/tasks/${third}/complete`);

assert.deepEqual(await call('DELETE', '/tasks/done'), { status: 200, json: { removed: 2 } });
assert.equal((await call('GET', `/tasks/${first}`)).status, 404);
assert.equal((await call('GET', `/tasks/${third}`)).status, 404);
assert.deepEqual(await call('GET', `/tasks/${open}`), { status: 200, json: { id: open, title: 'Still open', done: false } });

await call('DELETE', `/tasks/${open}`);
});

it('answers removed 0 when no task is done', async () => {
await call('DELETE', '/tasks/done');
const id = (await call('POST', '/tasks', { title: 'Not done yet' })).json!.id as number;

assert.deepEqual(await call('DELETE', '/tasks/done'), { status: 200, json: { removed: 0 } });
assert.deepEqual((await call('GET', `/tasks/${id}`)).json, { id, title: 'Not done yet', done: false });

await call('DELETE', `/tasks/${id}`);
});

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
27 changes: 27 additions & 0 deletions test/tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,33 @@ describe('the task list', () => {
assert.equal(tasks.get(id), undefined);
});

it('removes the tasks that are done, says how many, and keeps the rest', () => {
const tasks = new TaskList();
const first = tasks.add('Write the spec');
const second = tasks.add('Review it');
const third = tasks.add('Ship it');
tasks.complete(first.id);
tasks.complete(third.id);

assert.equal(tasks.removeDone(), 2);
assert.deepEqual(tasks.all(), [{ id: second.id, title: 'Review it', done: false }]);
assert.deepEqual(tasks.count(), { total: 1, done: 0 });
assert.equal(tasks.add('Celebrate').id, third.id + 1);
});

it('removes nothing when no task is done', () => {
const tasks = new TaskList();
assert.equal(tasks.removeDone(), 0);

const { id } = tasks.add('Write the spec');
assert.equal(tasks.removeDone(), 0);
assert.deepEqual(tasks.all(), [{ id, title: 'Write the spec', done: false }]);

tasks.complete(id);
assert.equal(tasks.removeDone(), 1);
assert.equal(tasks.removeDone(), 0);
});

it('counts the tasks, and the ones that are done', () => {
const tasks = new TaskList();
assert.deepEqual(tasks.count(), { total: 0, done: 0 });
Expand Down
Loading