Learning bite
HTTP APIs and tests
Distinguish transport errors, HTTP responses, and invalid response data.
On this page
Separate the network from the answer
An HTTP API accepts requests and returns responses. The URL identifies a destination and path, a method such as GET describes the operation, headers carry metadata, and a response contains a status code and optional body. A connection can succeed while the server returns 404, and a 200 response can contain unusable data. Our checker needs all three: a successful request, valid JSON, and the expected status field.
In capacity-tool, create a subdirectory http-fixture with only a file called status.json:
{"status": "ok"}
In a separate terminal, change into http-fixture and run:
python3 -m http.server 8765 --bind 127.0.0.1
This serves that directory over loopback. Keep it running during the exercise; use Ctrl+C to stop it. Do not serve a directory containing secrets. It is a development fixture, not an application server.
Save status_client.py at the project root:
import json
from urllib.request import urlopen
def check_status(payload: object) -> bool:
if not isinstance(payload, dict) or payload.get("status") != "ok":
raise ValueError("unexpected status response")
return True
def fetch_status(url: str) -> bool:
with urlopen(url, timeout=3) as response:
payload = json.load(response)
return check_status(payload)
if __name__ == "__main__":
fetch_status("http://127.0.0.1:8765/status.json")
print("fixture check passed")
Run python status_client.py from the root. Expected output: fixture check passed. with closes the response when the block ends, including on failure. The timeout bounds individual socket operations here; it is not a complete deadline for an arbitrarily large or slowly streamed response.
Use failures to locate the problem
Request a missing filename: expect an HTTP error such as 404. Stop the server: expect a connection-related error. Restore the server, then break the JSON: expect a decoding error. Restore JSON but change ok to degraded: expect our ValueError. Change one condition at a time so each result has an explanation.
A retry repeats an operation. Bounded retries with a delay may help a transient read failure, but retrying malformed JSON indefinitely only adds traffic. A timed-out write may already have succeeded. An idempotency key lets a cooperating API recognize a repeated logical request; simply adding a header does not create that guarantee. MicroBank later gives this distinction a concrete transaction example.
Authenticated clients also need an explicit token source, correct TLS verification, and careful logging. Pagination means one response may be only one page of results. Reusable HTTP clients, retry policies, pagination, and concurrency are useful extensions after this first request; they are not provided by the tiny checker.
Test the rule without a server
Create tests if it does not exist. Save tests/test_status.py:
import unittest
from status_client import check_status
class StatusTests(unittest.TestCase):
def test_accepts_ok(self):
self.assertTrue(check_status({"status": "ok"}))
def test_rejects_bad_payloads(self):
for payload in ({}, {"status": "degraded"}, ["ok"], None):
with self.subTest(payload=payload):
with self.assertRaises(ValueError):
check_status(payload)
Run python -m unittest discover -s tests -p 'test_status.py' -v. Expect two discovered tests, one of which checks four rejected inputs. Stop the server and run them again: they should still pass because they test the rule, not the network. A unit test checks a small piece in isolation; the earlier loopback exercise checks how pieces interact. A fake response cannot prove that a deployed endpoint is reachable.
Next, combine the capacity tool's calculation, file, and CLI tests. Keep these tests separately named so you can see which behavior each suite covers.
References: urllib.request↗, unittest↗, and HTTP server limitations↗.
Your notes and evidence
Record observations, questions, or links to your work. Keep credentials out of your notes.
Back up or restore this path
Progress and notes stay in this browser. A backup contains only this learning path.