ЁЯПл The SchoolтА║ЁЯПв APIsтА║ЁЯЪк рдзрдбрд╛ 12 тАФ Testing, docs рдЖрдгрд┐ gateway: рд╢рд╛рд│реЗрдЪреЗ рдЧреЗрдЯ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЪк рдзрдбрд╛ 12 тАФ Testing, docs рдЖрдгрд┐ gateway: рд╢рд╛рд│реЗрдЪреЗ рдЧреЗрдЯ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 12 тАФ рд╢реЗрд╡рдЯрдЪрд╛ рдзрдбрд╛! ┬╖ рдорд╛рдЧреАрд▓: lesson-11-beyond-rest


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдкреВрд░реНрдг рдХреЛрд░реНрд╕, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рд╢реЗрд╡рдЯрдЪрд╛ рдЯрдкреНрдкрд╛: CI рдордзреНрдпреЗ рдЪрд╛рд▓рдгрд╛рд▒реНрдпрд╛ contract tests, рдХреЕрдЯрд▓реЙрдЧрдордзреВрди рддрдпрд╛рд░ рд╣реЛрдгрд╛рд░реЗ documentation, observability (request ids рдЖрдгрд┐ log lines), рдЖрдгрд┐ API gateway тАФ рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рдЙрдВрдЯрд░рд╕рдореЛрд░рдЪреЗ рд╢рд╛рд│реЗрдЪреЗ рдЧреЗрдЯ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдХрд╛рдЙрдВрдЯрд░ рд▓реЛрдХрд╛рдВрд╕рд╛рдареА рдЙрдШрдбрдгреНрдпрд╛рдЖрдзреА рддреАрди рдЧреЛрд╖реНрдЯреА рдШрдбрддрд╛рдд:

  1. рддрдкрд╛рд╕рдгреАрд╕ ЁЯзк тАФ рдХреЛрдгреАрддрд░реА рдореБрджреНрджрд╛рдо рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░рдЪреА slip рджреЗрддреЛ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рд╢рд┐рдХреНрдХрд╛ рдХреЕрдЯрд▓реЙрдЧрд╢реА рдЬреБрд│рд╡реВрди рдкрд╛рд╣рддреЛ. рддреЗрдЪ рдореНрд╣рдгрдЬреЗ api/openapi.yaml рд╢реА рддреБрд▓рдирд╛ рдХреЗрд▓реЗрд▓рд╛ api/smoke_test.sh: рдПрдХ contract test. рдкреНрд░рддреНрдпреЗрдХ рдмрджрд▓рд╛рд╡рд░ рддреЛ рдЪрд╛рд▓рд╡рд╛ (CI/CD рд╢рд╛рд│реЗрдЪреЗ рддрдкрд╛рд╕рдгреА рдЯреЗрдмрд▓).
  2. рдЫрд╛рдкрд▓реЗрд▓реА рдорд╛рд░реНрдЧрджрд░реНрд╢рд┐рдХрд╛ ЁЯУЪ тАФ рдХреЕрдЯрд▓реЙрдЧрдЪ docs page, client library рдЖрдгрд┐ mock server рдмрдирддреЛ. рдорд╛рд░реНрдЧрджрд░реНрд╢рд┐рдХрд╛ рдХреЛрдгреАрд╣реА рд╣рд╛рддрд╛рдиреЗ рд▓рд┐рд╣реАрдд рдирд╛рд╣реА.
  3. рдЧреЗрдЯ ЁЯЪк тАФ рдХрд╛рдЙрдВрдЯрд░рд╕рдореЛрд░ рд╢рд╛рд│реЗрдЪреЗ рдЧреЗрдЯ рдЙрднреЗ рдЕрд╕рддреЗ (рдПрдХ API gateway): рддреЗ passes рддрдкрд╛рд╕рддреЗ, рд░рд╛рдВрдЧ рд╕рд╛рдВрднрд╛рд│рддреЗ, рдкреНрд░рддреНрдпреЗрдХ slip рдпреЛрдЧреНрдп рдХрд╛рдЙрдВрдЯрд░рдХрдбреЗ рдкрд╛рдард╡рддреЗ, рдЖрдгрд┐ log рд▓рд┐рд╣рд┐рддреЗ. рддреНрдпрд╛рдорд╛рдЧрдЪрд╛ рдХрд╛рдЙрдВрдЯрд░ рдЫреЛрдЯрд╛рдЪ рд░рд╛рд╣рддреЛ. AWS рд╢рд╛рд│рд╛ рд╣реЗ рдЧреЗрдЯ API Gateway (L25) рдореНрд╣рдгреВрди рднрд╛рдбреНрдпрд╛рдиреЗ рдШреЗрддреЗ; Kubernetes рд╢рд╛рд│реЗрдЪреЗ Ingress (L10) рд╣реАрдЪ рдХрд▓реНрдкрдирд╛ рдЖрд╣реЗ.

рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ slip рдорд╛рдЧреЗ рдЦреВрдг рд░рд╛рд╣рддреЗ: рдЙрддреНрддрд░рд╛рд╡рд░ рдПрдХ X-Request-Id рдЖрдгрд┐ рдХрд╛рдЙрдВрдЯрд░рд╡рд░ рдПрдХ log line тАФ рддреНрдпрд╛рдореБрд│реЗ "14:03 рд▓рд╛ рдорд╛рдЭреНрдпрд╛ slip рдЪреЗ рдХрд╛рдп рдЭрд╛рд▓реЗ?" рд╣реЗ рдЧреВрдв рдирд╡реНрд╣реЗ, рдлрдХреНрдд рдПрдХ grep рдард░рддреЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    ci["ЁЯзк CI: smoke_test.sh vs openapi.yaml<br/>every slip, every stamp"]
    docs["ЁЯУЪ docs, clients, mocks<br/>generated from the catalogue"]
    gate["ЁЯЪк API gateway<br/>auth ┬╖ rate limit ┬╖ routing ┬╖ TLS ┬╖ logs"]
    api["ЁЯПв school-api<br/>small, focused"]
    obs["ЁЯСА X-Request-Id + one log line per slip<br/>latency, error rate per route"]
    ci -->|"1 green?"| docs -->|"2 publish"| gate -->|"3 route"| api
    api -.->|"4"| obs

тЭУ рдХрд╛рдп

ЁЯзк Testing рдЪрд╛ рд╕рдВрдкреВрд░реНрдг рдирдХрд╛рд╢рд╛: рд╣рд╛ рдзрдбрд╛ smoke test рдЖрдгрд┐ contract test рдмрд╛рдВрдзрддреЛ. рдЗрддрд░ рд╕рд╛рдд рдкреНрд░рдХрд╛рд░ тАФ functional, integration, regression, load, stress, security, fuzz тАФ рдЖрдгрд┐ UI рдкреНрд░рдХрд╛рд░ рдПрдХрд╛ рдУрд│реАрдд рдПрдХ рдЕрд╕реЗ рдХрд╛рдврд▓реЗ рдЖрд╣реЗрдд рдзрдбрд╛ 16 тАФ API test рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░ рдордзреНрдпреЗ, рдкреНрд░рддреНрдпреЗрдХрд╛рд╕реЛрдмрдд рддреЛ рдпрд╛ рдХрд╛рдЙрдВрдЯрд░рд╡рд░ рдХрд╕рд╛ рдЪрд╛рд▓рд╡рд╛рдпрдЪрд╛ рддреЗ.

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг tests рдирд╕рд▓реЗрд▓рд╛ рдХрд╛рдЙрдВрдЯрд░ рдЧреБрдкрдЪреВрдк рдмрд┐рдШрдбрддреЛ, docs рдирд╕рд▓реЗрд▓рд╛ рдХрд╛рдЙрдВрдЯрд░ рдЪреБрдХреАрдЪреНрдпрд╛ рдкрджреНрдзрддреАрдиреЗ рдмреЛрд▓рд╛рд╡рд▓рд╛ рдЬрд╛рддреЛ, request id рдирд╕рд▓реЗрд▓реНрдпрд╛ рдХрд╛рдЙрдВрдЯрд░рдЪрд╛ debug рд╣реЛрдд рдирд╛рд╣реА, рдЖрдгрд┐ рдкрд╛рдЪ рдХрд╛рдЙрдВрдЯрд░реНрд╕рдиреА рдкреНрд░рддреНрдпреЗрдХреА auth рдЖрдгрд┐ rate limits рдкреБрдиреНрд╣рд╛ рд▓рд┐рд╣рд┐рдгреЗ рдореНрд╣рдгрдЬреЗ bugs рдЪреЗ рдкрд╛рдЪ рд╕рдВрдЪ. рдЧреЗрдЯ рдЖрдгрд┐ рдХреЕрдЯрд▓реЙрдЧ рдпрд╛рдВрдЪреНрдпрд╛ рдорджрддреАрдиреЗрдЪ рдПрдХ рдЫреЛрдЯреА рдЯреАрдо рдЕрдиреЗрдХ рдХрд╛рдЙрдВрдЯрд░реНрд╕ рд╕реБрд░рдХреНрд╖рд┐рддрдкрдгреЗ рдЪрд╛рд▓рд╡рддреЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

