Skip to content

chore: sql tests#338

Merged
lukepolo merged 13 commits into
mainfrom
claude/sql-system-testing-9fvbeb
Jul 6, 2026
Merged

chore: sql tests#338
lukepolo merged 13 commits into
mainfrom
claude/sql-system-testing-9fvbeb

Conversation

@lukepolo

@lukepolo lukepolo commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

No description provided.

claude added 13 commits July 3, 2026 21:36
… rosters

- add shared testcontainers boot helper (bootMigratedDb / seedRegionWithServer /
  runAsUser) that runs the real HasuraService.setup() pipeline and provisions
  the fivestack.app_key GUC needed by the servers trigger
- add match-lifecycle spec: lineup provisioning, region resolution, map
  materialization, pending-match guard, scheduling rules, Live/Veto
  redirection, check-in auto-cancel, server release, delete cleanup
- add player-stats spec: kill/assist/death/headshot/weapon aggregates,
  decrement-on-delete reparse balance, season attribution on/off
- add team-rosters spec: owner bootstrap, invite conversion, captain rules,
  lineup captain election, ban enforcement, VAC flag clearing
- fix seasons spec: seed a region+server so tbi_match's region sanitizer
  passes on a fresh install, compare timestamptz columns as ISO strings, drop
  an enum insert that violates e_match_types.description NOT NULL

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
…oring

- map-veto spec: rulebook patterns (BO1/BO3), type/turn/side/pool enforcement,
  auto-Decider with map materialization and Live transition, Pick+Side side
  assignment, pick deletion removing its map, cancel wiping picks; documents
  that out-of-phase picks are gated by the Hasura permission function
  (lineup_is_picking_map_veto), not verify_map_veto_pick
- region-veto spec: region cleared on Veto entry, turn order, LAN ban guard,
  last-available-region guard, auto-Decider locking the region and going Live
  (or staying in Veto when map veto is pending), cancel wiping picks
- match-scoring spec: latest-round-snapshot scoring, BO1/BO3 series winner
  determination via update_match_state, tied maps deciding nothing, forfeit
  protection, map Live/Paused arming and disarming the live-match timeout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
- tournaments spec: status-transition guards (stages required, past start
  date, non-organizer, manual finish), stage size validation, registration
  roster copy + eligibility (below-minimum, capacity overflow, revocation),
  single-elim bracket seeding on registration close, winner propagation into
  the final, automatic finish with trophy placement, min-teams auto-cancel,
  leave protection after the tournament is decided, trophy toggle wipe/rebuild
- lobbies spec: creator bootstrap as accepted captain, single-lobby membership
  on accept, captain succession on leave, lobby teardown when the last
  accepted player leaves
- match-options spec: edit lock after finish, invite-code lock outside
  PickingPlayers, structural field freeze during Live, cosmetic edits still
  allowed, map re-sync on pool swap, custom-pool garbage collection
- widen PostgresService bindings type with boolean/null (accepted by pg at
  runtime; lets specs pass booleans without casts)
- clean up cleanup order in tournaments beforeEach: matches before
  tournaments, otherwise bracket cascade triggers modify mid-delete tuples

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
- add test/utils/fixtures.ts: a typed Fixtures builder (players, map pools,
  match options, matches, bare imported matches, teams+rosters, seasons,
  kills/assists, round scores) that seeds through the real triggers, and
  refactor the SQL specs onto it, removing the per-spec copies of the same
  seed helpers
- expose HasuraService from bootMigratedDb so suites can drive apply()
- add test/fixtures.spec.ts guarding the LOAD_FIXTURES=true dev seeding path:
  fixtures.sql applies cleanly on a fresh install, seeds the documented
  dataset, re-enables every trigger it disabled, and cleanup.sql + re-apply
  round-trips (the scripts write with triggers disabled, so nothing else
  catches schema drift)
- fix hasura/fixtures/fixtures.sql: the LAN game-server node referenced a
  'LAN' region that was never inserted (the install seed spells it 'Lan'),
  so loading fixtures on a fresh database failed the game_server_nodes
  region FK; insert 'Lan' and point the node at it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
…bugs

New specs:
- tournament-stages: RoundRobin full-schedule seeding, per-round scheduling,
  complete-table playthrough; Swiss (Valve format, 16 teams) pool structure,
  W/L pool routing, exact results-distribution playthrough; stage minimum
  halving propagation; RoundRobin -> SingleElimination multi-stage
  advancement with the stage-1 leader among the advancers
