The e-commerce API was the project where I started caring less about isolated endpoint demos and more about complete user journeys. A cart endpoint can pass tests while checkout is broken. A payment initializer can work while webhook confirmation fails. The important confidence comes from testing the workflow.
The integration tests use pytest-django and DRF’s APIClient. One purchase test authenticates a user, creates a category and product, adds the product to the cart, reads the cart total, posts checkout, verifies the order state, checks that stock decreased, and confirms the cart is empty.
That test is valuable because it crosses boundaries. It exercises serializers, viewsets, model relationships, permissions, computed totals, transaction logic, and database writes in one path. It catches mistakes that unit tests around one function would miss.
Payment tests use mocks at the right boundary
The payment flow test creates a pending order locally, mocks Chapa initialization, calls the initialize endpoint, verifies that a pending Payment row was created, mocks Chapa verification, signs a webhook payload, calls the webhook endpoint, and checks that both order and payment become success.
That is the right kind of mocking. The test does not mock the app’s own serializer, model, or view logic. It mocks the external provider. The boundary is the network.
This is a general testing lesson I got from the project: mock what you do not own, but let your own stack run. For a Django API, the ORM, serializers, permissions, URL routing, and response codes are part of the behavior worth testing.
Documentation from the code
The project uses drf-spectacular to generate an OpenAPI schema and serve Swagger UI and ReDoc. That was a practical win because the API has several resource types: auth, products, categories, cart items, orders, and payments.
OpenAPI docs also forced a useful discipline. Once an endpoint is visible in Swagger, sloppy request and response shapes are harder to ignore. Extending schema descriptions on the viewsets made the API easier to inspect without reading source code.
The tradeoff is that generated docs are only as good as the serializers and annotations. Complex custom actions and payment responses still need explicit schema hints. Generated docs reduce documentation drift, but they do not remove the need to design clear API contracts.
CI and deployability
The repo is Dockerized and has a GitHub Actions workflow to build and test changes. It also includes management commands for seeding data and creating an admin user from environment variables, which makes deployment more repeatable.
Those operational pieces are not glamorous, but they changed how I viewed the project. A backend is not only models and views. It is also how a new environment gets a database schema, seed data, static files, secrets, a superuser, and a passing test suite.
The lesson I took from this project is that confidence has layers:
API docs make behavior discoverable.
Integration tests make workflows defensible.
CI makes regressions harder to merge silently.
Docker makes the runtime repeatable.
For commerce, those layers are not extras. They are part of making irreversible workflows safe enough to evolve.