๐ŸŽŸ๏ธ SeatMatrix

Event ticketing system with seat maps, tier pricing, and intentional QA bugs for testing.

Overview

SeatMatrix is an event ticketing API that lets users browse an event, view a live seat map, reserve seats by ID, and apply promo codes. The application is designed with intentional bugs for QA practice โ€” including a promo code stacking vulnerability and a concurrency race condition.

Base Path
/api/ticket
Auth Header
x-api-key: <your-key>
App URL
/seatmatrix.html
Swagger
/ticket/docs
Seat reservations are in-memory only โ€” they reset when the server restarts. Pre-seeded reserved seats exist on every fresh load.

Quick Start

Step 1
Get Event
GET /events โ†’ get event metadata
โ†’
Step 2
View Seats
GET /seats โ†’ see full seat map
โ†’
Step 3
Pick Seats
Choose available seats by ID (e.g. A1, C5)
โ†’
Step 4
Apply Promo
POST /promo with code + subtotal
โ†’
Step 5
Reserve
POST /reserve with seat IDs
curl https://qacloud.dev/api/ticket/seats \
  -H "x-api-key: YOUR_KEY"
curl -X POST https://qacloud.dev/api/ticket/reserve \
  -H "x-api-key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"seats": ["A1", "A2"]}'

Seat Map

The venue has 8 rows (Aโ€“H) ร— 10 columns = 80 seats total.

Seat IDs use letter + number format: A1, B7, H10.

Ghost Seat: C7 โ€” The API always reports C7 as "available", but the frontend ignores clicks on it. If you reserve C7 via the API directly, it will succeed. This is an intentional UI/API mismatch for testing.

Pre-Seeded Reserved Seats

These seats are reserved on every server start: A3 B6 D2 D9 E5 F1 G8 H4

Tiers & Pricing

VIP
$150
Rows Aโ€“B ยท Gold highlight
GENERAL
$80
Rows Cโ€“E ยท Blue highlight
BALCONY
$40
Rows Fโ€“H ยท Purple highlight

Booking Flow

The booking flow is client-side: get the seat map, calculate the subtotal from selected seats, optionally apply a promo code, then POST /reserve. The API only validates seat availability at reservation time โ€” there is no cart or session concept.

Conflict Handling

If any seat in the seats array is already reserved, the API returns 409 Conflict with a conflicts array listing the problematic seat IDs and a booked array for any that succeeded before the conflict was detected.

Promo Codes

The only valid promo code is QA-DISCOUNT, which applies a 10% discount.

POST /api/ticket/promo
{
  "code": "QA-DISCOUNT",
  "subtotal": 230
}
โ†’ { "valid": true, "code": "QA-DISCOUNT", "discount": 23, "percent": 10 }
Bug 1 โ€” Promo Stacking: The backend correctly calculates 10% of the original subtotal. However, the frontend applies the discount to the running total โ€” meaning you can apply the code multiple times and keep reducing the price. This is a deliberate frontend validation bug for testing.

Known Bugs (QA Surfaces)

Bug 1 โ€” Promo Code Stacking (Frontend): Apply QA-DISCOUNT multiple times. Each application reduces the total by 10% of the displayed amount rather than the original. The backend is correct; the bug is in the UI recalculation.
Bug 2 โ€” Concurrency Illusion (API): The reservation check uses a 50ms async "lock" delay. A client that reads "available" status immediately before the lock can still reserve the seat in a race condition. Try sending two simultaneous POST /reserve requests for the same seat to reproduce.
Ghost Seat C7 (UI/API Mismatch): The API reports C7 as available and accepts its reservation. The UI renders the seat but blocks clicks. The discrepancy exists so testers can find the inconsistency between UI state and API state.

API Reference

GET /api/ticket/events Get upcoming events

Returns the event list. Currently one event: QA Summit 2026 โ€” Live Testing Spectacular at TechArena, Silicon Bay on June 15, 2026.

{
  "events": [{
    "id": "evt-001",
    "name": "QA Summit 2026 โ€” Live Testing Spectacular",
    "venue": "TechArena, Silicon Bay",
    "date": "2026-06-15T19:00:00Z",
    "doors_open": "2026-06-15T18:00:00Z"
  }]
}
GET /api/ticket/seats Get the full seat map with availability

Returns all 80 seats with their tier, price, and current available or reserved status. Also returns tier metadata for UI rendering (color, price, rows).

{
  "rows": ["A","B","C","D","E","F","G","H"],
  "cols": 10,
  "seats": [
    { "id": "A1", "row": "A", "col": 1, "tier": "VIP", "price": 150, "status": "available" },
    ...
  ],
  "tiers": {
    "VIP":     { "price": 150, "color": "#f59e0b", "rows": ["A","B"] },
    "GENERAL": { "price": 80,  "color": "#3b82f6", "rows": ["C","D","E"] },
    "BALCONY": { "price": 40,  "color": "#8b5cf6", "rows": ["F","G","H"] }
  }
}
POST /api/ticket/reserve Reserve one or more seats
FieldRequiredDescription
seatsrequiredArray of seat ID strings e.g. ["A1", "B3"]

