From eb0d0165b7ad7d13e2c8c0b402b068a5bb01cbc6 Mon Sep 17 00:00:00 2001 From: hello674583 Date: Fri, 24 Jul 2026 23:24:49 +0530 Subject: [PATCH 1/2] assigment completed --- task-api/src/routes/tasks.js | 16 +- task-api/src/services/taskService.js | 15 +- task-api/src/utils/validators.js | 13 +- task-api/tests/task.routes.test.js | 274 +++++++++++++++++++++++++++ task-api/tests/taskService.test.js | 195 +++++++++++++++++++ 5 files changed, 508 insertions(+), 5 deletions(-) create mode 100644 task-api/tests/task.routes.test.js create mode 100644 task-api/tests/taskService.test.js diff --git a/task-api/src/routes/tasks.js b/task-api/src/routes/tasks.js index e8c370fe..95a1cf3e 100644 --- a/task-api/src/routes/tasks.js +++ b/task-api/src/routes/tasks.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); const taskService = require('../services/taskService'); -const { validateCreateTask, validateUpdateTask } = require('../utils/validators'); +const { validateAssignTask,validateCreateTask, validateUpdateTask } = require('../utils/validators'); router.get('/stats', (req, res) => { const stats = taskService.getStats(); @@ -69,4 +69,18 @@ router.patch('/:id/complete', (req, res) => { res.json(task); }); +router.patch('/:id/assign', (req, res) => { + const error = validateAssignTask(req.body); + if (error) { + return res.status(400).json({ error }); + } + + const task = taskService.assignTask(req.params.id, req.body.assignee); + if (!task) { + return res.status(404).json({ error: 'Task not found' }); + } + + res.json(task); +}); + module.exports = router; diff --git a/task-api/src/services/taskService.js b/task-api/src/services/taskService.js index f8e89189..0fc3f674 100644 --- a/task-api/src/services/taskService.js +++ b/task-api/src/services/taskService.js @@ -6,10 +6,10 @@ const getAll = () => [...tasks]; const findById = (id) => tasks.find((t) => t.id === id); -const getByStatus = (status) => tasks.filter((t) => t.status.includes(status)); +const getByStatus = (status) => tasks.filter((t) => t.status===status); const getPaginated = (page, limit) => { - const offset = page * limit; + const offset = (page - 1) * limit; return tasks.slice(offset, offset + limit); }; @@ -66,7 +66,6 @@ const completeTask = (id) => { const updated = { ...task, - priority: 'medium', status: 'done', completedAt: new Date().toISOString(), }; @@ -76,11 +75,21 @@ const completeTask = (id) => { return updated; }; +const assignTask = (id, assignee) => { + const task = findById(id); + + if (!task) return null; + + task.assignee = assignee; + + return task; +}; const _reset = () => { tasks = []; }; module.exports = { + assignTask, getAll, findById, getByStatus, diff --git a/task-api/src/utils/validators.js b/task-api/src/utils/validators.js index 1e908ff5..2535d3a0 100644 --- a/task-api/src/utils/validators.js +++ b/task-api/src/utils/validators.js @@ -1,6 +1,17 @@ const VALID_STATUSES = ['todo', 'in_progress', 'done']; const VALID_PRIORITIES = ['low', 'medium', 'high']; +const validateAssignTask = (body) => { + if ( + !body.assignee || + typeof body.assignee !== "string" || + body.assignee.trim() === "" + ) { + return "assignee is required and must be a non-empty string"; + } + + return null; +}; const validateCreateTask = (body) => { if (!body.title || typeof body.title !== 'string' || body.title.trim() === '') { return 'title is required and must be a non-empty string'; @@ -33,4 +44,4 @@ const validateUpdateTask = (body) => { return null; }; -module.exports = { validateCreateTask, validateUpdateTask }; +module.exports = { validateAssignTask, validateCreateTask, validateUpdateTask }; diff --git a/task-api/tests/task.routes.test.js b/task-api/tests/task.routes.test.js new file mode 100644 index 00000000..58cc5deb --- /dev/null +++ b/task-api/tests/task.routes.test.js @@ -0,0 +1,274 @@ +const request = require("supertest"); +const app = require("../src/app"); +const taskService = require("../src/services/taskService"); + +describe("Task API", () => { + beforeEach(() => { + taskService._reset(); + }); + + describe("POST /tasks", () => { + test("should create a task", async () => { + const res = await request(app) + .post("/tasks") + .send({ + title: "Learn Jest", + priority: "high", + }); + + expect(res.statusCode).toBe(201); + expect(res.body.title).toBe("Learn Jest"); + expect(res.body.priority).toBe("high"); + expect(res.body.id).toBeDefined(); + }); + + test("should reject empty title", async () => { + const res = await request(app) + .post("/tasks") + .send({ + title: "", + }); + + expect(res.statusCode).toBe(400); + }); + + test("should reject invalid priority", async () => { + const res = await request(app) + .post("/tasks") + .send({ + title: "Task", + priority: "urgent", + }); + + expect(res.statusCode).toBe(400); + }); + }); + + describe("GET /tasks", () => { + test("returns all tasks", async () => { + taskService.create({ title: "Task 1" }); + taskService.create({ title: "Task 2" }); + + const res = await request(app).get("/tasks"); + + expect(res.statusCode).toBe(200); + expect(res.body.length).toBe(2); + }); + + test("filters by status", async () => { + taskService.create({ + title: "Todo", + status: "todo", + }); + + taskService.create({ + title: "Done", + status: "done", + }); + + const res = await request(app) + .get("/tasks") + .query({ + status: "done", + }); + + expect(res.statusCode).toBe(200); + expect(res.body.length).toBe(1); + expect(res.body[0].status).toBe("done"); + }); + }); + + describe("PUT /tasks/:id", () => { + test("updates a task", async () => { + const task = taskService.create({ + title: "Old", + }); + + const res = await request(app) + .put(`/tasks/${task.id}`) + .send({ + title: "New", + }); + + expect(res.statusCode).toBe(200); + expect(res.body.title).toBe("New"); + }); + + test("returns 404", async () => { + const res = await request(app) + .put("/tasks/123") + .send({ + title: "New", + }); + + expect(res.statusCode).toBe(404); + }); + }); + + describe("DELETE /tasks/:id", () => { + test("deletes task", async () => { + const task = taskService.create({ + title: "Delete", + }); + + const res = await request(app) + .delete(`/tasks/${task.id}`); + + expect(res.statusCode).toBe(204); + }); + + test("returns 404", async () => { + const res = await request(app) + .delete("/tasks/unknown"); + + expect(res.statusCode).toBe(404); + }); + }); + + describe("PATCH /tasks/:id/complete", () => { + test("completes task", async () => { + const task = taskService.create({ + title: "Complete", + }); + + const res = await request(app) + .patch(`/tasks/${task.id}/complete`); + + expect(res.statusCode).toBe(200); + expect(res.body.status).toBe("done"); + expect(res.body.completedAt).toBeDefined(); + }); + + test("returns 404", async () => { + const res = await request(app) + .patch("/tasks/abc/complete"); + + expect(res.statusCode).toBe(404); + }); + }); + + describe("GET /tasks/stats", () => { + test("returns statistics", async () => { + taskService.create({ + title: "Todo", + status: "todo", + }); + + taskService.create({ + title: "Done", + status: "done", + }); + + const res = await request(app) + .get("/tasks/stats"); + + expect(res.statusCode).toBe(200); + expect(res.body.todo).toBe(1); + expect(res.body.done).toBe(1); + }); + }); + //pagination bug + test("GET /tasks?page=1&limit=2 returns first page", async () => { + taskService.create({ title: "A" }); + taskService.create({ title: "B" }); + taskService.create({ title: "C" }); + + const res = await request(app) + .get("/tasks?page=1&limit=2"); + + expect(res.statusCode).toBe(200); + expect(res.body).toHaveLength(2); + expect(res.body[0].title).toBe("A"); + }); + //priority bug + test("complete endpoint should preserve priority", async () => { + const task = taskService.create({ + title: "Important", + priority: "high", + }); + + const res = await request(app) + .patch(`/tasks/${task.id}/complete`); + + expect(res.body.priority).toBe("high"); + }); + describe("PATCH /tasks/:id/assign", () => { + + test("assigns a task", async () => { + + const task = taskService.create({ + title: "Build API", + }); + + const res = await request(app) + .patch(`/tasks/${task.id}/assign`) + .send({ + assignee: "Alice", + }); + + expect(res.statusCode).toBe(200); + expect(res.body.assignee).toBe("Alice"); + + }); + + test("returns 404 for missing task", async () => { + + const res = await request(app) + .patch("/tasks/123/assign") + .send({ + assignee: "Alice", + }); + + expect(res.statusCode).toBe(404); + + }); + + test("rejects empty assignee", async () => { + + const task = taskService.create({ + title: "Task", + }); + + const res = await request(app) + .patch(`/tasks/${task.id}/assign`) + .send({ + assignee: "", + }); + + expect(res.statusCode).toBe(400); + + }); + + }); + test("rejects invalid status", async () => { + const res = await request(app) + .post("/tasks") + .send({ + title: "Test", + status: "invalid", + }); + + expect(res.statusCode).toBe(400); +}); + test("rejects invalid dueDate", async () => { + const res = await request(app) + .post("/tasks") + .send({ + title: "Test", + dueDate: "not-a-date", + }); + + expect(res.statusCode).toBe(400); +}); +test("updates dueDate successfully", async () => { + const task = taskService.create({ title: "Task" }); + + const res = await request(app) + .put(`/tasks/${task.id}`) + .send({ + dueDate: "2026-12-31T00:00:00.000Z", + }); + + expect(res.statusCode).toBe(200); +}); +}); \ No newline at end of file diff --git a/task-api/tests/taskService.test.js b/task-api/tests/taskService.test.js new file mode 100644 index 00000000..113022ef --- /dev/null +++ b/task-api/tests/taskService.test.js @@ -0,0 +1,195 @@ +const taskService = require("../src/services/taskService"); + +describe("Task Service", () => { + beforeEach(() => { + taskService._reset(); + }); + + describe("create()", () => { + test("should create a task with default values", () => { + const task = taskService.create({ + title: "Learn Jest", + }); + + expect(task.id).toBeDefined(); + expect(task.title).toBe("Learn Jest"); + expect(task.status).toBe("todo"); + expect(task.priority).toBe("medium"); + expect(task.createdAt).toBeDefined(); + expect(task.completedAt).toBeNull(); + }); + + test("should create task with custom values", () => { + const task = taskService.create({ + title: "API", + status: "in_progress", + priority: "high", + }); + + expect(task.status).toBe("in_progress"); + expect(task.priority).toBe("high"); + }); + }); + + describe("getAll()", () => { + test("returns all tasks", () => { + taskService.create({ title: "One" }); + taskService.create({ title: "Two" }); + + expect(taskService.getAll()).toHaveLength(2); + }); + }); + + describe("findById()", () => { + test("finds existing task", () => { + const task = taskService.create({ title: "Test" }); + + expect(taskService.findById(task.id)).toEqual(task); + }); + + test("returns undefined for missing task", () => { + expect(taskService.findById("abc")).toBeUndefined(); + }); + }); + + describe("getByStatus()", () => { + test("filters by status", () => { + taskService.create({ title: "A", status: "todo" }); + taskService.create({ title: "B", status: "done" }); + + const tasks = taskService.getByStatus("done"); + + expect(tasks).toHaveLength(1); + expect(tasks[0].status).toBe("done"); + }); + }); + + describe("update()", () => { + test("updates task", () => { + const task = taskService.create({ title: "Old" }); + + const updated = taskService.update(task.id, { + title: "New", + }); + + expect(updated.title).toBe("New"); + }); + + test("returns null for invalid id", () => { + expect(taskService.update("bad-id", {})).toBeNull(); + }); + }); + + describe("remove()", () => { + test("removes task", () => { + const task = taskService.create({ + title: "Delete me", + }); + + expect(taskService.remove(task.id)).toBe(true); + expect(taskService.getAll()).toHaveLength(0); + }); + + test("returns false for missing task", () => { + expect(taskService.remove("bad")).toBe(false); + }); + }); + + describe("completeTask()", () => { + test("marks task complete", () => { + const task = taskService.create({ + title: "Finish", + }); + + const completed = taskService.completeTask(task.id); + + expect(completed.status).toBe("done"); + expect(completed.completedAt).not.toBeNull(); + }); + + test("returns null for invalid id", () => { + expect(taskService.completeTask("bad")).toBeNull(); + }); + }); + + describe("getStats()", () => { + test("counts tasks correctly", () => { + taskService.create({ + title: "A", + status: "todo", + }); + + taskService.create({ + title: "B", + status: "done", + }); + + const stats = taskService.getStats(); + + expect(stats.todo).toBe(1); + expect(stats.done).toBe(1); + expect(stats.in_progress).toBe(0); + }); + }); + + describe("getPaginated()", () => { + test("returns first page", () => { + taskService.create({ title: "1" }); + taskService.create({ title: "2" }); + taskService.create({ title: "3" }); + + const tasks = taskService.getPaginated(1, 2); + + expect(tasks).toHaveLength(2); + }); + }); + //pagination bug + test("page 1 should return first two tasks", () => { + taskService.create({ title: "A" }); + taskService.create({ title: "B" }); + taskService.create({ title: "C" }); + + const tasks = taskService.getPaginated(1, 2); + + expect(tasks[0].title).toBe("A"); + expect(tasks[1].title).toBe("B"); + }); + //priority should not change when completing a task + test("completing a task should not change priority", () => { + const task = taskService.create({ + title: "API", + priority: "high", + }); + + const completed = taskService.completeTask(task.id); + + expect(completed.priority).toBe("high"); + }); + describe("assignTask()", () => { + + test("assigns a user", () => { + + const task = taskService.create({ + title: "API", + }); + + const updated = taskService.assignTask( + task.id, + "Alice" + ); + + expect(updated.assignee).toBe("Alice"); + + }); + + test("returns null for invalid id", () => { + + expect( + taskService.assignTask("bad-id", "Alice") + ).toBeNull(); + + }); + +}); + +}); \ No newline at end of file From 7f7a995acf6f01c07b42b7610048861e79958fa6 Mon Sep 17 00:00:00 2001 From: hello674583 Date: Fri, 24 Jul 2026 23:33:08 +0530 Subject: [PATCH 2/2] assigment bug report --- BUG_REPORT.md | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 BUG_REPORT.md diff --git a/BUG_REPORT.md b/BUG_REPORT.md new file mode 100644 index 00000000..f688ba60 --- /dev/null +++ b/BUG_REPORT.md @@ -0,0 +1,387 @@ +# BUG_REPORT.md + +# Bug Report + +## Project + +**Task Manager API** + +--- + +# Bug 1 – Incorrect Pagination Offset + +## Severity + +**High** + +--- + +## Description + +The pagination logic in `taskService.js` calculated the starting index incorrectly. This caused the API to skip the first set of records whenever the first page was requested. + +--- + +## Location + +``` +src/services/taskService.js +``` + +Original implementation: + +```javascript +const getPaginated = (page, limit) => { + const offset = page * limit; + return tasks.slice(offset, offset + limit); +}; +``` + +--- + +## Expected Behaviour + +For the request + +``` +GET /tasks?page=1&limit=2 +``` + +the API should return: + +``` +Task A +Task B +``` + +which are the first two tasks. + +The formula should calculate: + +``` +offset = (page - 1) × limit +``` + +Example: + +``` +page = 1 +limit = 2 + +offset = (1 - 1) × 2 + +offset = 0 +``` + +Therefore the API should return + +``` +tasks[0] +tasks[1] +``` + +--- + +## Actual Behaviour + +The code calculated + +``` +offset = page × limit +``` + +Example + +``` +page = 1 +limit = 2 + +offset = 2 +``` + +Therefore the API returned + +``` +tasks[2] +tasks[3] +``` + +instead of the first page. + +The first two tasks were skipped completely. + +--- + +## Impact + +Users requesting the first page never receive the earliest tasks. + +Every page after that is shifted incorrectly, resulting in inconsistent pagination. + +Applications depending on pagination (front-end tables, infinite scrolling, mobile apps) would display incorrect data. + +--- + +## How the Bug Was Discovered + +A unit test was written for the pagination function. + +Test: + +```javascript +const tasks = taskService.getPaginated(1, 2); + +expect(tasks[0].title).toBe("A"); +expect(tasks[1].title).toBe("B"); +``` + +The test failed because the returned data began with later tasks instead of the expected first page. + +The same issue was reproduced through an integration test using + +``` +GET /tasks?page=1&limit=2 +``` + +--- + +## Root Cause + +Incorrect mathematical calculation of the pagination offset. + +The implementation assumed page numbers started from zero. + +The API, however, expects page numbering to start from one. + +--- + +## Fix + +Changed + +```javascript +const offset = page * limit; +``` + +to + +```javascript +const offset = (page - 1) * limit; +``` + +--- + +## Verification + +After the fix: + +* Unit tests passed. +* Integration tests passed. +* Pagination returned the expected tasks for page 1, page 2, and later pages. + +--- + +# Bug 2 – Completing a Task Resets Priority + +## Severity + +**Medium** + +--- + +## Description + +The `completeTask()` function unexpectedly modified the task priority when marking a task as completed. + +Completing a task should only update completion-related information. + +Priority should remain unchanged. + +--- + +## Location + +``` +src/services/taskService.js +``` + +Original code + +```javascript +const updated = { + ...task, + priority: "medium", + status: "done", + completedAt: new Date().toISOString(), +}; +``` + +--- + +## Expected Behaviour + +When a task is completed + +``` +PATCH /tasks/:id/complete +``` + +the API should update only + +``` +status + +completedAt +``` + +All other task properties should remain unchanged. + +Example + +Before + +```json +{ + "priority":"high", + "status":"todo" +} +``` + +After + +```json +{ + "priority":"high", + "status":"done" +} +``` + +--- + +## Actual Behaviour + +The API changed + +``` +priority = "high" +``` + +to + +``` +priority = "medium" +``` + +even though the request never asked to modify the priority. + +--- + +## Impact + +Users lose the original priority assigned to a task. + +Any workflow that relies on task priority becomes inaccurate. + +Historical information about task urgency is lost after completion. + +--- + +## How the Bug Was Discovered + +A unit test was written to ensure that completing a task does not modify unrelated fields. + +Test + +```javascript +const completed = taskService.completeTask(task.id); + +expect(completed.priority).toBe("high"); +``` + +Before fixing the bug, the test failed because the returned priority became + +``` +medium +``` + +instead of + +``` +high +``` + +--- + +## Root Cause + +The service manually assigned + +```javascript +priority: "medium" +``` + +during task completion. + +This assignment was unrelated to completing a task and unintentionally overwrote existing data. + +--- + +## Fix + +Removed + +```javascript +priority: "medium" +``` + +from the updated task object. + +The completion endpoint now modifies only + +``` +status + +completedAt +``` + +--- + +## Verification + +After the fix + +* Existing priority remains unchanged. +* Status changes to "done". +* completedAt is populated correctly. +* All related unit and integration tests pass. + +--- + +# Testing Summary + +The bugs were identified using automated tests rather than manual inspection. + +Tests written included + +* Unit tests for service functions +* Integration tests for API endpoints +* Validation tests +* Edge-case tests +* Pagination tests +* Completion tests + +These tests successfully exposed hidden implementation defects before deployment. + +--- + +# Overall Result + +Both issues have been resolved. + +After implementing the fixes: + +* All automated tests pass. +* Pagination behaves correctly. +* Task completion preserves existing task priority. +* API behaviour matches the expected specification. +* Overall test coverage exceeds the required threshold.