Guided Walkthrough — Test Plan, Test Cases, Authentication, Edge Cases

12 min read

This is the worked walkthrough through each of the nine deliverables from the brief. Read it after you've made a first attempt — the goal is to confirm or correct your instincts, not substitute for them. The examples below are illustrative; treat them as a quality bar, not a copy-paste template. If yours look better, use yours. If yours feel thinner, the gap tells you what to fix.

How the deliverables hang together

Step 1 of 6

Strategy

One page setting scope, approach, environments, tools, risks. Everything below flows from it.

Six logical phases. The order matters — strategy informs scope, the matrix surfaces authorisation gaps before they multiply across test cases, schemas can wait until you know which responses you actually want to validate.

Step 1 — Test strategy

The shortest useful strategy fits on one page. Mine for BookItNow:

Scope. All endpoints listed in the brief, with deeper coverage on bookings (highest risk) and pagination on GET /hotels (most likely to be misused). Out of scope for this pass: admin-only hotel creation (low traffic), localisation, internationalisation.

Approach. Functional first — I want a baseline of "every endpoint works for the main happy path." Then auth: every endpoint tested under three roles (guest, user, admin). Then edge cases focused on bookings. Pagination and schemas layered in. Performance and contract testing deferred (stretch goals).

Environments. All tests run against the staging URL. Smoke runs against production read-only, never against any endpoint that mutates data.

Tools. Postman for exploration; Python requests + pytest for automation; jsonschema for response validation. One tool per job.

Risks. (1) No reset between test runs in staging — bookings accumulate; mitigated by per-test cleanup. (2) Auth tokens expire; mitigated by a fixture that fetches a fresh token per session. (3) Rate limits on /auth/login may break parallel runs; mitigated by serialising the auth tests.

That's about 200 words. It's enough. The point isn't to be comprehensive — it's to make defensible choices and document them so the team can challenge or accept each.

Step 2 — Permission matrix

Build it as a table. Rows are roles, columns are endpoints, cells are expected status codes:

Endpointguestuseradmin
POST /auth/login200200200
GET /hotels200200200
GET /hotels/:id200200200
POST /hotels401403201
GET /hotels/:id/rooms200200200
POST /bookings401201201
GET /bookings/:id (own)401200200
GET /bookings/:id (other user's)401403200
PUT /bookings/:id (own)401200403
PUT /bookings/:id (other user's)401403403
DELETE /bookings/:id (own)401204204
GET /users/me401200200
PUT /users/me401200200

A few things this table makes obvious that prose hides:

  • A guest never sees 403 — they can't even authenticate, so it's always 401.
  • PUT /bookings/:id by an admin returns 403 in this design — admins can read and cancel any booking, but can't modify one on the user's behalf. That's a deliberate scoping decision worth documenting.
  • "Other user's" rows are easy to miss without a matrix; they catch IDOR-style bugs.

Build this first. Every cell becomes a test, and you'll spot ambiguities you'd otherwise leave to the engineer's interpretation.

Step 3 — Sample test cases

Fifteen examples, three per category, illustrating the shape every entry in your inventory should take.

Booking — positive cases

TC-B-001: User books an available room

  • POST /bookings with { roomId: 42, from: "2026-08-01", to: "2026-08-05", guests: 2 }, valid user token.
  • Expected: 201 Created, body matches booking schema, total matches nights × pricePerNight.

TC-B-002: User reads their own booking

  • GET /bookings/{id} with the user's token.
  • Expected: 200 OK, body matches booking schema, userId matches the caller.

TC-B-003: User cancels their own booking

  • DELETE /bookings/{id} with the user's token.
  • Expected: 204 No Content. Follow-up GET returns the booking with status: cancelled, or 404 if hard-deleted.

Booking — negative cases

TC-B-101: Missing roomId

  • POST /bookings with no roomId, valid token.
  • Expected: 400 Bad Request, body identifies the missing field.

TC-B-102: from after to

  • POST /bookings with from: "2026-08-05", to: "2026-08-01".
  • Expected: 400 or 422, body explains the date inversion.

TC-B-103: Anonymous booking attempt

  • POST /bookings with no Authorization header.
  • Expected: 401 Unauthorized.

Authentication

TC-A-001: Successful login

  • POST /auth/login with valid credentials.
  • Expected: 200, body has accessToken (JWT format), refreshToken, expiresIn.

TC-A-002: Expired token

  • Any protected endpoint with a JWT whose exp is in the past.
  • Expected: 401, error code token_expired (or equivalent).

TC-A-003: Wrong role

  • POST /hotels with a user-role (not admin) token.
  • Expected: 403 Forbidden.

Edge cases

TC-E-001: Double booking

  • Two users attempt POST /bookings for the same room and dates.
  • First call: 201. Second call: 409 Conflict.

TC-E-002: Booking starting in the past

  • POST /bookings with from: "2020-01-01".
  • Expected: 400 or 422, error explains "start date must be in the future."

TC-E-003: Overlapping dates

  • Existing booking: from: 2026-08-01, to: 2026-08-05. New attempt: from: 2026-08-04, to: 2026-08-07.
  • Expected: 409 Conflict.

Pagination

TC-P-001: Default first page

  • GET /hotels with no params.
  • Expected: 200, default page (1) with default limit (e.g. 20), metadata reflects total count.

TC-P-002: Page beyond total

  • GET /hotels?page=999.
  • Expected: 200 with empty array, hasMore: false. Not 404.

TC-P-003: Invalid limit

  • GET /hotels?limit=99999.
  • Expected: 400 (over max) or capped to documented max — confirm and assert.

Fifteen cases, three per category. Your inventory of fifty extends this same shape across every endpoint family.

Step 4 — Auth deep dive

The auth tests deserve their own focused mini-suite. The brief listed six; here's what each looks like in practice:

1. Register → returns 201 with accessToken and refreshToken
2. Register with existing email → 409 Conflict
3. Login with correct credentials → 200 + token
4. Login with wrong password → 401 (NOT 400 — preserves user enumeration safety)
5. Login with non-existent email → also 401 (same response as wrong password)
6. Refresh with valid refresh token → 200 + new accessToken
7. Refresh with expired refresh token → 401
8. Refresh with reused (already-used) refresh token → 401 (rotation enforced)
9. Protected endpoint with no token → 401
10. Protected endpoint with malformed JWT → 401
11. Protected endpoint with tampered JWT (changed payload) → 401
12. Protected endpoint with expired JWT → 401, error code `token_expired`

That set covers the whole authentication surface and a chunk of the authorisation surface. Wire it up as the first test suite to run; if any of these fail, the rest of the suite is meaningless.

Step 5 — Schemas

Two JSON Schemas, one per critical response.

Booking response (POST /bookings 201)

{
  "type": "object",
  "required": ["id", "roomId", "userId", "from", "to", "status", "total", "createdAt"],
  "properties": {
    "id":        { "type": "integer", "minimum": 1 },
    "roomId":    { "type": "integer", "minimum": 1 },
    "userId":    { "type": "integer", "minimum": 1 },
    "from":      { "type": "string", "format": "date" },
    "to":        { "type": "string", "format": "date" },
    "guests":    { "type": "integer", "minimum": 1 },
    "status":    { "type": "string", "enum": ["confirmed", "pending", "cancelled"] },
    "total":     { "type": "number", "minimum": 0 },
    "createdAt": { "type": "string", "format": "date-time" }
  },
  "additionalProperties": false
}

Hotel response (GET /hotels/:id 200)

{
  "type": "object",
  "required": ["id", "name", "city", "rating"],
  "properties": {
    "id":      { "type": "integer", "minimum": 1 },
    "name":    { "type": "string", "minLength": 1, "maxLength": 200 },
    "city":    { "type": "string" },
    "rating":  { "type": "number", "minimum": 0, "maximum": 5 },
    "address": { "type": "object" },
    "rooms": {
      "type": "array",
      "items": { "type": "object", "required": ["id", "type", "pricePerNight"] }
    }
  },
  "additionalProperties": false
}

The additionalProperties: false line is a deliberate choice. It means "if you add a field, the schema fails until I update it." That's friction by design — you want to be reminded.

Step 6 — Smoke test list

Ten curl commands, each with an expected status:

# 1. API is up
curl -sf -o /dev/null -w "%{http_code}\n" $BASE/health           # 200
 
# 2. Anonymous hotel list works
curl -sf -o /dev/null -w "%{http_code}\n" $BASE/hotels           # 200
 
# 3. Anonymous hotel detail works
curl -sf -o /dev/null -w "%{http_code}\n" $BASE/hotels/1         # 200
 
# 4. Login works
curl -X POST -H "Content-Type: application/json" \
  -d "{\"email\":\"$SMOKE_USER\",\"password\":\"$SMOKE_PASS\"}" \
  -sf -o /dev/null -w "%{http_code}\n" $BASE/auth/login          # 200
 
# 5. /users/me works with a token
curl -H "Authorization: Bearer $TOKEN" \
  -sf -o /dev/null -w "%{http_code}\n" $BASE/users/me            # 200
 
# 6. Anonymous protected call refused
curl -o /dev/null -w "%{http_code}\n" $BASE/users/me             # 401
 
# 7. Pagination works
curl -sf -o /dev/null -w "%{http_code}\n" "$BASE/hotels?page=1&limit=5"  # 200
 
# 8. Hotel rooms endpoint works
curl -sf -o /dev/null -w "%{http_code}\n" $BASE/hotels/1/rooms   # 200
 
# 9. Token refresh works
curl -X POST -H "Content-Type: application/json" \
  -d "{\"refreshToken\":\"$REFRESH\"}" \
  -sf -o /dev/null -w "%{http_code}\n" $BASE/auth/refresh        # 200
 
# 10. Non-existent resource returns 404 (not 500)
curl -o /dev/null -w "%{http_code}\n" $BASE/hotels/999999        # 404

Wrap them in a bash script with a final "all 10 expected codes matched" check. That single script is now your post-deploy gate.

How the deliverables interlock

Read top to bottom and notice the dependencies:

  • The strategy sets scope and tool choices that the rest assumes.
  • The permission matrix generates roughly half the test cases — each non-trivial cell becomes a test.
  • Schemas plug into every test that reads booking or hotel responses, providing free structural validation.
  • Auth tests are the prerequisite for everything else; if login is broken, nothing in the user-scoped suite runs.
  • The smoke list is the trip-wire that runs in production after every deploy — three minutes of value for thirty seconds of runtime.

If you've done this work — even at a rough first-pass quality — you have something a real team would adopt. The next lesson helps you grade your own attempt and points at where to take this work next.

📝 Capstone task

Compare your draft from lesson 1 against this walkthrough.

  1. Open your test strategy. Does it cover scope, approach, environments, tools, and risks? Add anything you missed.
  2. Compare your permission matrix against the example above. Did you remember "other user's resource" rows? IDOR coverage?
  3. Pick three test cases from each of your category lists. Verify each has an ID, request shape, expected status, and expected body assertion. Tighten loose ones.
  4. Run a sanity check on your auth deep-dive — does it cover all twelve scenarios listed here, or are some missing?
  5. If you skipped the schemas or smoke list, add them now. Both are short and high-value.
  6. Save the v2 of your work alongside v1. The diff between them is your learning artefact.

Onward to the review lesson.

// tip to track lessons you complete and pick up where you left off across devices.