send() X-Request-Id рддрдпрд╛рд░ рдХрд░рддреЛ рдЖрдгрд┐ log_line() рдЦреВрдг рдЫрд╛рдкрддреЛ; api/smoke_test.sh рд╣рд╛ contract test рдЖрд╣реЗ; api/openapi.yaml рд╣рд╛ рдХреЕрдЯрд▓реЙрдЧ рдЖрд╣реЗ. Gateway рдпрд╛ repo рдордзреНрдпреЗ рдирд╛рд╣реА тАФ рддреЛ рддреБрдореНрд╣реА рднрд╛рдбреНрдпрд╛рдиреЗ рдШреЗрддрд╛ рдХрд┐рдВрд╡рд╛ рд╕рдореЛрд░ рдЪрд╛рд▓рд╡рддрд╛ рддреЛ рднрд╛рдЧ рдЖрд╣реЗ (AWS L25, Kubernetes L10).

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛ тАФ рд╢реЗрд╡рдЯрдЪрд╛ рдкреНрд░рдХрд▓реНрдк

# 1) make the smoke test a real check: exit non-zero when a stamp is wrong
#    (wrap each curl with an expected status; e.g. code=$(curl -s -o /dev/null -w '%{http_code}' тАж); [ "$code" = 201 ] || exit 1)
# 2) follow one slip end to end:
curl -si http://127.0.0.1:8080/v1/students/2 | grep -i x-request-id          # copy the id
#    тАжand find that id in terminal 1's log line: GET /v1/students/2 тЖТ 200 ┬╖ 1 ms ┬╖ <id>
# 3) render the catalogue: paste api/openapi.yaml into https://editor.swagger.io тАФ every form, documented, for free
# 4) on paper: which of these does the gate do, which does the counter do?
#    check the API key ┬╖ reject class "9Z" ┬╖ 429 after 20 slips ┬╖ add X-Request-Id ┬╖ replay an Idempotency-Key ┬╖ TLS

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

рддреБрдордЪрд╛ рдкрдХреНрдХрд╛ рдХреЗрд▓реЗрд▓рд╛ smoke test рдирд┐рд░реЛрдЧреА API рд╡рд░ 0 рдиреЗ рд╕рдВрдкрддреЛ рдЖрдгрд┐ рддреБрдореНрд╣реА рдореБрджреНрджрд╛рдо рдПрдЦрд╛рджрд╛ status code рдмрд┐рдШрдбрд╡рд▓реНрдпрд╛рд╡рд░ 1 рдиреЗ (do_POST рдордзреНрдпреЗ 201 рдЪреЗ 200 рдХрд░рд╛ рдЖрдгрд┐ рддреЛ fail рд╣реЛрддрд╛рдирд╛ рдкрд╛рд╣рд╛). рддреБрдореНрд╣реА copy рдХреЗрд▓реЗрд▓рд╛ request id server log рдордзреНрдпреЗ рджрд┐рд╕рддреЛ. Render рдХреЗрд▓реЗрд▓рд╛ рдХреЕрдЯрд▓реЙрдЧ рдХреЛрд░реНрд╕рдордзрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ path рддреНрдпрд╛рдЪреНрдпрд╛ schemas рд╕рд╣ рджрд╛рдЦрд╡рддреЛ. рддреБрдордЪреЗ рдХрд╛рдЧрджрд╛рд╡рд░рдЪреЗ рдЙрддреНрддрд░: рдЧреЗрдЯ = key, 429, request id, TLS; рдХрд╛рдЙрдВрдЯрд░ = validation, idempotency replay.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдХрд╛рдЙрдВрдЯрд░ testable, documented, traceable рдЖрд╣реЗ рдЖрдгрд┐ рдЧреЗрдЯрдорд╛рдЧреЗ рдЙрднрд╛ рд░рд╛рд╣рд╛рдпрд▓рд╛ рддрдпрд╛рд░ рдЖрд╣реЗ тАФ API рдЙрдШрдб рдХрд░рдгреЗ рд╕реБрд░рдХреНрд╖рд┐рдд рдмрдирд╡рдгрд╛рд▒реНрдпрд╛ рдпрд╛ рдЪрд╛рд░ рдЧреЛрд╖реНрдЯреА.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: API public рд╣реЛрдгреНрдпрд╛рдЖрдзреА reviewers рд╣реАрдЪ рдпрд╛рджреА рд╡рд╛рдкрд░рддрд╛рдд: CI рдордзреНрдпреЗ contract tests, рддрдпрд╛рд░ рд╣реЛрдгрд╛рд░реЗ docs, request ids рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ route рдЪреЗ metrics, рд╕рдореЛрд░ рдПрдХ gateway. рдПрдХ рдЬрд░реА рд╕реБрдЯрд▓реЗ рддрд░реА рддреЗрдЪ incident рдмрдирддреЗ.