- tournament-reset: preview chain, downstream match deletion with per-feeder
  slot clearing, source match reopening, finished-tournament rollback with
  trophy revocation, corrected-winner repropagation, and the live/foreign-
  winner/non-bracket guards
- shared TournamentFixtures helper for tournament flows

SQL fixes (found by the new tests):
- find_adjacent_swiss_team: assigning NULL to a RECORD leaves it unassigned,
  so the IS NOT NULL checks raised 'record preferred_pool is not assigned
  yet' the first time an odd Swiss pool needed cross-pool pairing, breaking
  result reporting; track candidate pools with scalars instead. 8-team Swiss
  still surfaces a clean 'Odd number of teams in pool' error (the Valve pool
  math needs 16 teams) — documented by a regression test.
- tai_player_kills: headshot_percentage is now also set on the very first
  kill (insert path) instead of reading 0% until the second kill; same for
  the season-stats insert
- verify_map_veto_pick: reject picks when no veto step is active instead of
  letting NULL comparisons silently pass every guard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
…ollision

New coverage:
- tournament-stages DoubleElimination: WB/LB/grand-final bracket shape,
  loser drops into the losers bracket, WB-final loser meeting the LB
  survivor, grand final pairing WB champion vs LB champion, full
  playthrough with an undefeated champion and 6 win/loss pairs
- stat-recompute spec: finalized-round gating (events only count once
  their round lands), per-map kill/headshot/death/assist/damage
  aggregation, team-kill exclusion, exclusive multi-kill buckets, round
  deletion dropping events back out, the app.skip_round_recompute bulk
  import switch, and detect_round_clutch won/lost/saved/none outcomes
- Fixtures: kills/damage/rounds now carry team sides, rounds and damage
  builders added

SQL fix (found by the new tests):
- assign_team_to_bracket_slot ranked a bracket's feeders by round and
  match_number only; the grand final's two feeders (WB final, LB final)
  tie on both, so the ordering was non-deterministic, both winners
  resolved to the same slot, and the second finisher overwrote the
  first. The half-filled grand final then resolved as a bye: the match
  was never played and the losers-bracket winner was crowned over the
  undefeated WB champion. Add a WB-before-LB ordering key, mirrored in
  reset_tournament_match's slot-clearing so resets unwind the same slots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
