Skip to content
Open
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
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Read **[ASSIGNMENT.md](./ASSIGNMENT.md)** for the full brief before you start.
You're welcome to use AI tools. What we're evaluating is your ability to read and reason about unfamiliar code — so your submission should reflect your own understanding, not just generated output.

Concretely:

- For each bug you report: include where in the code it lives and why it happens
- For the feature you implement: briefly explain the design decisions you made
- If something surprised you or you had to make a tradeoff, say so
Expand Down Expand Up @@ -42,6 +43,7 @@ npm run coverage # run with coverage report
task-api/
src/
app.js # Express app setup
server.js # Server startup (listen)
routes/tasks.js # Route handlers
services/taskService.js # Business logic + in-memory data store
utils/validators.js # Input validation helpers
Expand All @@ -57,15 +59,15 @@ ASSIGNMENT.md # Full brief — read this first

## API Reference

| Method | Path | Description |
|----------|---------------------------|------------------------------------------|
| `GET` | `/tasks` | List all tasks. Supports `?status=`, `?page=`, `?limit=` |
| `POST` | `/tasks` | Create a new task |
| `PUT` | `/tasks/:id` | Full update of a task |
| `DELETE` | `/tasks/:id` | Delete a task (returns 204) |
| `PATCH` | `/tasks/:id/complete` | Mark a task as complete |
| `GET` | `/tasks/stats` | Counts by status + overdue count |
| `PATCH` | `/tasks/:id/assign` | **Assign a task to a user** _(to implement)_ |
| Method | Path | Description |
| -------- | --------------------- | -------------------------------------------------------- |
| `GET` | `/tasks` | List all tasks. Supports `?status=`, `?page=`, `?limit=` |
| `POST` | `/tasks` | Create a new task |
| `PUT` | `/tasks/:id` | Full update of a task |
| `DELETE` | `/tasks/:id` | Delete a task (returns 204) |
| `PATCH` | `/tasks/:id/complete` | Mark a task as complete |
| `GET` | `/tasks/stats` | Counts by status + overdue count |
| `PATCH` | `/tasks/:id/assign` | Assign a task to a user |

### Task shape

Expand All @@ -74,9 +76,10 @@ ASSIGNMENT.md # Full brief — read this first
"id": "uuid",
"title": "string",
"description": "string",
"status": "pending | in-progress | completed",
"status": "todo | in_progress | done",
"priority": "low | medium | high",
"dueDate": "ISO 8601 or null",
"assignee": "string | null",
"completedAt": "ISO 8601 or null",
"createdAt": "ISO 8601"
}
Expand All @@ -85,18 +88,21 @@ ASSIGNMENT.md # Full brief — read this first
### Sample requests

**Create a task**

```bash
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Write tests", "priority": "high"}'
```

**List tasks with filter**

```bash
curl "http://localhost:3000/tasks?status=pending&page=1&limit=10"
curl "http://localhost:3000/tasks?status=todo&page=1&limit=10"
```

**Mark complete**

```bash
curl -X PATCH http://localhost:3000/tasks/<id>/complete
```
Expand Down
196 changes: 196 additions & 0 deletions coverage-report-index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for src</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../prettify.css" />
<link rel="stylesheet" href="../base.css" />
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
.coverage-summary .sorter {
background-image: url(../sort-arrow-sprite.png);
}
</style>
</head>

<body>
<div class="wrapper">
<div class="pad1">
<h1><a href="../index.html">All files</a> src</h1>
<div class="clearfix">
<div class="fl pad1y space-right2">
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class="fraction">13/13</span>
</div>

<div class="fl pad1y space-right2">
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class="fraction">2/2</span>
</div>

<div class="fl pad1y space-right2">
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class="fraction">2/2</span>
</div>

<div class="fl pad1y space-right2">
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class="fraction">13/13</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block,
<em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch" />
</div>
</template>
</div>
<div class="status-line high"></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">
File
</th>
<th
data-col="pic"
data-type="number"
data-fmt="html"
data-html="true"
class="pic"
></th>
<th
data-col="statements"
data-type="number"
data-fmt="pct"
class="pct"
>
Statements
</th>
<th
data-col="statements_raw"
data-type="number"
data-fmt="html"
class="abs"
></th>
<th
data-col="branches"
data-type="number"
data-fmt="pct"
class="pct"
>
Branches
</th>
<th
data-col="branches_raw"
data-type="number"
data-fmt="html"
class="abs"
></th>
<th
data-col="functions"
data-type="number"
data-fmt="pct"
class="pct"
>
Functions
</th>
<th
data-col="functions_raw"
data-type="number"
data-fmt="html"
class="abs"
></th>
<th
data-col="lines"
data-type="number"
data-fmt="pct"
class="pct"
>
Lines
</th>
<th
data-col="lines_raw"
data-type="number"
data-fmt="html"
class="abs"
></th>
</tr>
</thead>
<tbody>
<tr>
<td class="file high" data-value="app.js">
<a href="app.js.html">app.js</a>
</td>
<td data-value="100" class="pic high">
<div class="chart">
<div class="cover-fill cover-full" style="width: 100%"></div>
<div class="cover-empty" style="width: 0%"></div>
</div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="0" class="abs high">0/0</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="9" class="abs high">9/9</td>
</tr>

<tr>
<td class="file high" data-value="server.js">
<a href="server.js.html">server.js</a>
</td>
<td data-value="100" class="pic high">
<div class="chart">
<div class="cover-fill cover-full" style="width: 100%"></div>
<div class="cover-empty" style="width: 0%"></div>
</div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="4" class="abs high">4/4</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="2" class="abs high">2/2</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="4" class="abs high">4/4</td>
</tr>
</tbody>
</table>
</div>
<div class="push"></div>
<!-- for sticky footer -->
</div>
<!-- /wrapper -->
<div class="footer quiet pad2 space-top1 center small">
Code coverage generated by
<a
href="https://istanbul.js.org/"
target="_blank"
rel="noopener noreferrer"
>istanbul</a
>
at 2026-04-04T13:27:24.278Z
</div>
<script src="../prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="../sorter.js"></script>
<script src="../block-navigation.js"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions task-api/BUG_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Bug Report

## 1) Pagination returns the wrong page (fixed)

- **What should happen:** `/tasks?page=1&limit=2` should return the first two tasks. Page 2 should return the next two.
- **What happens now:** Page 1 starts too late, so it returns tasks 3-4.
- **How found:** Tests for pagination failed.
- **Where:** `task-api/src/services/taskService.js` in `getPaginated`.
- **Why:** It used `page * limit` instead of `(page - 1) * limit`.
- **Fix applied:** Use `(page - 1) * limit`.

## 2) Status filter matches part of a word (fixed)

- **What should happen:** `/tasks?status=todo` should only return tasks with `todo`.
- **What happens now:** `/tasks?status=do` would match both `todo` and `done`.
- **How found:** While writing tests for the status filter.
- **Where:** `task-api/src/services/taskService.js` in `getByStatus`.
- **Why:** It used `includes` instead of exact match.
- **Fix applied:** Use exact match only.

## 3) Completing a task resets priority (fixed)

- **What should happen:** A task should keep its priority when completed.
- **What happens now:** The priority is forced to `medium`.
- **How found:** While adding tests for completing a task.
- **Where:** `task-api/src/services/taskService.js` in `completeTask`.
- **Why:** It set `priority: 'medium'` every time.
- **Fix applied:** Do not change priority when completing.

## 4) README status values were wrong (fixed)

- **What should happen:** The README should match the real status values.
- **What happens now:** README listed different status names.
- **How found:** Comparing README with the code and tests.
- **Where:** `README.md` vs `task-api/src/utils/validators.js`.
- **Fix applied:** README now matches the real status values.
35 changes: 35 additions & 0 deletions task-api/COVERAGE_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Coverage Summary

Command used (works on this Windows setup):

```bash
node ./node_modules/jest/bin/jest.js --coverage --runInBand
```

Output:

```
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 96.42 | 100 | 100 |
src | 100 | 100 | 100 | 100 |
app.js | 100 | 100 | 100 | 100 |
server.js | 100 | 100 | 100 | 100 |
src/routes | 100 | 91.66 | 100 | 100 |
tasks.js | 100 | 91.66 | 100 | 100 | 20-21
src/services | 100 | 94.73 | 100 | 100 |
taskService.js | 100 | 94.73 | 100 | 100 | 22
src/utils | 100 | 100 | 100 | 100 |
validators.js | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------
PASS tests/server.test.js
PASS tests/tasks.api.test.js
PASS tests/taskService.test.js

Test Suites: 3 passed, 3 total
Tests: 27 passed, 27 total
Snapshots: 0 total
Time: 1.866 s, estimated 2 s
Ran all test suites.
```
24 changes: 24 additions & 0 deletions task-api/SUBMISSION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Submission Notes

## New assign route decisions

- `assignee` must be a real name (not empty). Extra spaces are trimmed.
- You can change the assignee later. The route always returns the updated task.
- The task stores `assignee` and starts as `null` when created.

## What I would test next

- Two requests at the same time (for example: update and complete).
- Very long titles/descriptions and very large `page`/`limit` values.
- Make sure `completedAt` is set when a task is set to `done` using `PUT`.

## Anything that surprised me

- The README used different status names than the code.
- Pagination was off by one because of the math.

## Questions before shipping

- Should `PUT /tasks/:id` replace the whole task, or only the fields sent?
- Should changing assignee be allowed, or should it fail if already assigned?
- Should we block moving from `done` back to `in_progress`?
Loading