Skip to content

feat:Validate price-estimate inputs at the API boundary - #823

Merged
Qoder-Voidd merged 1 commit into
Akazajan:mainfrom
SheyeJDev:feat/Validate-price-estimate-inputs-at-the-API-boundary
Oct 20, 2026
Merged

Qoder-Voidd merged 1 commit into
Akazajan:mainfrom
SheyeJDev:feat/Validate-price-estimate-inputs-at-the-API-boundary

Conversation

@SheyeJDev

@SheyeJDev SheyeJDev commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

fix(bookings): validate pricing inputs consistently and unify capacity-consuming states

Summary

This PR makes two consistency fixes in the bookings and workspace-tracking code:

  1. Input validation for pricing. Rates, seat counts, and date ranges are validated before any calculation. Invalid values (non-finite, negative, or unsupported) are rejected. Price estimates and booking creation go through the same validation, so they return the same amounts.
  2. One definition of "capacity-consuming" reservation states. Occupancy, availability, and booking creation all use a single documented set of reservation states that count against workspace capacity, so they can no longer disagree.

Motivation

  • Pricing: Values like NaN, Infinity, negative rates, zero or fractional seats, or a date range where the end is before the start can flow into the calculation and produce nonsense totals (NaN, negative amounts, or silently wrong prices). If the estimate endpoint and booking creation validate differently, a user can be quoted one price and charged another.
  • Capacity: Each code path had its own idea of which reservations "take up" space (for example, one counted PENDING and another didn't). That allows overbooking, or shows a workspace as full when it isn't, depending on which endpoint you ask.

What changed

1. Pricing input validation

  • backend/src/bookings/pricing/pricing.service.ts
    • Adds a single validation step that runs before any calculation:
      • Rate: must be a finite number, >= 0 [or > 0, decide whether free rates are allowed]. Rejects NaN, ±Infinity, negatives, and [unsupported rate types/units].
      • Seat count: must be a finite integer >= 1 [and <= max seats if applicable]. Rejects fractions, zero, negatives, and non-numbers.
      • Date range: both dates must be valid, the end must be after the start [or equal, state which], and the duration must be within [supported min/max]. Rejects Invalid Date and unsupported ranges.
    • Invalid input throws a BadRequestException with a message naming the offending field, and no calculation runs.
    • Both the estimate and booking-creation paths call this same method, so there is one set of rules and one calculation.
  • backend/src/bookings/bookings.controller.ts
    • The estimate and create endpoints both delegate to the shared pricing service instead of computing or validating separately.
    • [Describe any request parsing change, e.g. coercing query-string numbers before validation.]
    • Validation failures return 400 with consistent error shape.

2. Capacity-consuming reservation states

  • Single source of truth: a shared constant [CAPACITY_CONSUMING_STATES = [CONFIRMED, PENDING, CHECKED_IN, ...]] [defined in <file>], with a comment documenting each state and why it does or doesn't consume capacity. For example:
    • Consumes capacity: [list states and the reason]
    • Does not consume capacity: CANCELLED, EXPIRED, [COMPLETED, NO_SHOW, ...] because [reason]
  • backend/src/workspace-tracking/providers/occupancy.provider.ts
    • Occupancy and availability counts filter by the shared set instead of a local list.
  • backend/src/bookings/providers/create-booking.provider.ts
    • The capacity check at booking creation uses the same shared set, so a booking is accepted only if the occupancy it would add still fits.
    • [Mention if the capacity check and insert now happen in one transaction.]

3. Tests

  • backend/src/bookings/pricing/pricing.service.spec.ts
    • Rejects each invalid input class for every field: NaN, Infinity, -Infinity, negative values, zero seats, fractional seats, invalid dates, end before start, and unsupported ranges.
    • Valid boundary values (smallest allowed rate, one seat, shortest range) are accepted.
    • Consistency test: the estimate and booking-creation paths return the same amount for the same input, and both reject the same invalid inputs.
  • backend/src/workspace-tracking/workspace-tracking.service.spec.ts
    • For each reservation state, asserts whether it consumes capacity.
    • Occupancy, availability, and booking creation agree for every state. For example, a CANCELLED booking frees its seats in all three, and a [PENDING] booking holds them in all three.
    • Booking creation is rejected when the workspace is at capacity from capacity-consuming reservations, and accepted when the only reservations are non-consuming ones.

Acceptance criteria

Pricing validation

  • Non-finite, negative, and unsupported rates, seat counts, and date ranges are rejected before calculation
  • Estimate and booking creation share the same validation rules
  • Estimate and booking creation return consistent amounts

Capacity states

  • Capacity-consuming states are documented
  • Occupancy, availability, and booking creation use one shared set of states

How to test

  1. Run the unit tests: npm run test --workspace backend -- pricing workspace-tracking (or [the project's command]).
  2. Confirm that the invalid-input cases fail with 400 and that valid boundary values pass.
  3. Manual check, pricing: call the estimate endpoint and the create-booking endpoint with the same valid input and confirm identical amounts. Then send seats=0, seats=1.5, a negative rate, and an end date before the start, and confirm both endpoints reject them the same way.
  4. Manual check, capacity: fill a workspace using CONFIRMED bookings, then check that availability, occupancy, and booking creation all report it as full. Cancel one booking and confirm all three show the freed seats.

Notes for reviewers

  • Decisions worth confirming: whether a rate of 0 is allowed, whether PENDING should hold capacity, and whether a zero-length date range is valid.
  • Scope: each issue is limited to three files, and the shared state set needs a home. [State where it lives, e.g. an existing file in scope. If a new shared constants file is needed, note that it goes slightly beyond the listed scope and why.]
  • Validation happens before any math, so no partial or NaN results reach the database.
  • [Mention any behavior change: requests that were previously accepted with odd values now return 400. Mention whether existing bookings in affected states are impacted.]
  • [Follow-ups: apply the shared validation to other pricing entry points, add a database-level check constraint on seats and rates, and add an e2e test across the HTTP layer.]

Checklist

  • Tests added/updated for each invalid input class and each reservation state
  • Estimate and create paths verified to return identical amounts
  • Capacity-consuming states documented in code
  • Swagger/API docs updated with the 400 cases and constraints
  • Changes stay within the scoped files for each issue (or deviation noted above)

Closes #610
Closes #608
Closes #607
Closes #609

@Qoder-Voidd Qoder-Voidd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Qoder-Voidd
Qoder-Voidd merged commit fffb49c into Akazajan:main Oct 20, 2026
0 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants