Testing Strategy - Full Flight Simulation

Philosophy: Every pull request runs a complete "simulated flight" - testing the entire passenger journey from search to post-flight, including edge cases, failures, and recovery scenarios. Inspired by SpaceX's approach of simulating a full rocket mission before every launch.

The SpaceX Principle

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   "If you can't test it end-to-end, you can't ship it"         │
│                                                                 │
│   Every PR triggers:                                            │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │                                                         │  │
│   │   FULL PASSENGER JOURNEY SIMULATION                     │  │
│   │                                                         │  │
│   │   Search → Book → Pay → Check-in → Board → Fly → Land   │  │
│   │      ↓                                                  │  │
│   │   + 50+ edge cases                                      │  │
│   │   + Failure scenarios                                   │  │
│   │   + Recovery paths                                      │  │
│   │   + Concurrent operations                               │  │
│   │                                                         │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
│   If ANY scenario fails → PR blocked                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Testing Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     Testing Pyramid                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                        ┌───────┐                                │
│                        │  E2E  │  Full journey simulations      │
│                        │ Tests │  (~20 min)                     │
│                       ┌┴───────┴┐                               │
│                       │Integration│ API + DB + Services         │
│                       │  Tests   │ (~5 min)                     │
│                      ┌┴──────────┴┐                             │
│                      │   Unit     │ Business logic              │
│                      │   Tests    │ (~30 sec)                   │
│                     ┌┴────────────┴┐                            │
│                     │    Static    │ Types, linting             │
│                     │   Analysis   │ (~10 sec)                  │
│                     └──────────────┘                            │
│                                                                 │
│   Total PR pipeline: ~30 minutes                                │
│   All layers run in parallel where possible                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Full Journey Test Suites

Suite 1: Happy Path - Complete Passenger Journey

describe('Full Passenger Journey - Happy Path', () => {
  it('completes booking → check-in → boarding → flight', async () => {
    // 1. SEARCH
    const flights = await searchFlights({
      origin: 'CCS',
      destination: 'MIA',
      date: '2024-06-15',
      passengers: 2
    })
    expect(flights.length).toBeGreaterThan(0)

    // 2. SELECT FLIGHT
    const selectedFlight = flights[0]
    const availability = await getAvailability(selectedFlight.id)
    expect(availability.economy.available).toBeGreaterThanOrEqual(2)

    // 3. CREATE BOOKING
    const booking = await createBooking({
      flightId: selectedFlight.id,
      passengers: [
        { firstName: 'John', lastName: 'Doe', type: 'ADULT' },
        { firstName: 'Jane', lastName: 'Doe', type: 'ADULT' }
      ],
      contact: { email: 'john@test.com', phone: '+1234567890' }
    })
    expect(booking.pnr).toMatch(/^[A-Z0-9]{6}$/)
    expect(booking.status).toBe('PENDING')

    // 4. ADD ANCILLARIES
    const seatSelection = await selectSeats(booking.id, [
      { passengerId: booking.passengers[0].id, seat: '12A' },
      { passengerId: booking.passengers[1].id, seat: '12B' }
    ])
    expect(seatSelection.success).toBe(true)

    // 5. PAYMENT
    const payment = await processPayment(booking.id, {
      method: 'CREDIT_CARD',
      token: 'tok_visa_test',
      amount: booking.totalAmount
    })
    expect(payment.status).toBe('COMPLETED')

    // 6. TICKETING
    const tickets = await issueTickets(booking.id)
    expect(tickets.length).toBe(2)
    expect(tickets[0].status).toBe('OPEN')

    // 7. RETRIEVE BOOKING (verify persistence)
    const retrievedBooking = await getBooking(booking.pnr)
    expect(retrievedBooking.status).toBe('TICKETED')

    // 8. WEB CHECK-IN (24h before departure)
    await timeTravelTo(hoursBeforeFlight(24))
    const checkIn = await performCheckIn(booking.pnr, {
      passengers: booking.passengers.map(p => p.id)
    })
    expect(checkIn.boardingPasses.length).toBe(2)

    // 9. BOARDING PASS GENERATION
    const boardingPass = checkIn.boardingPasses[0]
    expect(boardingPass.bcbp).toBeDefined() // IATA BCBP format
    expect(boardingPass.qrCode).toBeDefined()

    // 10. AIRPORT CHECK-IN VERIFICATION
    const verification = await verifyBoardingPass(boardingPass.bcbp)
    expect(verification.valid).toBe(true)

    // 11. BOARDING
    await timeTravelTo(minutesBeforeFlight(45))
    const boarding = await boardPassenger(
      selectedFlight.id,
      booking.passengers[0].id
    )
    expect(boarding.status).toBe('BOARDED')

    // 12. FLIGHT DEPARTURE
    await timeTravelTo(scheduledDeparture(selectedFlight))
    await departFlight(selectedFlight.id)
    const flightStatus = await getFlightStatus(selectedFlight.id)
    expect(flightStatus).toBe('DEPARTED')

    // 13. FLIGHT ARRIVAL
    await timeTravelTo(scheduledArrival(selectedFlight))
    await arriveFlight(selectedFlight.id)

    // 14. POST-FLIGHT VERIFICATION
    const finalBooking = await getBooking(booking.pnr)
    expect(finalBooking.status).toBe('FLOWN')
    expect(finalBooking.segments[0].status).toBe('FLOWN')

    // 15. LOYALTY CREDIT
    const loyaltyAccount = await getLoyaltyAccount('john@test.com')
    expect(loyaltyAccount.currentSpend).toBeGreaterThan(0)
  })
})

Suite 2: Booking Edge Cases

describe('Booking Edge Cases', () => {
  describe('Inventory Management', () => {
    it('prevents overbooking when last seat is taken', async () => {
      // Setup: Flight with 1 seat remaining
      const flight = await createFlightWithInventory({ economy: 1 })

      // User A starts booking
      const bookingA = await startBooking(flight.id, 1)

      // User B tries to book same seat concurrently
      const bookingB = startBooking(flight.id, 1) // Don't await

      // User A completes first
      await completeBooking(bookingA.id)

      // User B should fail
      await expect(bookingB).rejects.toThrow('Insufficient inventory')
    })

    it('releases held inventory after timeout', async () => {
      const flight = await createFlightWithInventory({ economy: 1 })

      // Start booking but don't complete
      const booking = await startBooking(flight.id, 1)
      expect(await getAvailability(flight.id)).toBe(0)

      // Wait for hold timeout (15 min)
      await timeTravelForward(minutes(16))

      // Inventory should be released
      expect(await getAvailability(flight.id)).toBe(1)
      expect(await getBookingStatus(booking.id)).toBe('EXPIRED')
    })

    it('handles concurrent seat selection', async () => {
      const flight = await createFlightWithInventory({ economy: 10 })
      const booking1 = await createBooking(flight.id, 1)
      const booking2 = await createBooking(flight.id, 1)

      // Both try to select seat 12A simultaneously
      const [result1, result2] = await Promise.allSettled([
        selectSeat(booking1.id, '12A'),
        selectSeat(booking2.id, '12A')
      ])

      // One succeeds, one fails
      const successes = [result1, result2].filter(r => r.status === 'fulfilled')
      const failures = [result1, result2].filter(r => r.status === 'rejected')

      expect(successes.length).toBe(1)
      expect(failures.length).toBe(1)
    })
  })

  describe('Payment Scenarios', () => {
    it('handles payment failure and allows retry', async () => {
      const booking = await createBookingWithPaymentPending()

      // First payment fails
      const failedPayment = await processPayment(booking.id, {
        token: 'tok_card_declined'
      })
      expect(failedPayment.status).toBe('FAILED')
      expect(await getBookingStatus(booking.id)).toBe('PENDING')

      // Retry with valid card
      const successPayment = await processPayment(booking.id, {
        token: 'tok_visa_test'
      })
      expect(successPayment.status).toBe('COMPLETED')
    })

    it('prevents double payment', async () => {
      const booking = await createBookingWithPaymentPending()

      // Process payment twice concurrently
      const [pay1, pay2] = await Promise.allSettled([
        processPayment(booking.id, { token: 'tok_visa_test' }),
        processPayment(booking.id, { token: 'tok_visa_test' })
      ])

      // Only one should succeed
      const successes = [pay1, pay2].filter(r =>
        r.status === 'fulfilled' && r.value.status === 'COMPLETED'
      )
      expect(successes.length).toBe(1)
    })

    it('handles partial refund correctly', async () => {
      const booking = await createTicketedBooking({ amount: 500 })

      // Cancel one passenger
      await cancelPassenger(booking.id, booking.passengers[0].id)

      // Verify partial refund
      const refund = await getRefunds(booking.id)
      expect(refund.amount).toBeLessThan(500)
      expect(refund.status).toBe('PROCESSED')
    })
  })

  describe('PNR Management', () => {
    it('generates unique PNRs under high load', async () => {
      const pnrs = await Promise.all(
        Array(100).fill(null).map(() => createBooking())
      )

      const uniquePnrs = new Set(pnrs.map(b => b.pnr))
      expect(uniquePnrs.size).toBe(100)
    })

    it('splits PNR correctly when passengers diverge', async () => {
      // Booking with 2 passengers
      const booking = await createBookingWithPassengers(2)

      // Change flight for one passenger only
      const newFlight = await getAlternativeFlight(booking.flightId)
      const splitResult = await splitBooking(booking.id, {
        passengerId: booking.passengers[0].id,
        newFlightId: newFlight.id
      })

      expect(splitResult.originalPnr).toBe(booking.pnr)
      expect(splitResult.newPnr).not.toBe(booking.pnr)
      expect(splitResult.newPnr).toMatch(/^[A-Z0-9]{6}$/)
    })
  })
})

Suite 3: Check-in & Boarding Edge Cases

describe('Check-in & Boarding Edge Cases', () => {
  describe('Web Check-in', () => {
    it('blocks check-in before 24h window', async () => {
      const booking = await createTicketedBooking({
        departureIn: hours(48)
      })

      await expect(performCheckIn(booking.pnr))
        .rejects.toThrow('Check-in not yet available')
    })

    it('blocks check-in after window closes', async () => {
      const booking = await createTicketedBooking({
        departureIn: minutes(30)
      })

      await expect(performCheckIn(booking.pnr))
        .rejects.toThrow('Check-in closed')
    })

    it('handles check-in for infant with adult', async () => {
      const booking = await createBookingWithInfant()

      const checkIn = await performCheckIn(booking.pnr, {
        passengers: [
          booking.passengers.find(p => p.type === 'ADULT').id,
          booking.passengers.find(p => p.type === 'INFANT').id
        ]
      })

      // Infant should be on adult's boarding pass
      expect(checkIn.boardingPasses.length).toBe(1)
      expect(checkIn.boardingPasses[0].infantIncluded).toBe(true)
    })

    it('requires document verification for international', async () => {
      const booking = await createInternationalBooking()

      // Check-in without documents should fail
      await expect(performCheckIn(booking.pnr))
        .rejects.toThrow('Travel documents required')

      // Add passport
      await addTravelDocument(booking.passengers[0].id, {
        type: 'PASSPORT',
        number: 'AB123456',
        expiry: '2026-01-01',
        nationality: 'US'
      })

      // Now check-in should work
      const checkIn = await performCheckIn(booking.pnr)
      expect(checkIn.success).toBe(true)
    })
  })

  describe('Kiosk Check-in', () => {
    it('handles kiosk check-in with PNR lookup', async () => {
      const booking = await createTicketedBooking()

      const kioskSession = await startKioskSession()
      const lookup = await kioskLookup(kioskSession.id, {
        pnr: booking.pnr,
        lastName: booking.passengers[0].lastName
      })

      expect(lookup.booking.pnr).toBe(booking.pnr)

      const checkIn = await kioskCheckIn(kioskSession.id, {
        passengers: [booking.passengers[0].id]
      })

      expect(checkIn.boardingPasses.length).toBe(1)
      expect(checkIn.printJob.status).toBe('QUEUED')
    })

    it('handles bag drop at kiosk', async () => {
      const booking = await createCheckedInBooking()

      const kioskSession = await startKioskSession()
      const bagDrop = await kioskBagDrop(kioskSession.id, {
        bookingRef: booking.pnr,
        bags: [{ weight: 20 }]
      })

      expect(bagDrop.bagTags.length).toBe(1)
      expect(bagDrop.bagTags[0]).toMatch(/^[0-9]{10}$/)
    })

    it('handles kiosk timeout and session recovery', async () => {
      const booking = await createTicketedBooking()

      // Start session
      const session1 = await startKioskSession()
      await kioskLookup(session1.id, { pnr: booking.pnr })

      // Session times out
      await timeTravelForward(minutes(3))

      // New session should start fresh
      const session2 = await startKioskSession()
      expect(session2.id).not.toBe(session1.id)
    })
  })

  describe('Boarding', () => {
    it('enforces boarding sequence (zones)', async () => {
      const flight = await createFlightWithBoardingZones()
      const bookings = await createMultipleCheckedInBookings(flight.id, 10)

      // Try to board zone 3 passenger before zone 1
      const zone3Passenger = bookings.find(b =>
        b.boardingPass.zone === 3
      ).passengers[0]

      await expect(boardPassenger(flight.id, zone3Passenger.id))
        .rejects.toThrow('Zone 3 not yet boarding')

      // Board zone 1 first
      await startBoardingZone(flight.id, 1)
      const zone1Passenger = bookings.find(b =>
        b.boardingPass.zone === 1
      ).passengers[0]

      const boarding = await boardPassenger(flight.id, zone1Passenger.id)
      expect(boarding.status).toBe('BOARDED')
    })

    it('handles standby passengers', async () => {
      const flight = await createFullFlight()
      const standbyBooking = await createStandbyBooking(flight.id)

      // Initially cannot board
      await expect(boardPassenger(flight.id, standbyBooking.passengers[0].id))
        .rejects.toThrow('Standby - not cleared')

      // One passenger no-shows
      await markNoShow(flight.id, someOtherPassenger.id)

      // Clear standby
      await clearStandby(flight.id, standbyBooking.id)

      // Now can board
      const boarding = await boardPassenger(
        flight.id,
        standbyBooking.passengers[0].id
      )
      expect(boarding.status).toBe('BOARDED')
    })

    it('detects duplicate boarding attempt', async () => {
      const booking = await createCheckedInBooking()

      // First boarding succeeds
      await boardPassenger(booking.flightId, booking.passengers[0].id)

      // Second attempt fails
      await expect(boardPassenger(booking.flightId, booking.passengers[0].id))
        .rejects.toThrow('Already boarded')
    })
  })
})

Suite 4: Disruption & Recovery

describe('Disruption & Recovery Scenarios', () => {
  describe('Flight Cancellation', () => {
    it('automatically rebooks passengers on next available', async () => {
      const booking = await createTicketedBooking()
      const originalFlight = booking.segments[0].flight

      // Cancel flight
      await cancelFlight(originalFlight.id, {
        reason: 'MECHANICAL',
        notifyPassengers: true
      })

      // Verify automatic rebooking
      const updatedBooking = await getBooking(booking.pnr)
      expect(updatedBooking.segments[0].flight.id).not.toBe(originalFlight.id)
      expect(updatedBooking.status).toBe('REBOOKED')

      // Verify notification sent
      const notifications = await getNotifications(booking.contactEmail)
      expect(notifications).toContainEqual(
        expect.objectContaining({
          type: 'FLIGHT_CANCELLED',
          newFlight: expect.any(Object)
        })
      )
    })

    it('offers refund when no alternatives available', async () => {
      const booking = await createTicketedBooking()

      // Cancel with no alternatives
      await cancelFlight(booking.segments[0].flight.id, {
        reason: 'WEATHER',
        noAlternatives: true
      })

      const updatedBooking = await getBooking(booking.pnr)
      expect(updatedBooking.refundEligible).toBe(true)

      // Process refund
      const refund = await requestRefund(booking.pnr)
      expect(refund.amount).toBe(booking.totalAmount)
    })
  })

  describe('Flight Delay', () => {
    it('updates departure time and notifies passengers', async () => {
      const booking = await createCheckedInBooking()

      // Delay flight by 2 hours
      await delayFlight(booking.segments[0].flight.id, {
        newDeparture: hoursLater(2),
        reason: 'ATC_DELAY'
      })

      // Verify boarding pass updated
      const updatedBoardingPass = await getBoardingPass(
        booking.pnr,
        booking.passengers[0].id
      )
      expect(updatedBoardingPass.departureTime).toBe(hoursLater(2))

      // Verify notification
      const notifications = await getNotifications(booking.contactEmail)
      expect(notifications).toContainEqual(
        expect.objectContaining({
          type: 'FLIGHT_DELAYED',
          delayMinutes: 120
        })
      )
    })

    it('handles missed connection due to delay', async () => {
      const booking = await createConnectingFlightBooking()
      const firstFlight = booking.segments[0].flight
      const connectionFlight = booking.segments[1].flight

      // Delay first flight causing missed connection
      await delayFlight(firstFlight.id, {
        newDeparture: hoursLater(3) // Now arrives after connection departs
      })

      // System should detect and rebook connection
      const updatedBooking = await getBooking(booking.pnr)
      expect(updatedBooking.segments[1].flight.id).not.toBe(connectionFlight.id)
      expect(updatedBooking.segments[1].status).toBe('REBOOKED')
    })
  })

  describe('Passenger Issues', () => {
    it('handles no-show and releases seat', async () => {
      const booking = await createCheckedInBooking()

      // Flight departs without passenger
      await timeTravelTo(afterDeparture(booking.segments[0].flight))
      await processNoShows(booking.segments[0].flight.id)

      const updatedBooking = await getBooking(booking.pnr)
      expect(updatedBooking.segments[0].status).toBe('NO_SHOW')

      // Ticket should be updated
      expect(updatedBooking.tickets[0].status).toBe('VOID')
    })

    it('handles denied boarding with compensation', async () => {
      const flight = await createOverSoldFlight()
      const lastBooking = await getLastBookingForFlight(flight.id)

      // Deny boarding due to oversell
      const denial = await denyBoarding(flight.id, lastBooking.passengers[0].id, {
        reason: 'OVERSELL',
        voluntary: false
      })

      expect(denial.compensation.amount).toBeGreaterThan(0)
      expect(denial.rebooking).toBeDefined()

      // Verify in booking history
      const history = await getBookingHistory(lastBooking.pnr)
      expect(history).toContainEqual(
        expect.objectContaining({
          event: 'DENIED_BOARDING',
          compensation: expect.any(Number)
        })
      )
    })
  })

  describe('System Recovery', () => {
    it('recovers from payment processor outage', async () => {
      const booking = await createBookingWithPaymentPending()

      // Simulate payment processor down
      await simulateOutage('payment-processor')

      // Payment should be queued
      const paymentResult = await processPayment(booking.id, {
        token: 'tok_visa_test'
      })
      expect(paymentResult.status).toBe('QUEUED')

      // Restore service
      await restoreService('payment-processor')
      await processPaymentQueue()

      // Payment should complete
      const updatedBooking = await getBooking(booking.pnr)
      expect(updatedBooking.status).toBe('TICKETED')
    })

    it('maintains consistency during database failover', async () => {
      // Start a booking
      const booking = await startBooking()

      // Trigger DB failover mid-transaction
      await triggerDatabaseFailover()

      // Booking should either complete or cleanly fail
      const result = await completeBooking(booking.id).catch(e => e)

      if (result instanceof Error) {
        // If failed, inventory should be released
        const availability = await getAvailability(booking.flightId)
        expect(availability).toBe(originalAvailability)
      } else {
        // If succeeded, booking should be valid
        const retrievedBooking = await getBooking(result.pnr)
        expect(retrievedBooking).toBeDefined()
      }
    })
  })
})

Suite 5: Loyalty Program

describe('Loyalty Program - Spend Based', () => {
  it('tracks spend and upgrades tier automatically', async () => {
    // Create member
    const member = await enrollLoyaltyMember({
      email: 'frequent@test.com',
      firstName: 'Frequent',
      lastName: 'Flyer'
    })
    expect(member.tier).toBe('MEMBER')
    expect(member.currentSpend).toBe(0)

    // Book flights totaling $1,200
    await createAndCompleteBooking({
      memberEmail: 'frequent@test.com',
      amount: 600
    })
    await createAndCompleteBooking({
      memberEmail: 'frequent@test.com',
      amount: 600
    })

    // Should be Silver now
    const updatedMember = await getLoyaltyAccount('frequent@test.com')
    expect(updatedMember.tier).toBe('SILVER')
    expect(updatedMember.currentSpend).toBe(1200)
  })

  it('applies tier benefits during booking', async () => {
    // Gold member gets free seat selection
    const goldMember = await createGoldMember()

    const booking = await createBooking({
      memberEmail: goldMember.email,
      flightId: testFlight.id
    })

    // Premium seat should be free
    const seatSelection = await selectPremiumSeat(booking.id, '1A')
    expect(seatSelection.fee).toBe(0)
  })

  it('handles rolling 12-month qualification window', async () => {
    // Member with spend from 13 months ago
    const member = await createMemberWithHistoricalSpend({
      spendDate: monthsAgo(13),
      amount: 2000
    })

    // Should have dropped spend
    const account = await getLoyaltyAccount(member.email)
    expect(account.currentSpend).toBe(0)
    expect(account.tier).toBe('MEMBER') // Dropped from Silver
  })
})

Suite 6: Load & Concurrency

describe('Load & Concurrency Tests', () => {
  it('handles 100 concurrent bookings', async () => {
    const flight = await createFlightWithInventory({ economy: 100 })

    // 100 concurrent booking attempts
    const results = await Promise.allSettled(
      Array(100).fill(null).map(() =>
        createBookingWithPayment(flight.id, 1)
      )
    )

    const successes = results.filter(r => r.status === 'fulfilled')
    const failures = results.filter(r => r.status === 'rejected')

    expect(successes.length).toBe(100)
    expect(failures.length).toBe(0)

    // Verify inventory is accurate
    const finalAvailability = await getAvailability(flight.id)
    expect(finalAvailability).toBe(0)
  })

  it('handles flash sale scenario', async () => {
    // 10 seats, 50 concurrent requests
    const flight = await createFlightWithInventory({ economy: 10 })

    const results = await Promise.allSettled(
      Array(50).fill(null).map(() =>
        createBookingWithPayment(flight.id, 1)
      )
    )

    const successes = results.filter(r => r.status === 'fulfilled')

    // Exactly 10 should succeed
    expect(successes.length).toBe(10)

    // Inventory should be exactly 0
    expect(await getAvailability(flight.id)).toBe(0)
  })

  it('maintains response times under load', async () => {
    const flight = await createFlightWithInventory({ economy: 1000 })

    const startTime = Date.now()

    // 50 concurrent searches
    await Promise.all(
      Array(50).fill(null).map(() =>
        searchFlights({
          origin: 'CCS',
          destination: 'MIA',
          date: '2024-06-15'
        })
      )
    )

    const duration = Date.now() - startTime
    const avgLatency = duration / 50

    expect(avgLatency).toBeLessThan(200) // <200ms average
  })
})

Test Infrastructure

Test Database Strategy

// prisma/seed.test.ts
import { PrismaClient } from '@prisma/client'
import { execSync } from 'child_process'

const prisma = new PrismaClient()

export async function setupTestDatabase() {
  // Create isolated test database
  const testDbUrl = process.env.TEST_DATABASE_URL

  // Run migrations
  execSync(`DATABASE_URL=${testDbUrl} npx prisma migrate deploy`)

  // Seed base data
  await seedTestData(prisma)
}

export async function teardownTestDatabase() {
  // Clean all data
  await prisma.$executeRaw`TRUNCATE TABLE bookings CASCADE`
  await prisma.$executeRaw`TRUNCATE TABLE flights CASCADE`
  // ... etc
}

export async function resetBetweenTests() {
  // Fast reset using transactions
  await prisma.$executeRaw`BEGIN`
  // Tests run here
  await prisma.$executeRaw`ROLLBACK`
}

Time Travel Utilities

// test/utils/time-travel.ts
let mockNow: Date | null = null

export function timeTravelTo(date: Date) {
  mockNow = date
  jest.setSystemTime(date)
}

export function timeTravelForward(duration: number) {
  const newTime = new Date((mockNow || new Date()).getTime() + duration)
  timeTravelTo(newTime)
}

export function resetTime() {
  mockNow = null
  jest.useRealTimers()
}

// Helpers
export const hours = (n: number) => n * 60 * 60 * 1000
export const minutes = (n: number) => n * 60 * 1000
export const hoursBeforeFlight = (n: number, flight: Flight) =>
  new Date(flight.scheduledDeparture.getTime() - hours(n))

Test Data Factories

// test/factories/booking.factory.ts
import { faker } from '@faker-js/faker'

export function buildPassenger(overrides = {}) {
  return {
    firstName: faker.person.firstName(),
    lastName: faker.person.lastName(),
    dateOfBirth: faker.date.birthdate({ min: 18, max: 65, mode: 'age' }),
    passengerType: 'ADULT',
    ...overrides
  }
}

export function buildBookingInput(overrides = {}) {
  return {
    passengers: [buildPassenger()],
    contact: {
      email: faker.internet.email(),
      phone: faker.phone.number()
    },
    ...overrides
  }
}

export async function createTicketedBooking(overrides = {}) {
  const booking = await api.createBooking(buildBookingInput(overrides))
  await api.processPayment(booking.id, { token: 'tok_visa_test' })
  return api.getBooking(booking.pnr)
}

CI/CD Integration

GitHub Actions Workflow

# .github/workflows/full-flight-test.yml
name: Full Flight Simulation

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [main]

jobs:
  static-analysis:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run typecheck
      - run: npm run lint
      - run: npm run format:check

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm run test:unit -- --coverage
      - uses: codecov/codecov-action@v3

  integration-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: aura_test
        ports:
          - 5432:5432
      redis:
        image: redis:7
        ports:
          - 6379:6379
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_test
      - run: npm run test:integration
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_test
          REDIS_URL: redis://localhost:6379

  e2e-full-journey:
    runs-on: ubuntu-latest
    needs: [unit-tests, integration-tests]
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: aura_e2e
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_e2e

      # Run full flight simulation
      - name: Full Passenger Journey Tests
        run: npm run test:e2e:journey
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_e2e

      # Run all edge cases
      - name: Edge Case Tests
        run: npm run test:e2e:edge-cases
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_e2e

      # Run disruption scenarios
      - name: Disruption & Recovery Tests
        run: npm run test:e2e:disruption
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_e2e

      # Run load tests
      - name: Concurrency Tests
        run: npm run test:e2e:load
        env:
          DATABASE_URL: postgresql://postgres:test@localhost:5432/aura_e2e

  # Gate - All tests must pass
  test-gate:
    runs-on: ubuntu-latest
    needs: [static-analysis, unit-tests, integration-tests, e2e-full-journey]
    steps:
      - run: echo "All tests passed - PR is ready for review"

Test Scripts

// package.json
{
  "scripts": {
    "test": "npm run test:unit && npm run test:integration && npm run test:e2e",
    "test:unit": "vitest run --project unit",
    "test:integration": "vitest run --project integration",
    "test:e2e": "vitest run --project e2e",
    "test:e2e:journey": "vitest run --project e2e --testPathPattern=journey",
    "test:e2e:edge-cases": "vitest run --project e2e --testPathPattern=edge-cases",
    "test:e2e:disruption": "vitest run --project e2e --testPathPattern=disruption",
    "test:e2e:load": "vitest run --project e2e --testPathPattern=load",
    "test:watch": "vitest watch",
    "test:coverage": "vitest run --coverage"
  }
}

Test Coverage Requirements

Minimum Coverage Gates

LayerMinimum Coverage
Unit Tests80%
Integration Tests70%
E2E Journey Tests100% of happy paths
Edge Cases50+ scenarios

Critical Path Coverage (Must be 100%)

  • Booking creation flow
  • Payment processing
  • Ticket issuance
  • Check-in (web + kiosk)
  • Boarding pass generation
  • Boarding gate scan
  • Inventory management
  • Cancellation & refunds
  • Disruption rebooking

Research Topics

  • Vitest vs Jest for parallel test execution
  • Test containers for isolated database instances
  • Playwright for kiosk/web UI testing
  • k6 for load testing integration
  • Test data generation strategies
  • Snapshot testing for API contracts
  • Visual regression testing for boarding passes
  • Chaos engineering integration (Gremlin/Litmus)