Skip to content

Parallel Booking API Testing

Complete end-to-end testing guide for the Parallel Booking feature with step-by-step API calls and expected responses.

📚 For Feature Overview: See the Parallel Booking documentation for concepts, configuration, and business use cases.


Overview

This guide provides practical, hands-on instructions for testing the parallel booking functionality through actual API endpoints. Each test scenario includes complete request/response examples, verification steps, and troubleshooting tips.

Purpose: Enable developers and QA engineers to thoroughly test parallel booking features including:

  • Capacity Management - Verify multiple bookings up to configured limits
  • Legacy Compatibility - Ensure single-booking services work unchanged
  • Availability Display - Confirm capacity information in availability grid
  • Rescheduling Logic - Test capacity updates during reschedules
  • Cancellation Impact - Verify capacity release on cancellations

Prerequisites:

  • API testing tool (Postman, Insomnia, or cURL)
  • Valid JWT token with appropriate permissions
  • Access to tenant/outlet IDs
  • Basic understanding of REST APIs

Environment Setup

Step 1: Authentication

Obtain a JWT token for API authentication.

Endpoint: POST /api/v1/auth/login

Request:

{
  "email": "admin@example.com",
  "password": "your-secure-password"
}

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "email": "admin@example.com",
    "role": "admin"
  }
}

Save the access_token - You'll need it for all subsequent requests.

Step 2: Configure API Client Headers

Add these headers to all requests:

Authorization: Bearer {access_token}
Content-Type: application/json

Step 3: Identify Tenant and Outlet

Get your tenant and outlet information.

Endpoint: GET /api/v1/tenants/me

Response:

{
  "id": "507f1f77bcf86cd799439011",
  "name": "Beauty Studio",
  "outlets": [
    {
      "id": "507f1f77bcf86cd799439012",
      "name": "Downtown Branch"
    }
  ]
}

Save these IDs:

  • tenant_id: 507f1f77bcf86cd799439011
  • outlet_id: 507f1f77bcf86cd799439012

Understanding Availability Grid API Response

The /api/v1/availability/availability-grid endpoint returns a structured response with the following format:

{
  "start_date": "2025-11-03",
  "end_date": "2025-11-03",
  "num_days": 1,
  "slot_interval_minutes": 30,
  "availability_grid": {
    "2025-11-03": [
      {
        "start_time": "10:00",
        "end_time": "11:00",
        "staff_id": "...",
        "is_available": true,
        "allows_parallel_bookings": true,
        "available_capacity": 5,
        "max_capacity": 10
      }
    ]
  },
  "metadata": {
    "service_id": "...",
    "service_name": "Group Yoga Class",
    "outlet_id": "...",
    "outlet_name": "Downtown Branch",
    "staff_id": "...",
    "total_available_slots": 1,
    "service_duration_minutes": 60
  }
}

Key Points:

  • Top-level fields: start_date, end_date, num_days, slot_interval_minutes, availability_grid, metadata
  • Time format: Uses "HH:MM" (e.g., "10:00") not "HH:MM:SS"
  • No staff names: Only staff_id is included in slot data
  • Date-keyed grid: Slots are nested under date keys (e.g., "2025-11-03")
  • Capacity fields: allows_parallel_bookings, available_capacity, max_capacity for parallel booking services
  • Metadata: Includes service and outlet information for context

Test Scenario 1: Single Booking Service (Legacy Behavior)

Objective: Verify backward compatibility - services with allow_parallel_bookings=false still work as before.

💡 Concept Reference: See Parallel Booking - Single-Client Services for business context.

Step 1.1: Create Single-Booking Service

Endpoint: POST /api/v1/services

Request:

{
  "name": "Haircut",
  "slug": "haircut-test",
  "description": "Professional haircut service",
  "category": "Hair",
  "duration_minutes": 60,
  "pricing": {
    "base_price": 75000,
    "currency": "IDR"
  },
  "allow_parallel_bookings": false,
  "max_parallel_bookings": 1,
  "outlet_id": "507f1f77bcf86cd799439012"
}

Response:

{
  "id": "67890abc12345def67890001",
  "name": "Haircut",
  "slug": "haircut-test",
  "allow_parallel_bookings": false,
  "max_parallel_bookings": 1,
  "duration_minutes": 60,
  "created_at": "2025-10-27T10:00:00Z"
}

