Contract, load and security tests: the bugs that live between and under
Monday, 9:00: the booking team will rename guestName on Wednesday, and two other teams read that field. Black Friday is three weeks away, with 4× the usual search traffic. Every test passes, and nobody knows what breaks first.
Some bugs hide between services, some only appear under heavy traffic, some only under attack. Normal tests miss all three.
- Problem
Each service's tests are green, yet production breaks: a renamed field between two services, a search that dies at 300 concurrent users, a review form that runs attackers' scripts.
- Why the simple system fails
Unit and integration tests check one service, one request at a time, with inputs you thought of. Bugs between services, under concurrency and from hostile input slip through.
- Engineering concept
Contracts verify the seams between services; load tests verify behaviour under realistic and extreme traffic; security tests hunt for inputs nobody planned for.
- Technology
Pact-style consumer-driven contracts, schema compatibility checks, k6 load scripts with p95/p99 thresholds, SAST, DAST, dependency scanning and fuzzing.
- Trade-offs
Contracts need cross-team discipline; load tests need prod-like environments and time; security scanners produce false positives. Each costs more than a unit test, so aim them precisely.
The problem
2 min- Omar · On-call SREBooking renames guestName → guest_name in Wednesday's release. Its tests all pass.
- Maya · Product managerEmail and invoicing read that field. Their tests pass too. And Black Friday brings 4× the usual search traffic in three weeks.
- Lina · Senior engineer, your mentorYou lead this one. Start with the rename.
The big idea
4 minTest the seams unit tests can't see: contracts check what each service expects of the other; load tests check what happens when thousands of guests arrive at once.
Building a bridge, you check that supplier A's bolts fit supplier B's holes before either ships. Then you drive the expected trucks across, and keep adding trucks until something bends.
How it works
5 min- 1Consumers write contracts
A : email records the requests it makes and only the fields it reads (guestName, email, checkIn), and publishes it to a broker.
- 2The provider verifies
Booking's CI replays every consumer's contract before deploying. The rename fails in minutes, naming email and invoicing.
- 3Expand, migrate, contract
Add guest_name and keep sending guestName; consumers switch; remove guestName when no contract reads it. A rename breaks backward compatibility (new code reads old data) and forward (old code tolerates new data).
- 4Load and stress
A holds expected peak (600 req/s for 15 min). A stress test keeps adding traffic to find the breaking point and how you fail.
- 5Spike and soak
Spike: jump 10× in seconds (does autoscaling keep up?). Soak: normal load for hours (memory leaks, slow drift).
- 6Thresholds decide
A threshold is a pass/fail rule: p99 under 1 s and errors under 1%. Never the average: a 142 ms mean can hide a 2.9 s .
Watch a decision being made
4 minYou prepare search for Black Friday, Lina checks
Search must meet its SLO (under 1 s, no 5xx) at the forecast peak of 600 searches a second, 4× today. Find out whether it does, what breaks first, and how much margin is left.
- 1Write the question as thresholds, prod-like
At 600 req/s for 15 minutes: p99 < 1 s, errors < 1%. Same instance sizes, pool and data volume as production, spun up only for the test.
Why: Lina: "Without thresholds, a load test is a chart people argue about. A tiny box finds the test environment's limits."
- 2Make traffic look like guests
Cities and dates drawn from last year's search logs, 1–4 s of think time, a realistic mix of search, hotel page and checkout.
Why: Repeating one URL measures one cache entry.
- 3Read percentiles and saturation together
Result: avg 142 ms, p99 2.9 s, errors 0.4%. App CPU 40%. DB pool: 20 connections busy, ~180 requests waiting.
Why: High latency with idle CPU means waiting. The resource with a queue in front of it is the bottleneck, so more app servers would only add waiters.
- 4Fix, rerun, measure the margin
Cache popular searches 60 s, pool 20 → 40 behind PgBouncer, index on availability(city, date). Rerun: p99 380 ms. Stress: breaking point ~1,500 req/s. Soak 8 h: memory flat.
Why: Passing at the forecast isn't enough; you need to know how much is left.
In real life
3 minPact: consumers write the contract
- Open-source tool that publicly traces its origins to the property site realestate.com.au
- Each consumer records what it needs; the provider verifies all of them
Fits HTTP APIs between teams that deploy independently.
Confluent Schema Registry: rules on the schema
- Widely used with Kafka: producers register event schemas centrally
- Rejects versions that break the compatibility mode; doesn't know who reads which field
Fits events with many unknown or future readers.
What goes wrong
3 minIt warms one cache entry and makes everything look fast.
A realistic mix, think time, parameters from real logs.
A 142 ms mean hid a 2.9 s p99.
Thresholds on p95 and p99 per endpoint, plus the error rate.
Both suites stay green and the seam stays untested.
The provider verifies the consumers' published contracts.
Trade-offs
3 min| Option | Fits when | You pay |
|---|---|---|
| Consumer-driven contracts | Teams deploy independently | A workflow for every team; checks shape, not full behaviour |
| Shared staging environment | A few journeys as a final smoke check | Slow, contended, often red; blocks independent deploys |
| Load tests in a prod-like environment | A known peak, latency SLOs | ~€4,000/month while running; noisy results |
- Karim · CTOKeep the shared staging environment and test everything together before releases. No new tools.
- Omar · On-call SREStaging found last month's rename three days late. And it's red half the time for someone else's change.
- Karim · CTOContracts mean every team learns a broker and a new workflow.
Your job as tech lead
4 minClaude Code can write the code for this. You still own the decisions, and you have to know what good looks like to accept or reject its work.
- How a rename ships: expand, migrate, contract over several releases, not one Wednesday deploy.
- The load test target and pass rule: hold 600 req/s with under 1 s and errors under 1%, then push until it breaks.
- What "realistic" means: varied cities and dates, think time, production-sized data, a prod-like environment spun up only for test runs.
Write a k6 [[load-test]] for search, plan first. Mix: 80% search, 15% hotel pages, 5% booking starts, 1–4 s think time. Pick cities and dates from the CSV of last month's real searches in perf/data. Ramp to 600 req/s over 5 minutes, hold 15, then add 150 req/s every 3 minutes until errors pass 1%. Thresholds: p99 under 1 s, errors under 1% during the hold. Target only the perf URL from config; refuse to run against production.
- Cities and dates vary and there's think time; one fixed city only measures the cache.
- Thresholds name p99 and error rate; if the report leads with the average, ask for percentiles.
- The script refuses a production URL: ask Claude to show you that line.
- It calls the test passed on a 142 ms average while p99 is 2.9 s.
- It "fixes" slow search by raising the k6 timeout or lowering the target.
- It runs the test against production or shared staging without asking.
Check yourself
2 minguestName to guest_name. Booking's tests pass. Email service's tests pass (they use a stub of the booking API). Production emails say 'Dear undefined'. Which test would have failed first, in which pipeline?Next episode: the September cloud bill arrives at €310k, and it grew much faster than bookings did.
Mark it complete to update your skill map.
Go deeper (optional)You don't need this to understand the lesson. Open it when a project needs the details: Technical deep dive, Interactive simulation, Visual diagram, Analogy, Check your understanding, Practice ladder, Explain it yourself.
Technical deep dive
You prepare search for Black Friday, Lina checks
Search must meet its SLO (good = under 1 s, no 5xx) at the forecast Black Friday peak of 600 searches a second, 4× today. Find out whether it does, what breaks first, and how much margin there is.
- 1Write the question as thresholds
At 600 req/s for 15 minutes: p99 < 1 s, p95 < 400 ms, errors < 1%.
Why: Lina: "Good: the SLO becomes a pass/fail gate. Without thresholds, a load test is a chart people argue about."
- 2Use a prod-like environment
Same instance sizes, pool settings and data volume as production (a copy of availability data), spun up only for the test.
Why: A smaller box or a tiny database finds the test environment's limits, not production's.
- 3Make traffic look like guests
Cities, dates and nights drawn from last year's search logs; think time between requests; search, hotel page and checkout in a realistic mix.
tsexport default function () { const s = pickFromProductionMix(); // city, check-in, nights http.get(`${__ENV.BASE_URL}/search?city=${s.city}&checkin=${s.checkIn}&nights=${s.nights}`, { tags: { name: "search" } }); sleep(1 + Math.random() * 3); // guests pause between clicks }Why: Repeating one URL measures one cache entry. Varied searches exercise the database the way Black Friday will.
- 4Read percentiles and saturation together
Result: avg 142 ms, p99 2.9 s, errors 0.4%. App CPU 40%. DB pool: 20 connections busy, ~180 requests waiting.
Why: High latency with idle CPU means waiting. The resource with a queue in front of it is the bottleneck.
- 5Fix the bottleneck, then rerun
Cache popular searches for 60 s, pool 20 → 40 behind a connection pooler, add an index on availability(city, date). Rerun: p99 380 ms.
Why: Each fix targets what saturated. Rerunning proves it, and shows where the next bottleneck is.
- 6Find the margin and the slow failures
Stress until errors appear: breaking point ~1,500 req/s (2.5× forecast). Soak at 300 req/s for 8 h: memory flat. Spike 150 → 1,000 in 30 s: autoscaling lags 90 s, so pre-scale on the morning of Black Friday.
Why: Passing at the forecast isn't enough. You need to know how much is left, and what happens in the first minute of a surge.
Interactive simulation
Visual diagram
Top: a consumer-driven contract flows from consumer to provider CI. Bottom: a load test hits a prod-like environment while monitoring records percentiles.
Analogy
- Building a bridgeSupplier A's bolts fit supplier B's holesTesting a systemContract test between services
- Building a bridgeDrive the expected number of trucks acrossTesting a systemLoad test at expected peak
- Building a bridgeKeep adding trucks until something bendsTesting a systemStress test: find the limit
- Building a bridgeLeave traffic running for weeksTesting a systemSoak test: find slow leaks
- Building a bridgeAn inspector hunting for weak spotsTesting a systemSecurity testing
Where the analogy breaks: a bridge changes rarely. Your services change every day, so these tests must run automatically, on every change.
Check your understanding
specialRequests fieldcheckIn to check_intotalCents from integer to stringpromoCode requiredPractice ladder
Which provider changes break which consumer?
Scenario Warm-upContracts on the broker for GET /bookings/:id:
- email reads:
guestName(string),email(email),checkIn(date) - invoicing reads:
guestName(string),totalCents(integer),currency(string)
Trace each proposed booking change: which contract fails, if any?
- Add a new field
loyaltyTier. - Remove
roomView(nobody lists it). - Change
totalCentsfrom integer to a decimaltotalin euros. - Rename
guestNametoguest_name. - Send
checkInas"10/11/2026"instead of"2026-11-10".
Finish the rename plan
Design PracticeYour plan to rename guestName → guest_name has two gaps. Fill in 2 and 4.
- Expand (booking, release 1): add
guest_name; keep sendingguestNamewith the same value. All contracts still pass. - ___
- Check: the broker shows no consumer version in production still reads
guestName. - ___
- For the
booking.createdevent: ___ (one line)
Load-test checkout before Black Friday
Design PracticeSearch is ready. Now checkout: normal peak ~12 checkouts/s, Black Friday forecast 50/s, with a flash sale at 10:00 that Maya expects to bring 200/s for about 2 minutes. Checkout calls the payment provider (whose sandbox allows only 20 requests/s) and writes to the bookings database.
Design the tests: which types, what traffic, what thresholds, and how you handle the payment provider.
Load test in production at 3 a.m.?
Decision StretchKarim: "Spinning up a prod-like perf environment costs ~€4,000 a month while it runs. Let's load-test production itself at 3 a.m., when traffic is low. It's the real environment, for free."
Facts: at 3 a.m. there are still ~20 searches/s from Asia; tests would add 600/s; search shares its database with booking; the payment provider can't be load-tested.
Write a short decision: "We chose X over Y because…, accepting the risk of…".
Explain it yourself
Explain to a teammate why 'all our services have 90% coverage' doesn't mean a renamed field, a Black Friday peak or a stored XSS will be caught, and what you would add for each.