ЁЯОУ рдХрд╛рдЙрдВрдЯрд░ рдЖрддрд╛ рддреБрдордЪрд╛ рдЖрд╣реЗ

рджрдкреНрддрд░рдЦрд╛рдирд╛ тЖТ рдХрд╛рдЙрдВрдЯрд░ тЖТ slip рдЖрдгрд┐ рд╢рд┐рдХреНрдХрд╛ тЖТ рдХрдкрд╛рдЯрд╛рдВрддрд▓реА рдирд╛рдореЗ тЖТ рдард░рд▓реЗрд▓рд╛ form тЖТ рдЪрд╛рд░ рднрд╛рдЧ тЖТ рд╣реЙрд▓ рдкрд╛рд╕ тЖТ рдкрд╛рд╡рддреА рдирдВрдмрд░ тЖТ "рдкреБрдврдЪреЗ 20" тЖТ рдЬреБрдиреНрдпрд╛ forms рд╢реЗрдЬрд╛рд░реА рдирд╡реЗ forms тЖТ рд░рд╛рдВрдЧ рдЖрдгрд┐ рдкреНрд░рдд тЖТ callback тЖТ рдЧреЗрдЯ. рддреБрдореНрд╣реА рдлрдХреНрдд APIs рд╢рд┐рдХрд▓рд╛ рдирд╛рд╣реАрдд тАФ рддреБрдореНрд╣реА рдПрдХ рдЪрд╛рд▓рд╡рддрд╛. ЁЯПвЁЯОУ

рд╢рд╛рд│реЗрддрд▓реЗ рдкреБрдврдЪреЗ рджрд░рд╡рд╛рдЬреЗ: Database рд╢рд╛рд│рд╛ рдпрд╛ рдХрд╛рдЙрдВрдЯрд░рдорд╛рдЧрдЪреА рдиреЛрдВрджреАрдВрдЪреА рдЦреЛрд▓реА рдЯрд┐рдХрд╛рдК рдмрдирд╡рддреЗ; UI рд╢рд╛рд│рд╛ рдпрд╛ slips рднрд░рдгрд╛рд░рд╛ рд╕реВрдЪрдирд╛ рдлрд▓рдХ рдмрд╛рдВрдзрддреЗ.

git checkout main
bash api/smoke_test.sh     # one last run, for fun

ЁЯЪк Lesson 12 тАФ Testing, docs & the gateway: the school gate

ЁЯУН You are here: Lesson 12 of 12 тАФ the final lesson! ┬╖ Previous: lesson-11-beyond-rest


ЁЯУж What's in this branch

The complete course, plus the last mile: contract tests that run in CI, documentation generated from the catalogue, observability (request ids and log lines), and the API gateway тАФ the school gate in front of every counter.

ЁЯзТ Explain like I'm 5