Save service_id: 67890abc12345def67890001

Step 1.2: Create Staff Member

Endpoint: POST /api/v1/staff

Request:

{
  "first_name": "John",
  "last_name": "Smith",
  "email": "john.smith@example.com",
  "phone": "+62812345678",
  "position": "Senior Stylist",
  "hire_date": "2024-01-01",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Response:

{
  "id": "67890abc12345def67890002",
  "first_name": "John",
  "last_name": "Smith",
  "full_name": "John Smith",
  "email": "john.smith@example.com"
}

Save staff_id: 67890abc12345def67890002

Step 1.3: Create Staff Availability

Endpoint: POST /api/v1/availability

Request:

{
  "staff_id": "67890abc12345def67890002",
  "day_of_week": "monday",
  "start_time": "09:00",
  "end_time": "17:00",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Response:

{
  "id": "67890abc12345def67890003",
  "staff_id": "67890abc12345def67890002",
  "day_of_week": "monday",
  "start_time": "09:00:00",
  "end_time": "17:00:00",
  "is_available": true
}

Step 1.4: Create First Customer

Endpoint: POST /api/v1/customers

Request:

{
  "first_name": "Alice",
  "last_name": "Johnson",
  "email": "alice.test@example.com",
  "phone": "+62812345679"
}

Response:

{
  "id": "67890abc12345def67890004",
  "first_name": "Alice",
  "last_name": "Johnson",
  "email": "alice.test@example.com"
}

Save customer_id: 67890abc12345def67890004

Step 1.5: Book First Appointment (Should Succeed)

Endpoint: POST /api/v1/appointments

Request:

{
  "customer_id": "67890abc12345def67890004",
  "outlet_id": "507f1f77bcf86cd799439012",
  "appointment_date": "2025-11-03",
  "services": [
    {
      "service_id": "67890abc12345def67890001",
      "staff_id": "67890abc12345def67890002",
      "start_time": "10:00",
      "duration_minutes": 60
    }
  ]
}

Expected Response:SUCCESS (201 Created)

{
  "id": "67890abc12345def67890005",
  "status": "confirmed",
  "appointment_date": "2025-11-03",
  "services": [
    {
      "service_id": "67890abc12345def67890001",
      "service_name": "Haircut",
      "staff_id": "67890abc12345def67890002",
      "staff_name": "John Smith",
      "start_time": "10:00:00",
      "end_time": "11:00:00",
      "duration_minutes": 60
    }
  ],
  "customer": {
    "id": "67890abc12345def67890004",
    "name": "Alice Johnson"
  }
}

Step 1.6: Create Second Customer

Endpoint: POST /api/v1/customers

Request:

{
  "first_name": "Bob",
  "last_name": "Williams",
  "email": "bob.test@example.com",
  "phone": "+62812345680"
}

Save customer_id: 67890abc12345def67890006

Step 1.7: Attempt Second Booking (Should Fail)

Endpoint: POST /api/v1/appointments

Request:

{
  "customer_id": "67890abc12345def67890006",
  "outlet_id": "507f1f77bcf86cd799439012",
  "appointment_date": "2025-11-03",
  "services": [
    {
      "service_id": "67890abc12345def67890001",
      "staff_id": "67890abc12345def67890002",
      "start_time": "10:00",
      "duration_minutes": 60
    }
  ]
}

Expected Response:CONFLICT (409 Conflict)

{
  "detail": "Staff member John Smith is not available at the requested time slot"
}

✅ Verification Checklist

  • [ ] First booking succeeded with status 201
  • [ ] Second booking was blocked with status 409
  • [ ] Error message mentions staff unavailability
  • [ ] Legacy single-booking behavior maintained
  • [ ] Same staff available at different time (test by booking at 14:00)

Test Scenario 2: Parallel Booking Within Capacity

Objective: Verify services with allow_parallel_bookings=true accept multiple bookings up to capacity.

💡 Concept Reference: See Parallel Booking - Group Services and Service Configuration for details.

Step 2.1: Create Parallel Booking Service

Endpoint: POST /api/v1/services

Request:

{
  "name": "Group Yoga Class",
  "slug": "group-yoga-test",
  "description": "60-minute group yoga session",
  "category": "Fitness",
  "duration_minutes": 60,
  "pricing": {
    "base_price": 150000,
    "currency": "IDR"
  },
  "allow_parallel_bookings": true,
  "max_parallel_bookings": 10,
  "outlet_id": "507f1f77bcf86cd799439012"
}

Response:

{
  "id": "67890abc12345def67890010",
  "name": "Group Yoga Class",
  "allow_parallel_bookings": true,
  "max_parallel_bookings": 10,
  "duration_minutes": 60
}

Save service_id: 67890abc12345def67890010

Step 2.2: Create Yoga Instructor

Endpoint: POST /api/v1/staff

Request:

{
  "first_name": "Emma",
  "last_name": "Martinez",
  "email": "emma.test@example.com",
  "phone": "+62812345681",
  "position": "Yoga Instructor",
  "hire_date": "2024-01-01",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save staff_id: 67890abc12345def67890011

Step 2.3: Set Staff Availability

Endpoint: POST /api/v1/availability

Request:

{
  "staff_id": "67890abc12345def67890011",
  "day_of_week": "monday",
  "start_time": "10:00",
  "end_time": "12:00",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Step 2.4: Book Multiple Students (Bookings 1-5)

Create 5 customers and book them all for the same time slot.

For each student (i = 1 to 5):

  1. Create Customer:

    {
      "first_name": "Student{i}",
      "last_name": "Test",
      "email": "student{i}@example.com",
      "phone": "+6281234568{i}"
    }
    

  2. Book Appointment:

    {
      "customer_id": "{customer_id}",
      "outlet_id": "507f1f77bcf86cd799439012",
      "appointment_date": "2025-11-03",
      "services": [
        {
          "service_id": "67890abc12345def67890010",
          "staff_id": "67890abc12345def67890011",
          "start_time": "10:00",
          "duration_minutes": 60
        }
      ]
    }
    

Expected Response for ALL 5 bookings:SUCCESS (201 Created)

Step 2.5: Check Availability Grid

Endpoint: GET /api/v1/availability/availability-grid

Query Parameters:

outlet_id=507f1f77bcf86cd799439012
service_id=67890abc12345def67890010
staff_id=67890abc12345def67890011
start_date=2025-11-03
end_date=2025-11-03

Expected Response:

{
  "start_date": "2025-11-03",
  "end_date": "2025-11-03",
  "num_days": 1,
  "slot_interval_minutes": 30,
  "availability_grid": {
    "2025-11-03": [
      {
        "start_time": "10:00",
        "end_time": "11:00",
        "staff_id": "67890abc12345def67890011",
        "is_available": true,
        "allows_parallel_bookings": true,
        "available_capacity": 5,
        "max_capacity": 10
      }
    ]
  },
  "metadata": {
    "service_id": "67890abc12345def67890010",
    "service_name": "Group Yoga Class",
    "outlet_id": "507f1f77bcf86cd799439012",
    "outlet_name": "Downtown Branch",
    "staff_id": "67890abc12345def67890011",
    "total_available_slots": 1,
    "service_duration_minutes": 60
  }
}

✅ Verification Checklist

  • [ ] All 5 bookings succeeded (status 201)
  • [ ] Response has top-level fields: start_date, end_date, num_days, slot_interval_minutes
  • [ ] Availability grid nested under availability_grid key with date keys
  • [ ] Slot shows available_capacity: 5 (10 - 5 = 5 remaining)
  • [ ] Slot shows max_capacity: 10
  • [ ] Field allows_parallel_bookings: true present
  • [ ] Field is_available: true (slot still available)
  • [ ] Metadata section includes service and outlet information
  • [ ] Time format is "HH:MM" not "HH:MM:SS"
  • [ ] 6th booking succeeds (test by creating another customer)

Test Scenario 3: Capacity Exceeded

Objective: Verify bookings are blocked when capacity limit is reached.

Step 3.1: Create Small Capacity Service

Endpoint: POST /api/v1/services

Request:

{
  "name": "Boot Camp Session",
  "slug": "boot-camp-test",
  "description": "High-intensity training",
  "category": "Fitness",
  "duration_minutes": 45,
  "pricing": {
    "base_price": 200000,
    "currency": "IDR"
  },
  "allow_parallel_bookings": true,
  "max_parallel_bookings": 3,
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save service_id: 67890abc12345def67890020

Step 3.2: Create Trainer Staff

Endpoint: POST /api/v1/staff

Request:

{
  "first_name": "Mike",
  "last_name": "Johnson",
  "email": "mike.test@example.com",
  "phone": "+62812345682",
  "position": "Personal Trainer",
  "hire_date": "2024-01-01",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save staff_id: 67890abc12345def67890021

Step 3.3: Set Trainer Availability

Endpoint: POST /api/v1/availability

Request:

{
  "staff_id": "67890abc12345def67890021",
  "day_of_week": "monday",
  "start_time": "14:00",
  "end_time": "16:00",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Step 3.4: Fill to Capacity (Bookings 1-3)

Create 3 customers and book them all for the same 14:00 time slot.

Expected Response for bookings 1-3:SUCCESS (201 Created)

Step 3.5: Check Capacity Status

Endpoint: GET /api/v1/availability/availability-grid

Query Parameters:

outlet_id=507f1f77bcf86cd799439012
service_id=67890abc12345def67890020
staff_id=67890abc12345def67890021
start_date=2025-11-03
end_date=2025-11-03

Expected Response:

{
  "start_date": "2025-11-03",
  "end_date": "2025-11-03",
  "num_days": 1,
  "slot_interval_minutes": 30,
  "availability_grid": {
    "2025-11-03": [
      {
        "start_time": "14:00",
        "end_time": "14:45",
        "staff_id": "67890abc12345def67890021",
        "is_available": false,
        "allows_parallel_bookings": true,
        "available_capacity": 0,
        "max_capacity": 3
      }
    ]
  },
  "metadata": {
    "service_id": "67890abc12345def67890020",
    "service_name": "Boot Camp Session",
    "outlet_id": "507f1f77bcf86cd799439012",
    "outlet_name": "Downtown Branch",
    "staff_id": "67890abc12345def67890021",
    "total_available_slots": 1,
    "service_duration_minutes": 45
  }
}

Step 3.6: Attempt 4th Booking (Should Fail)

Create 4th customer and attempt booking.

Endpoint: POST /api/v1/appointments

Request:

{
  "customer_id": "{customer_4_id}",
  "outlet_id": "507f1f77bcf86cd799439012",
  "appointment_date": "2025-11-03",
  "services": [
    {
      "service_id": "67890abc12345def67890020",
      "staff_id": "67890abc12345def67890021",
      "start_time": "14:00",
      "duration_minutes": 45
    }
  ]
}

Expected Response:CONFLICT (409 Conflict)

{
  "detail": "Staff member Mike Johnson is not available at the requested time slot"
}

✅ Verification Checklist

  • [ ] First 3 bookings succeeded
  • [ ] Response has correct structure with availability_grid and metadata sections
  • [ ] Slot shows available_capacity: 0
  • [ ] Slot shows is_available: false
  • [ ] 4th booking was blocked (status 409)
  • [ ] Error message mentions staff unavailability
  • [ ] Capacity limit strictly enforced

Test Scenario 4: Availability Grid Capacity Display

Objective: Verify availability grid returns accurate capacity information across different booking states.

Step 4.1: Create Test Service

Endpoint: POST /api/v1/services

Request:

{
  "name": "Cooking Class",
  "slug": "cooking-class-test",
  "description": "Learn to cook amazing dishes",
  "category": "Education",
  "duration_minutes": 120,
  "pricing": {
    "base_price": 300000,
    "currency": "IDR"
  },
  "allow_parallel_bookings": true,
  "max_parallel_bookings": 8,
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save service_id: 67890abc12345def67890030

Step 4.2: Create Multiple Time Slots with Different Capacity

Create staff availability and bookings to achieve different capacity states:

Time Slot Bookings to Create Expected Capacity Expected Status
10:00 0 8/8 Available
14:00 3 5/8 Available
18:00 7 1/8 Available (Almost Full)
21:00 8 0/8 Full

Step 4.3: Query Availability Grid

Endpoint: GET /api/v1/availability/availability-grid

Query Parameters:

outlet_id=507f1f77bcf86cd799439012
service_id=67890abc12345def67890030
staff_id=staff_id_assigned_to_service
start_date=2025-11-03
end_date=2025-11-03

Expected Response:

{
  "start_date": "2025-11-03",
  "end_date": "2025-11-03",
  "num_days": 1,
  "slot_interval_minutes": 30,
  "availability_grid": {
    "2025-11-03": [
      {
        "start_time": "10:00",
        "end_time": "12:00",
        "staff_id": "67890abc12345def67890031",
        "is_available": true,
        "allows_parallel_bookings": true,
        "available_capacity": 8,
        "max_capacity": 8
      },
      {
        "start_time": "14:00",
        "end_time": "16:00",
        "staff_id": "67890abc12345def67890031",
        "is_available": true,
        "allows_parallel_bookings": true,
        "available_capacity": 5,
        "max_capacity": 8
      },
      {
        "start_time": "18:00",
        "end_time": "20:00",
        "staff_id": "67890abc12345def67890031",
        "is_available": true,
        "allows_parallel_bookings": true,
        "available_capacity": 1,
        "max_capacity": 8
      },
      {
        "start_time": "21:00",
        "end_time": "23:00",
        "staff_id": "67890abc12345def67890031",
        "is_available": false,
        "allows_parallel_bookings": true,
        "available_capacity": 0,
        "max_capacity": 8
      }
    ]
  },
  "metadata": {
    "service_id": "67890abc12345def67890030",
    "service_name": "Cooking Class",
    "outlet_id": "507f1f77bcf86cd799439012",
    "outlet_name": "Downtown Branch",
    "staff_id": "staff_id_assigned_to_service",
    "total_available_slots": 4,
    "service_duration_minutes": 120
  }
}

✅ Verification Checklist

  • [ ] Response structure includes availability_grid with date keys
  • [ ] All slots have allows_parallel_bookings: true
  • [ ] available_capacity matches expected values for each slot (8, 5, 1, 0)
  • [ ] max_capacity is 8 for all slots
  • [ ] is_available: true when capacity > 0
  • [ ] is_available: false when capacity = 0
  • [ ] Time format is "HH:MM" (e.g., "10:00" not "10:00:00")
  • [ ] Metadata section present with service details
  • [ ] Capacity calculation correct: available = max - current_bookings

Test Scenario 5: Mixed Services on Same Staff

Objective: Verify staff can handle both parallel and single-booking services independently.

Step 5.1: Create Two Services for Same Staff

Service 1: Group Yoga (Parallel)

Endpoint: POST /api/v1/services

Request:

{
  "name": "Group Yoga Mixed",
  "slug": "group-yoga-mixed-test",
  "description": "Group yoga class",
  "category": "Fitness",
  "duration_minutes": 60,
  "pricing": {
    "base_price": 150000,
    "currency": "IDR"
  },
  "allow_parallel_bookings": true,
  "max_parallel_bookings": 10,
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save yoga_service_id: 67890abc12345def67890040

Service 2: Personal Training (Single)

Endpoint: POST /api/v1/services

Request:

{
  "name": "Personal Training Mixed",
  "slug": "personal-training-mixed-test",
  "description": "One-on-one training",
  "category": "Fitness",
  "duration_minutes": 60,
  "pricing": {
    "base_price": 500000,
    "currency": "IDR"
  },
  "allow_parallel_bookings": false,
  "max_parallel_bookings": 1,
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save training_service_id: 67890abc12345def67890041

Step 5.2: Create Multi-Skill Staff

Endpoint: POST /api/v1/staff

Request:

{
  "first_name": "Sarah",
  "last_name": "Wilson",
  "email": "sarah.test@example.com",
  "phone": "+62812345683",
  "position": "Fitness Instructor",
  "hire_date": "2024-01-01",
  "outlet_id": "507f1f77bcf86cd799439012"
}

Save staff_id: 67890abc12345def67890042

Step 5.3: Book Multiple Students to Yoga Class (10:00)

Book 5 students to the same yoga class at 10:00.

Expected Result: ✅ All 5 bookings succeed

Step 5.4: Attempt 6th Yoga Booking (Should Succeed)

Book 6th student to same yoga class at 10:00.

Expected Result:SUCCESS (capacity not exceeded)

Step 5.5: Book Personal Training (14:00)

Book 1 customer for personal training at 14:00.

Expected Result:SUCCESS

Step 5.6: Attempt 2nd Personal Training (Should Fail)

Book 2nd customer for personal training at 14:00.

Expected Result:CONFLICT (single booking limit)

Step 5.7: Verify Service Isolation

Query 1: Yoga Class Availability

Endpoint: GET /api/v1/availability/availability-grid

Query Parameters:

service_id={yoga_service_id}
outlet_id=507f1f77bcf86cd799439012
start_date=2025-11-03
staff_id={staff_id_asigned-to_yoga}
end_date=2025-11-03

Expected: Response availability_grid["2025-11-03"] contains 10:00 slot with available_capacity: 4 (10 - 6 = 4)

Query 2: Personal Training Availability

Endpoint: GET /api/v1/availability/availability-grid

Query Parameters:

service_id={training_service_id}
outlet_id=507f1f77bcf86cd799439012
start_date=2025-11-03
staff_id={staff_id_assigned_to_training}
end_date=2025-11-03

Expected: Response availability_grid["2025-11-03"] contains 14:00 slot with is_available: false (single booking filled)

✅ Verification Checklist

  • [ ] Multiple yoga bookings succeeded (parallel service)
  • [ ] Single personal training succeeded
  • [ ] Second personal training blocked (single-booking service)
  • [ ] Yoga capacity independent of training bookings
  • [ ] Same staff handles both service types correctly
  • [ ] Service-specific capacity tracking works

Test Scenario 6: Rescheduling with Capacity

Objective: Verify rescheduling respects capacity limits and updates availability correctly.

💡 Concept Reference: See Parallel Booking - Rescheduling with Capacity for detailed behavior explanation.

Step 6.1: Create Service and Initial Bookings

Service: Wine Tasting (capacity 4)

Initial State: - 15:00 slot: Create 3 bookings (3/4 capacity) - 18:00 slot: Create 2 bookings (2/4 capacity) - 21:00 slot: Create 4 bookings (4/4 FULL)

Step 6.2: Reschedule from 15:00 to 18:00 (Should Succeed)

Endpoint: PUT /api/v1/appointments/{appointment_id}/reschedule

Request:

{
  "new_date": "2025-11-03",
  "new_start_time": "18:00"
}

Expected Response:SUCCESS (200 OK)

{
  "id": "{appointment_id}",
  "status": "confirmed",
  "appointment_date": "2025-11-03",
  "services": [
    {
      "start_time": "18:00:00",
      "end_time": "19:00:00"
    }
  ]
}

Step 6.3: Verify Capacity Updates

Check 15:00 slot (should show 2/4 after reschedule out):

Endpoint: GET /api/v1/availability/availability-grid

Expected: Response availability_grid["2025-11-03"] contains 15:00 slot with available_capacity: 2 (was 1, now 2)

Check 18:00 slot (should show 3/4 after reschedule in):

Expected: Response availability_grid["2025-11-03"] contains 18:00 slot with available_capacity: 1 (was 2, now 1)

Step 6.4: Attempt Reschedule to Full Slot (Should Fail)

Try to reschedule an appointment to 21:00 (already at 4/4 capacity).

Endpoint: PUT /api/v1/appointments/{appointment_id}/reschedule

Request:

{
  "new_date": "2025-11-03",
  "new_start_time": "21:00"
}

Expected Response:CONFLICT (409 Conflict)

{
  "detail": "Staff member is not available at the requested time slot"
}

✅ Verification Checklist

  • [ ] Reschedule to available slot succeeded
  • [ ] Source slot capacity increased (3/4 → 2/4)
  • [ ] Destination slot capacity decreased (2/4 → 3/4)
  • [ ] Reschedule to full slot blocked
  • [ ] exclude_appointment_id parameter working correctly

Test Scenario 7: Cancellation Impact

Objective: Verify cancellations free capacity and allow new bookings.

Step 7.1: Create Full Workshop

Service: Art Workshop (capacity 6)

Initial State: Book 6 students to 14:00 slot (6/6 FULL)

Step 7.2: Verify Slot is Full

Endpoint: GET /api/v1/availability/availability-grid

Expected Response:

{
  "start_date": "2025-11-03",
  "end_date": "2025-11-03",
  "num_days": 1,
  "slot_interval_minutes": 30,
  "availability_grid": {
    "2025-11-03": [
      {
        "start_time": "14:00",
        "end_time": "16:00",
        "staff_id": "67890abc12345def67890051",
        "is_available": false,
        "allows_parallel_bookings": true,
        "available_capacity": 0,
        "max_capacity": 6
      }
    ]
  },
  "metadata": {
    "service_id": "67890abc12345def67890050",
    "service_name": "Art Workshop",
    "outlet_id": "507f1f77bcf86cd799439012",
    "outlet_name": "Downtown Branch",
    "staff_id": "67890abc12345def67890051",
    "total_available_slots": 1,
    "service_duration_minutes": 120
  }
}

Step 7.3: Cancel One Booking

Endpoint: PUT /api/v1/appointments/{appointment_id}/cancel

Expected Response:SUCCESS (200 OK)

{
  "id": "{appointment_id}",
  "status": "cancelled",
  "cancelled_at": "2025-10-27T15:30:00Z"
}

Step 7.4: Verify Capacity Freed

Endpoint: GET /api/v1/availability/availability-grid

Expected Response:

{
  "start_date": "2025-11-03",
  "end_date": "2025-11-03",
  "num_days": 1,
  "slot_interval_minutes": 30,
  "availability_grid": {
    "2025-11-03": [
      {
        "start_time": "14:00",
        "end_time": "16:00",
        "staff_id": "67890abc12345def67890051",
        "is_available": true,
        "allows_parallel_bookings": true,
        "available_capacity": 1,
        "max_capacity": 6
      }
    ]
  },
  "metadata": {
    "service_id": "67890abc12345def67890050",
    "service_name": "Art Workshop",
    "outlet_id": "507f1f77bcf86cd799439012",
    "outlet_name": "Downtown Branch",
    "staff_id": "67890abc12345def67890051",
    "total_available_slots": 1,
    "service_duration_minutes": 120
  }
}

Step 7.5: Book New Customer (Should Succeed)

Endpoint: POST /api/v1/appointments

Request: Book new customer to 14:00 slot

Expected Response:SUCCESS (201 Created)

Step 7.6: Test Multiple Cancellations

Cancel 2 more bookings.

Expected Capacity: 1 (from step 7.5) + 2 (cancelled) = 3 available

Step 7.7: Test No-Show Status

Endpoint: PATCH /api/v1/appointments/{appointment_id}/status

Request:

{
  "status": "no_show"
}

Expected: No-show appointments also free capacity (excluded from count)

✅ Verification Checklist

  • [ ] Full slot (6/6) marked as unavailable
  • [ ] Single cancellation freed 1 spot (6/6 → 5/6)
  • [ ] Capacity immediately available for new bookings
  • [ ] Multiple cancellations compound correctly
  • [ ] No-show status also frees capacity
  • [ ] Cancelled bookings excluded from capacity count

Advanced Testing Scenarios

Scenario A: Race Condition Testing

Objective: Test concurrent bookings to same slot

Setup: - Service with capacity 1 - Use API client to send 2 simultaneous POST requests

Expected: Exactly 1 succeeds, 1 fails with conflict

Test Steps:

  1. Use Postman Runner or parallel cURL commands
  2. Send 2 identical booking requests simultaneously
  3. Verify one returns 201, other returns 409

Scenario B: Capacity Change on Existing Bookings

Objective: Test changing max_parallel_bookings when bookings exist

Test Steps:

  1. Create service with capacity 10
  2. Book 8 customers
  3. Update service to capacity 5 via PUT /api/v1/services/{id}
  4. Verify existing 8 bookings remain valid
  5. Attempt 9th booking - should be blocked (over capacity)

Expected: Grandfathered approach - existing bookings honored

Scenario C: Different Services Overlapping

Objective: Verify staff can't do two services simultaneously

Test Steps:

  1. Book Yoga class (parallel) at 10:00-11:00
  2. Attempt Personal Training (single) at 10:30-11:30 (overlaps)
  3. Verify booking blocked

Expected: Staff conflict detection across services

Scenario D: Multi-Day Capacity Tracking

Objective: Verify capacity tracked per day, not globally

Test Steps:

  1. Fill Monday 10:00 slot to capacity
  2. Book Tuesday 10:00 slot
  3. Verify Tuesday booking succeeds

Expected: Capacity is day-specific


Database Verification (Optional)

If you have MongoDB access, verify data directly:

Check Service Configuration

db.services.findOne(
  { _id: ObjectId("67890abc12345def67890010") },
  { name: 1, allow_parallel_bookings: 1, max_parallel_bookings: 1 }
)

Count Active Bookings for Time Slot

db.appointments.countDocuments({
  "tenant_id": ObjectId("507f1f77bcf86cd799439011"),
  "appointment_date": ISODate("2025-11-03"),
  "services.service_id": ObjectId("67890abc12345def67890010"),
  "services.staff_id": ObjectId("67890abc12345def67890011"),
  "services.start_time": "10:00",
  "status": { $nin: ["cancelled", "no_show", "rescheduled"] }
})

Verify Parallel Booking Index Exists

db.appointments.getIndexes().filter(idx =>
  idx.name === "idx_appointment_parallel_booking_capacity"
)

Troubleshooting

Issue 1: "Service not found" Error

Symptom:

{
  "detail": "Service {service_id} not found"
}

Solutions:

  • Verify service_id is correct (copy from create response)
  • Check service belongs to correct tenant
  • Ensure service is not deleted

Issue 2: Capacity Not Updating

Symptom: Availability grid shows wrong capacity

Solutions:

  • Clear any caching if implemented
  • Verify appointments have correct status (not cancelled)
  • Check database directly for appointment count
  • Verify exclude_appointment_id parameter in reschedule

Issue 3: Parallel Booking Not Working

Symptom: Second booking blocked even with capacity

Solutions:

  • Verify service has allow_parallel_bookings: true
  • Check max_parallel_bookings > 1
  • Ensure both bookings use same service_id
  • Verify staff availability covers time slot

Issue 4: Authentication Errors

Symptom: 401 Unauthorized

Solutions:

  • Refresh JWT token (may be expired)
  • Verify Authorization: Bearer {token} header format
  • Check user has correct permissions

Issue 5: Capacity Fields Missing

Symptom: API response lacks available_capacity

Solutions:

  • Verify using latest API version
  • Check service has allow_parallel_bookings: true
  • Ensure endpoint supports parallel booking feature

API Reference Summary

Core Endpoints

Endpoint Method Purpose
/api/v1/services POST Create service with parallel booking config
/api/v1/services/{id} GET Get service details
/api/v1/staff POST Create staff member
/api/v1/availability POST Create availability schedule
/api/v1/availability/availability-grid GET Get availability with capacity
/api/v1/customers POST Create customer
/api/v1/appointments POST Create appointment
/api/v1/appointments/{id}/reschedule PUT Reschedule appointment
/api/v1/appointments/{id}/cancel PUT Cancel appointment

Test Completion Checklist

Mandatory Tests

  • [ ] Scenario 1: Single Booking Service ✅
  • [ ] Scenario 2: Parallel Booking Within Capacity ✅
  • [ ] Scenario 3: Capacity Exceeded ✅
  • [ ] Scenario 4: Availability Grid Display ✅
  • [ ] Scenario 5: Mixed Services Same Staff ✅
  • [ ] Scenario 6: Rescheduling with Capacity ✅
  • [ ] Scenario 7: Cancellation Impact ✅

Advanced Tests (Optional)

  • [ ] Scenario A: Race Condition Testing
  • [ ] Scenario B: Capacity Change on Existing Bookings
  • [ ] Scenario C: Different Services Overlapping
  • [ ] Scenario D: Multi-Day Capacity Tracking

Regression Tests

  • [ ] Verify existing single-booking services still work
  • [ ] Test API backward compatibility
  • [ ] Verify performance (< 100ms for capacity checks)
  • [ ] Test with realistic data volumes

Success Criteria

All tests passing if:

  1. Single-booking services block second bookings
  2. Parallel services accept multiple bookings up to capacity
  3. Bookings blocked when capacity exceeded
  4. Availability grid displays accurate capacity info
  5. Rescheduling updates capacity correctly
  6. Cancellations free capacity immediately
  7. No breaking changes to existing functionality

Next Steps After Testing

  1. Document Issues - Report bugs or unexpected behavior
  2. Performance Testing - Test with realistic load
  3. Frontend Integration - Test with UI components
  4. User Acceptance Testing - Get feedback from end users
  5. Production Deployment - Follow deployment checklist