Detail ToDo API

In this chapter we will create endpoint /api/todos/{:id} to get detail about specific Task

Write the Test

Write test at tests/features/task/detail_task_api_test.py

from http import HTTPStatus
from tests.base import BaseAPITestCase
from app.tasks.models.task import Task
from app.factory import db


class DetailTaskAPITest(BaseAPITestCase):
    def test_detail_task(self):
        endpoint = "/api/todos"
        task = Task(name="Buy groceries")
        db.session.add(task)
        db.session.commit()

        db.session.refresh(task)

        resp = self.client.get(f"{endpoint}/{task.id}")
        self.assertEqual(resp.status_code, HTTPStatus.OK)

        resp_json = resp.json or dict()
        data = resp_json["data"]
        self.assertEqual(data["name"], "Buy groceries")

        Task.query.delete()
        db.session.commit()

It will raise error HTTP_NOT_FOUND (404), since we not implement our code

F
======================================================================
FAIL: test_create_task (tests.features.task.detail_task_api_test.CreateTaskAPITest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/src/app/tests/features/task/detail_task_api_test.py", line 17, in test_create_task
    self.assertEqual(resp.status_code, HTTPStatus.OK)
AssertionError: 404 != <HTTPStatus.OK: 200>

----------------------------------------------------------------------
Ran 1 test in 0.029s

FAILED (failures=1)

Let's implement our code at app/tasks/views/detail_task_api.py

from http import HTTPStatus

from flask import jsonify
from flask.typing import ResponseReturnValue
from flask.views import View

from app.tasks.models.task import Task
from app.tasks.schemas.task_schema import TaskSchema


class DetailTaskAPI(View):
    def dispatch_request(self, task_id) -> ResponseReturnValue:
        tasks_schema = TaskSchema()
        task = Task.query.filter(
            Task.id == task_id
        ).first()

        resp = {
            "data": tasks_schema.dump(task)
        }
        return jsonify(resp), HTTPStatus.OK

And update our api routes to

from app.http.route import Route
from app.tasks.views.detail_task_api import DetailTaskAPI
from app.tasks.views.list_task_api import ListTaskAPI
from app.tasks.views.create_task_api import CreateTaskAPI
from app.views.main_api import MainView

api_routes = [
    Route.get("/", MainView),
    Route.group("/api", routes=[
        Route.post("/todos", view=CreateTaskAPI),
        Route.get("/todos", view=ListTaskAPI),
        Route.get("/todos/<task_id>", view=DetailTaskAPI),
    ]),
]

Re-run our test file

$ bin/test tests/features/task/detail_task_api_test.py

It should pass

.
----------------------------------------------------------------------
Ran 1 test in 0.033s

OK

Let's add test to cover when id passed not available

    ...
    def test_detail_task_when_id_not_exists(self):
        endpoint = "/api/todos"
        resp = self.client.get(f"{endpoint}/123")
        self.assertEqual(resp.status_code, HTTPStatus.NOT_FOUND)

Re-run our test file

$ bin/test tests/features/task/detail_task_api_test.py

It should raise error

.F
======================================================================
FAIL: test_detail_task_when_id_not_exists (tests.features.task.detail_task_api_test.DetailTaskAPITest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/src/app/tests/features/task/detail_task_api_test.py", line 29, in test_detail_task_when_id_not_exists
    self.assertEqual(resp.status_code, HTTPStatus.NOT_FOUND)
AssertionError: 200 != <HTTPStatus.NOT_FOUND: 404>

----------------------------------------------------------------------
Ran 2 tests in 0.046s

FAILED (failures=1)

Let's change our view to

from http import HTTPStatus

from flask import jsonify
from flask.typing import ResponseReturnValue
from flask.views import View

from app.tasks.models.task import Task
from app.tasks.schemas.task_schema import TaskSchema


class DetailTaskAPI(View):
    def dispatch_request(self, task_id) -> ResponseReturnValue:
        tasks_schema = TaskSchema()
        task = Task.query.filter(
            Task.id == task_id
        ).first_or_404()

        resp = {
            "data": tasks_schema.dump(task)
        }
        return jsonify(resp), HTTPStatus.OK

Re-run our test file

$ bin/test tests/features/task/detail_task_api_test.py

It should be pass

..
----------------------------------------------------------------------
Ran 2 tests in 0.047s

OK

Let's re-run all of our tests

$ bin/test discover -s tests -p '*_test.py'

It should pass

.......
----------------------------------------------------------------------
Ran 7 tests in 0.123s

OK

Your final project structure should be look like this

flask-todo
├── app
│   ├── config.py
│   ├── factory.py
│   ├── http
│   │   ├── __init__.py
│   │   └── route.py
│   ├── __init__.py
│   ├── routes
│   │   ├── api.py
│   │   └── __init__.py
│   ├── tasks
│   │   ├── __init__.py
│   │   ├── models
│   │   │   ├── __init__.py
│   │   │   └── task.py
│   │   ├── schemas
│   │   │   ├── __init__.py
│   │   │   └── task_schema.py
│   │   └── views
│   │       ├── create_task_api.py
│   │       ├── detail_task_api.py
│   │       ├── __init__.py
│   │       └── list_task_api.py
│   └── views
│       ├── __init__.py
│       └── main_api.py
├── bin
│   └── test
├── db
│   ├── create.sql
│   └── Dockerfile
├── docker-compose.yml
├── Dockerfile
├── .dockerignore
├── .env.dev
├── .gitignore
├── migrations
│   ├── alembic.ini
│   ├── env.py
│   ├── README
│   ├── script.py.mako
│   └── versions
│       └── 213adaf34061_create_tasks_tables.py
├── requirements.txt
├── serve.py
└── tests
    ├── base.py
    ├── features
    │   ├── __init__.py
    │   └── task
    │       ├── create_task_api_test.py
    │       ├── detail_task_api_test.py
    │       ├── __init__.py
    │       └── list_task_api_test.py
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── task
            ├── __init__.py
            └── task_model_test.py

17 directories, 43 files

We can commit our works before continuing to next chapter.

(venv)$ git add .
(venv)$ git commit -m "Create DetailTaskAPI endpoint"