Three things happen before a counter opens to the public:

  1. The inspector ЁЯзк тАФ someone hands in every kind of slip on purpose and checks every stamp against the catalogue. That is api/smoke_test.sh compared with api/openapi.yaml: a contract test. Run it on every change (the CI/CD school's checking desk).
  2. The printed guide ЁЯУЪ тАФ the catalogue becomes the docs page, the client library and the mock server. Nobody writes the guide by hand.
  3. The gate ЁЯЪк тАФ in front of the counter stands the school gate (an API gateway): it checks passes, keeps the queue, routes each slip to the right counter, and writes the log. The counter behind it stays small. The AWS school rents this gate as API Gateway (L25); the Kubernetes school's Ingress (L10) is the same idea.

And every slip leaves a trace: an X-Request-Id on the answer and one log line at the counter тАФ so "what happened to my slip at 14:03?" is a grep, not a mystery.

ЁЯЧ║я╕П Diagram

flowchart LR
    ci["ЁЯзк CI: smoke_test.sh vs openapi.yaml<br/>every slip, every stamp"]
    docs["ЁЯУЪ docs, clients, mocks<br/>generated from the catalogue"]
    gate["ЁЯЪк API gateway<br/>auth ┬╖ rate limit ┬╖ routing ┬╖ TLS ┬╖ logs"]
    api["ЁЯПв school-api<br/>small, focused"]
    obs["ЁЯСА X-Request-Id + one log line per slip<br/>latency, error rate per route"]
    ci -->|"1 green?"| docs -->|"2 publish"| gate -->|"3 route"| api
    api -.->|"4"| obs

тЭУ What

ЁЯзк The full testing map: this lesson builds the smoke test and the contract test. The other seven kinds тАФ functional, integration, regression, load, stress, security, fuzz тАФ and the UI kind are drawn one per row in lesson 16 тАФ every kind of API test, each with how you would run it against this counter.

ЁЯдФ Why

Because a counter without tests breaks silently, a counter without docs gets called wrongly, a counter without a request id is undebuggable, and five counters each re-implementing auth and rate limits is five sets of bugs. The gate and the catalogue are how one small team runs many counters safely.

ЁЯФз How (in this repo)

send() mints X-Request-Id and log_line() prints the trace; api/smoke_test.sh is the contract test; api/openapi.yaml is the catalogue. The gateway is not in the repo тАФ it is the piece you rent or run in front (AWS L25, Kubernetes L10).

ЁЯзк Try it тАФ the capstone

# 1) make the smoke test a real check: exit non-zero when a stamp is wrong
#    (wrap each curl with an expected status; e.g. code=$(curl -s -o /dev/null -w '%{http_code}' тАж); [ "$code" = 201 ] || exit 1)
# 2) follow one slip end to end:
curl -si http://127.0.0.1:8080/v1/students/2 | grep -i x-request-id          # copy the id
#    тАжand find that id in terminal 1's log line: GET /v1/students/2 тЖТ 200 ┬╖ 1 ms ┬╖ <id>
# 3) render the catalogue: paste api/openapi.yaml into https://editor.swagger.io тАФ every form, documented, for free
# 4) on paper: which of these does the gate do, which does the counter do?
#    check the API key ┬╖ reject class "9Z" ┬╖ 429 after 20 slips ┬╖ add X-Request-Id ┬╖ replay an Idempotency-Key ┬╖ TLS

тЬЕ Verify тАФ what you should see

Your hardened smoke test exits 0 on the healthy API and 1 after you deliberately break a status code (change 201 to 200 in do_POST and watch it fail). The request id you copied appears in the server log. The rendered catalogue shows every path from the course with its schemas. Your paper answer: gate = key, 429, request id, TLS; counter = validation, idempotency replay.

ЁЯПБ What you just proved

The counter is testable, documented, traceable and ready to stand behind a gate тАФ the four things that make an API safe to expose.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: this is the checklist reviewers use before an API goes public: contract tests in CI, generated docs, request ids and per-route metrics, a gateway in front. Miss one and it becomes the incident.

ЁЯОУ The counter is yours

The archive тЖТ the counter тЖТ the slip and the stamp тЖТ nouns in cabinets тЖТ the standard form тЖТ the four parts тЖТ the hall pass тЖТ receipt numbers тЖТ "the next 20" тЖТ new forms beside old тЖТ the queue and the copy тЖТ the callback тЖТ the gate. You didn't just learn APIs тАФ you run one. ЁЯПвЁЯОУ

Next doors in the school: the Database school makes the record room behind this counter durable; the UI school builds the notice board that fills in these slips.

git checkout main
bash api/smoke_test.sh     # one last run, for fun
тЖР Previousbeyond restNext тЖТrest methods

This page is the lesson's README from the lesson-12-testing-docs-gateway branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.