Success: { success: true, booked: ["A1","B3"] }

Conflict: 409 { conflicts: ["A3"], booked: ["B3"] }

Unknown seat IDs are treated as conflicts. C7 (ghost seat) will succeed via API even though the UI blocks it.

POST /api/ticket/promo Validate a promo code and get discount amount
FieldRequiredDescription
coderequiredPromo code string. Valid: QA-DISCOUNT
subtotaloptionalCurrent total. Used to calculate discount amount.

Valid: { valid: true, code: "QA-DISCOUNT", discount: 23, percent: 10 }

Invalid: 404 { valid: false, error: "Invalid promo code" }

Test Cases

TC-SM-001 GET /seats returns all 80 seats with correct tier assignment PASS

Steps: GET /seats โ†’ count seats in response: expect exactly 80. Verify rows A,B are VIP; C,D,E are GENERAL; F,G,H are BALCONY. Verify prices match tier definitions.

TC-SM-002 Pre-seeded reserved seats show as reserved PASS

Steps: GET /seats โ†’ find A3, B6, D2, D9, E5, F1, G8, H4 โ†’ all should have status "reserved".

TC-SM-003 Reserve available seats โ€” success PASS

Steps: POST /reserve with seats: ["A1", "A2"] โ†’ expect 200 with booked: ["A1","A2"]. GET /seats โ†’ verify A1, A2 now show status "reserved".

TC-SM-004 Reserve already-reserved seat โ€” returns 409 FAIL

Steps: POST /reserve with seats: ["A3"] (pre-seeded reserved) โ†’ expect 409 Conflict with conflicts: ["A3"].

TC-SM-005 Ghost seat C7 is reservable via API PASS

Steps: GET /seats โ†’ confirm C7 shows status "available". POST /reserve with seats: ["C7"] โ†’ expect 200. This verifies the API/UI mismatch (UI blocks clicks on C7).

TC-SM-006 Promo code QA-DISCOUNT returns 10% discount PASS

Steps: POST /promo with code: "QA-DISCOUNT", subtotal: 300 โ†’ expect discount: 30, percent: 10.

TC-SM-007 Invalid promo code returns 404 FAIL

Steps: POST /promo with code: "DOESNOTEXIST" โ†’ expect 404 with valid: false.

TC-SM-008 Empty seats array returns 400 FAIL

Steps: POST /reserve with seats: [] โ†’ expect 400 Bad Request.

TC-SM-009 Unknown seat ID treated as conflict FAIL

Steps: POST /reserve with seats: ["Z99"] (non-existent seat) โ†’ expect 409 with conflicts: ["Z99"].

QA Tasks

1. Seat Map Data Validation
Call GET /seats and validate: exactly 80 seats exist, each row has exactly 10 seats, tier assignments match the spec (A-B=VIP, C-E=GENERAL, F-H=BALCONY), all prices are correct.
2. Promo Code Stacking Bug Reproduction
In the UI, add seats, apply QA-DISCOUNT, then apply it again. Document how many times it applies before hitting $0. Verify the backend /promo always returns 10% of the provided subtotal (not stacked).
3. Concurrency Race Condition
Send two simultaneous POST /reserve requests for the same seat within a few milliseconds of each other. Check if both succeed (race condition Bug 2). This requires concurrent API calls โ€” use a script or parallel HTTP tool.
4. Ghost Seat UI vs API Mismatch
Attempt to click C7 in the UI โ€” it should be unclickable. Then reserve C7 via direct API call. Reload the seat map in the UI and verify the UI still doesn't reflect the reserved state for C7.
5. Bulk Reservation Partial Conflict
POST /reserve with an array including both available and already-reserved seats. Verify the response includes both a conflicts array and a booked array. Document which seats are booked before the conflict is detected.
6. Server Restart Resets Reservations
Reserve several non-seeded seats. Restart the server. Call GET /seats and verify all non-seeded reservations are gone (only the 8 pre-seeded seats remain reserved). This verifies in-memory storage behavior.
7. Mixed Case Promo Code
POST /promo with code: "qa-discount" (lowercase). Verify whether the backend accepts it (it uses .toUpperCase() internally, so it should). Document the exact behavior.
8. Tier Price Calculation
Reserve 2 VIP, 3 GENERAL, and 4 BALCONY seats. Calculate expected subtotal: (2ร—150) + (3ร—80) + (4ร—40) = 300 + 240 + 160 = $700. Verify UI shows matching total.