- elo spec: 5000 baseline with symmetric expectations, rating chaining
  across matches (the favorite's expected score rises), series map
  differential scaling (2-0 sweep moves ratings twice as far as 2-1),
  recompute idempotency (delete + reinsert per match), external-source
  and undecided-match guards, per-season ladder isolation (new season
  restarts from baseline with its own season_id), tournament matches
  rating on the season-independent track, and the loss-protection
  transform (strong performers on losing teams lose less)
- scrims/invites spec: already-rostered invite guard, one open scrim
  request per team pair in either direction (partial unique on
  LEAST/GREATEST), a resolved pair can re-request, terminal statuses
  clear actionable notifications while outcome notifications survive,
  and deleting a matched scrim's match cancels the request while
  freezing match_outcome and per-team check-in state for reputation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
… counters

- servers-and-nodes spec: node creation populating on-demand servers across
  the port range (paired game/tv ports), dedicated servers claiming the
  lowest node slot (requested port overridden) and releasing it on delete,
  rcon passwords encrypted at rest with re-encryption on change, the node
  server guards (no type change, no node removal, no orphan node servers),
  node deletion cascading its servers, and region server counts following
  node enablement
- pools-players-clips spec: map-pool membership sync re-syncing pending
  matches on a map swap, refusing to shrink a pool below a pending match's
  best_of, leaving Live matches untouched; registered-name uniqueness and
  the role-hierarchy guards (cannot touch peers/superiors, cannot promote
  beyond own role, within-ceiling promotion works); match_maps clip summary
  counters through insert, publish, and delete

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
- draft-membership spec: join status resolution (host auto-accept,
  open-room accept, approval rooms requesting, host invites bypassing
  approval), the 5000 default elo snapshot, capacity waitlisting with
  promotion when seats free up, single-draft membership on accept, host
  succession to the oldest accepted member, draft teardown when the last
  player leaves or anyone leaves a started draft
- streams-and-ranks spec: match stream priority reordering (move up
  pushes displaced streams down, move down pulls them up, deletion
  compacts the sequence) and premier-rank history rollback
  (players.premier_rank follows the latest remaining observation and
  clears when history empties)

With this the trigger surface is covered end to end; the only remaining
uncovered trigger bodies are two updated_at stamps and the
settings-driven update_map_pools() hook, whose function does not exist
in this repo (defined externally).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
Correction to the previous commit note: update_map_pools() does exist
(hasura/enums/maps.sql). The taiu_settings trigger fires it when the
update_map_pools setting flips to 'true'; the new test drops a map from
the seeded Competitive pool and asserts the hook restores it from the
active roster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
- add .github/workflows/tests.yml: a fast unit job (typecheck of the test
  tree via the new tsconfig.spec.json, then src unit tests) and a sql job
  that runs the 22 testcontainers-backed SQL suites against the real
  migration pipeline (Docker ships on GitHub-hosted runners)
- add test:unit / test:sql / typecheck:tests scripts
- add tsconfig.spec.json so test files are actually typechecked — the root
  tsconfig only includes src/, which is how two impossible assertions
  reached main earlier on this branch's findings list
- fix a latent type error in the kubernetes-client-node jest stub that the
  new typecheck surfaced

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
…esult-report deadlock

New coverage:
- views spec: v_player_match_map_hltv (per-round rates + the HLTV 2.0
  formula, per-hit damage capping documented), v_match_clutches,
  v_player_elo ledger semantics, get_player_elo profile aggregation across
  seasons-off / seasons-on / tournament tracks, v_team_ranks roster
  averaging that ignores missing sources, v_team_reputation (completed
  scrims, no-shows pinned to the non-checked-in team even after match GC,
  late cancels charged only to the bailing team), and the elo leaderboard
- permissions spec: can_check_in (window, membership, Captains setting),
  can_start_match (organizer path, empty lineups, both-lineups-checked-in
  path), can_cancel_match (organizer-only, never after a result),
  is_in_another_match (live/imminent only), is_in_lobby / is_in_draft,
  the escalating matchmaking cooldown scoped to the player's own session,
  and can_change_team_role (owner / roster admin / administrator)
- concurrency spec: simultaneous veto picks serialize to exactly one Ban,
  simultaneous semifinal reports both propagate into distinct final slots,
  and racing waitlist promotions never over-fill a draft
- fixtures spec: warm re-apply — wipe the stored file digests and re-run
  setup() over a data-full database, proving every enum/function/view/
  trigger file applies cleanly against live data with all triggers left
  enabled (the upgrade path production actually takes)

SQL fix (found by the concurrency spec):
- update_tournament_bracket: two results reported at the same moment
  interleaved row locks in opposite orders (own bracket -> shared parent ->
  standings cache -> start-time sweep) and deadlocked; Postgres aborted one
  transaction, silently losing that match result. Lock the stage row first
  so every reporter takes locks in the same order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
Bug #12: calculate_tournament_trophies derived silver and bronze from
v_team_stage_results, but with a third-place match the runner-up and the
decider's winner both finish 1-1, so the view's wins-based tiebreakers
(round ratio, team KDR, team id) picked the medals arbitrarily. Medals now
come straight from the brackets when the final stage is SingleElimination
with a played third-place decider: final winner/loser take gold/silver,
the decider's winner takes bronze. Anything else (RoundRobin last rounds,
unfinished deciders) falls through to the view-based placements as before.

New coverage:
- test/tournament-edge-cases.spec.ts: byes for 5/6-team fields, the
  third-place decider routing both semifinal losers and awarding bronze
  to its winner (plus the trophies leaderboard mirroring all three
  medals), a seeded-PRNG upset run across 16-team Swiss landing on the
  exact Valve pool distribution, and a deep reset of a quarterfinal after
  the bracket finished, replayed to completion.
- test/views.spec.ts: get_leaderboard categories best_kdr (deathless
  fallback, kill/death detail), best_win_rate, highest_hs_pct, the day
  window (0 = all time), get_player_leaderboard_rank, and the invalid
  category error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gwbJtuPEvjR2D2eyGdz6U
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@lukepolo lukepolo changed the title Claude/sql system testing 9fvbeb chore: sql tests Jul 6, 2026
@lukepolo lukepolo merged commit 2b56dbe into main Jul 6, 2026
3 checks passed
@lukepolo lukepolo deleted the claude/sql-system-testing-9fvbeb branch July 6, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants