Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesLocationsLocations (Spaces) — QA Plan

Locations (Spaces) — QA Plan

Pre-requisites

  • Test org on lite platform tier (maxSpaces=1, lets you hit the tier ceiling quickly), and one on pro (maxSpaces=2).
  • A second org for isolation tests.
  • Owner, admin, coach, member personas.
  • An org session with location_id set to a location WITH coordinates, and another session with location_id set to a location WITHOUT coordinates.
  • For the spaces section: an org with the spaces flag on, a program with a class type, an exclusive space “Main Floor” (capacity 12) and a shared space “Open Gym”.

Golden paths

G1 — Owner adds a location with autocomplete

StepActionExpected
1Owner navigates to settings → Spaces.List loads, ordered by sort order then name.
2Click add. Type address into autocomplete.Suggestions appear from Google Places.
3Pick a result.Fields populate: address, city, country, lat, lng.
4Set name + capacity. Submit.POST succeeds; row appears with non-null lat/lng.

G2 — Owner adds a location without coords (manual)

StepActionExpected
1Submit form with name only, no autocomplete.Row inserted; lat/lng null.
2Try GPS check-in on a session linked to this location.400 ‘Session location coordinates are not configured’.

G3 — Deactivate

StepActionExpected
1Delete location.is_active=false. List omits it.
2A future session still linked: opens fine; location name still resolvable for display.

Edge cases

E1 — Tier limit

StepActionExpected
1Org at lite already has 1 active space (maxSpaces=1).
2POST a new location.403 ‘Space limit reached (1/1). Upgrade your plan to add more.‘
3Upgrade to pro.Now POST succeeds.

E1a — Pro can create up to 2 spaces

StepActionExpected
1pro org with 1 active space. POST a second.201.
2POST a third.403 ‘Space limit reached (2/2). Upgrade your plan to add more.‘
3Deactivate one; POST again.201 (only active spaces count).
4Same org on elite.No limit.

E2 — Tier downgrade with over-limit

StepActionExpected
1Org at pro with locations > lite cap. Downgrade to lite.Locations remain active. (Tier check is creation-time only — verify whether a separate enforcement exists in platform-billing TODO.)

E3 — Cross-org access

StepActionExpected
1Owner of org A: GET /organizations/A/locations/{idFromB}.404.
2Same for PATCH and DELETE.404 each.

E4 — Non-staff mutation

StepActionExpected
1Coach POST/PATCH/DELETE.403.
2Member same.403.

E5 — Partial coords

StepActionExpected
1POST with latitude=32.07 and no longitude.Row inserted (both nullable).
2GPS check-in against a session linked here.400 ‘Session location coordinates are not configured’.

E6 — GPS Haversine boundary

StepActionExpected
1Location at known coords. Self-checkin from just inside GPS_MAX_METERS.Succeeds.
2From just outside GPS_MAX_METERS.400 ‘GPS_TOO_FAR’.

E7 — Concurrency and order fields

StepActionExpected
1POST without concurrency/sortOrder.Row has concurrency='exclusive', sortOrder=0.
2PATCH concurrency:'shared', sortOrder:2.Response reflects both.
3PATCH concurrency:'private' or sortOrder:-1.400.
4Three spaces with sortOrder 1, 0, 0 (names “B”, “C”, “A”). GET list.Order: A, C, B.

Spaces flag (spaces)

Single session create/edit, flag on unless stated. Times are org-local on one date. Bulk writers are covered in scheduling-bookings QA.

#ScenarioExpected
S1Session 07:00–08:00 in Main Floor (exclusive). Coach creates 07:30–08:30 in Main Floor.409 { code:'space_conflict', locationId, conflictingSessionIds:[<first id>] }. No row inserted.
S2Same as S1 in Open Gym (shared).201.
S3Session 07:00–08:00 in Main Floor. Create 08:00–09:00 in Main Floor.201 (adjacent is not overlap).
S4S1, but the existing session is cancelled, or soft-deleted.201 (ignored).
S5S1 retried by owner with overrideSpaceConflict:{ reason:'Two coaches split the floor' }.201. One audit_events row (AuditService), action class_session.space_override, category operations, target the new session, metadata { rule: 'conflict', reason, locationId, locationName, conflictingSessionIds }.
S6S1 retried by a coach with overrideSpaceConflict.403. No row, no audit.
S7Create in Main Floor (capacity 12) with capacity:15.409 { code:'space_capacity_exceeded', locationId, spaceCapacity:12, capacity:15 }.
S8S7 retried by admin with overrideSpaceCapacity:{ reason }.201; audited as in S5. Same with capacity:12 and no override: 201.
S9Overlap and capacity both broken. Owner sends only overrideSpaceConflict.409 space_capacity_exceeded. Retry with both overrides: 201.
S10Edit an existing session: PATCH only title. Its time already overlaps another in an exclusive space.200 (no check: no location/time/capacity field in the request).
S11PATCH startsAt/endsAt into an overlap in Main Floor.409 space_conflict.
S12Override sent when nothing clashes.201; override ignored, no audit row.
S13Flag off: repeat S1, S7 and S6.All 201; no 409, no 403, no audit. Behavior identical to before TKN-444.
S14Web, flag on, org with ≥2 active spaces: open Create session.Space is required. On a 409, owner/admin see an override dialog asking for a reason; coaches see the error only.

Cross-persona

  • Members can list and get-by-id; cannot mutate.
  • Coaches read but cannot mutate.

i18n

LangStrings to verify
enschedule.locationsTab.empty + emptyHint show the empty state and talk about spaces.
heSame key, Hebrew.
ruSame key, Russian.
All”Space limit reached” copy localized; “GPS_TOO_FAR” is a stable machine string the client translates via schedule.checkinPage.tooFar.
AllSettings, space pickers and the override dialog read “Space(s)”, not “Location(s)”.

Expected vs actual

  • After deactivation: is_active=false, deleted_at still NULL.
  • After autocomplete fill: latitude and longitude both non-null and within plausible bounds (-90 to 90, -180 to 180).
  • Tier check: count of is_active=true locations in the org against the tier’s cap.