From b2125c458ad4f2f476e56966896ae9f730004381 Mon Sep 17 00:00:00 2001 From: nrupendra-eox Date: Thu, 20 Aug 2026 00:28:28 -0400 Subject: [PATCH] Fix task bugs and add search filter --- app/service.py | 21 +++++------ tests/__init__.py | 1 + tests/test_service.py | 82 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/test_service.py diff --git a/app/service.py b/app/service.py index 8022b93..82e4a97 100644 --- a/app/service.py +++ b/app/service.py @@ -10,15 +10,15 @@ def list_tasks(status: str | None = None, q: str | None = None) -> list[dict[str """Return task records, optionally filtered by status and search text.""" tasks = load_tasks() filtered: list[dict[str, Any]] = [] + query = q.casefold() if q else None for task in tasks: - # Instructor note: intentional bug for the lab. - # This uses the literal string "status" instead of the query parameter value. - if status and task["status"] != "status": + if status and task["status"] != status: + continue + + if query and query not in task["title"].casefold() and query not in task["description"].casefold(): continue - # Instructor note: partial feature for the lab. - # The route already accepts `q`, but search is not implemented yet. filtered.append(task) return filtered @@ -53,12 +53,9 @@ def complete_task(task_id: int) -> dict[str, Any] | None: for task in tasks: if task["id"] == task_id: - updated_task = dict(task) - updated_task["status"] = "done" - updated_task["completed_at"] = datetime.now(timezone.utc).isoformat() - - # Instructor note: intentional bug for the lab. - # The updated task is returned, but the stored list is never updated or saved. - return updated_task + task["status"] = "done" + task["completed_at"] = datetime.now(timezone.utc).isoformat() + save_tasks(tasks) + return task return None diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/test_service.py b/tests/test_service.py new file mode 100644 index 0000000..f07ac2e --- /dev/null +++ b/tests/test_service.py @@ -0,0 +1,82 @@ +import json +from pathlib import Path +from tempfile import TemporaryDirectory +import unittest +from unittest.mock import patch + +from app.service import complete_task, list_tasks +from app.store import load_tasks + + +class ListTasksTests(unittest.TestCase): + def test_filters_tasks_by_requested_status(self) -> None: + tasks = [ + {"id": 1, "status": "open"}, + {"id": 2, "status": "done"}, + {"id": 3, "status": "open"}, + ] + + with patch("app.service.load_tasks", return_value=tasks): + result = list_tasks(status="open") + + self.assertEqual([task["id"] for task in result], [1, 3]) + + def test_searches_task_titles_case_insensitively(self) -> None: + tasks = [ + {"id": 1, "title": "Write launch recap", "description": "Summary", "status": "open"}, + {"id": 2, "title": "Plan workshop", "description": "Agenda", "status": "open"}, + ] + + with patch("app.service.load_tasks", return_value=tasks): + result = list_tasks(q="LAUNCH") + + self.assertEqual([task["id"] for task in result], [1]) + + def test_searches_task_descriptions_case_insensitively(self) -> None: + tasks = [ + {"id": 1, "title": "Write recap", "description": "Product launch summary", "status": "open"}, + {"id": 2, "title": "Plan workshop", "description": "Agenda", "status": "open"}, + ] + + with patch("app.service.load_tasks", return_value=tasks): + result = list_tasks(q="LAUNCH") + + self.assertEqual([task["id"] for task in result], [1]) + + def test_combines_search_with_status_filter(self) -> None: + tasks = [ + {"id": 1, "title": "Plan workshop", "description": "Agenda", "status": "open"}, + {"id": 2, "title": "Write recap", "description": "Summary", "status": "open"}, + {"id": 3, "title": "Plan launch", "description": "Agenda", "status": "done"}, + ] + + with patch("app.service.load_tasks", return_value=tasks): + result = list_tasks(status="open", q="PLAN") + + self.assertEqual([task["id"] for task in result], [1]) + + +class CompleteTaskTests(unittest.TestCase): + def test_persists_completed_task(self) -> None: + with TemporaryDirectory() as directory: + data_file = Path(directory) / "tasks.json" + data_file.write_text( + json.dumps( + [ + { + "id": 1, + "status": "open", + "completed_at": None, + } + ] + ), + encoding="utf-8", + ) + + with patch("app.store.DATA_FILE", data_file): + completed_task = complete_task(1) + stored_task = load_tasks()[0] + + self.assertEqual(completed_task["status"], "done") + self.assertEqual(stored_task["status"], "done") + self.assertIsNotNone(stored_task["completed_at"])