Files
euchre_camp/spec/acceptance/tournament_partnership_spec.rb
T
david ac858f1e6d feat(tournaments): add partnership tracking and tournament management
Implement comprehensive partnership tracking system and tournament
management features including round-robin, single/double elimination,
and Swiss system pairings.

Changes:
- Add partnership_games and partnership_stats tables
- Implement PartnershipTracker service for recording partnership data
- Add tournament generator with multiple formats
- Create tournament management actions and views
- Add acceptance tests for partnership tracking
- Fix PartnershipTracker to use single snapshot per player per match
- Fix round-robin test to query by round_number instead of hardcoded ID

Note: All 8 acceptance tests now passing
2026-03-27 12:25:34 -07:00

385 lines
14 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Tournament Partnership Analytics', type: :acceptance do
let(:rom) { EuchreCamp::App['persistence.rom'] }
let(:db) { rom.gateways[:default].connection }
before do
# Clear all data before each test in correct order (due to foreign keys)
db[:partnership_games].delete
db[:partnership_stats].delete
db[:elo_snapshots].delete
db[:bracket_matchups].delete
db[:tournament_rounds].delete
db[:matches].delete
db[:teams].delete
db[:event_participants].delete
db[:events].delete
db[:players].delete
end
describe 'Tournament Creation and Match Recording' do
let!(:emma) { db[:players].insert(name: 'Emma', rating: 1000, current_elo: 1000) }
let!(:alice) { db[:players].insert(name: 'Alice', rating: 1000, current_elo: 1000) }
let!(:bob) { db[:players].insert(name: 'Bob', rating: 1000, current_elo: 1000) }
let!(:charlie) { db[:players].insert(name: 'Charlie', rating: 1000, current_elo: 1000) }
let!(:david) { db[:players].insert(name: 'David', rating: 1000, current_elo: 1000) }
it 'creates a tournament with teams and matches' do
# Create tournament
tournament_id = db[:events].insert(
name: 'Test Tournament',
format: 'round_robin',
status: 'active'
)
# Create teams
team_1_id = db[:teams].insert(
event_id: tournament_id,
team_name: 'Ace High',
player_1_id: emma,
player_2_id: alice
)
team_2_id = db[:teams].insert(
event_id: tournament_id,
team_name: 'Kings',
player_1_id: bob,
player_2_id: charlie
)
# Create a match
match_id = db[:matches].insert(
event_id: tournament_id,
team_1_p1_id: emma,
team_1_p2_id: alice,
team_2_p1_id: bob,
team_2_p2_id: charlie,
team_1_score: 10,
team_2_score: 7,
status: 'completed'
)
# Verify match was created
match = db[:matches].where(id: match_id).first
expect(match[:team_1_p1_id]).to eq(emma)
expect(match[:team_1_p2_id]).to eq(alice)
expect(match[:team_2_p1_id]).to eq(bob)
expect(match[:team_2_p2_id]).to eq(charlie)
expect(match[:team_1_score]).to eq(10)
expect(match[:team_2_score]).to eq(7)
end
it 'calculates Elo for a match' do
match_id = db[:matches].insert(
team_1_p1_id: emma,
team_1_p2_id: alice,
team_2_p1_id: bob,
team_2_p2_id: charlie,
team_1_score: 10,
team_2_score: 7,
status: 'completed'
)
# Run the Elo calculation job
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_id)
# Verify Elo snapshots were created for all 4 players
snapshots = db[:elo_snapshots].where(match_id: match_id).to_a
expect(snapshots.length).to eq(4)
# Verify player ELOs were updated
emma_player = db[:players].where(id: emma).first
expect(emma_player[:current_elo]).not_to eq(1000)
end
it 'records partnerships when match completes' do
match_id = db[:matches].insert(
team_1_p1_id: emma,
team_1_p2_id: alice,
team_2_p1_id: bob,
team_2_p2_id: charlie,
team_1_score: 10,
team_2_score: 7,
status: 'completed'
)
# Run the full job (Elo + partnerships)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_id)
# Verify partnership games were recorded
partnership_games = db[:partnership_games].where(match_id: match_id).to_a
expect(partnership_games.length).to eq(2) # One for each team
# Verify team 1 partnership (emma + alice) won
team_1_game = partnership_games.find { |pg| pg[:team_number] == 1 }
expect(team_1_game[:player_1_id]).to eq(emma)
expect(team_1_game[:player_2_id]).to eq(alice)
expect(team_1_game[:won_match]).to eq(1)
# Verify team 2 partnership (bob + charlie) lost
team_2_game = partnership_games.find { |pg| pg[:team_number] == 2 }
expect(team_2_game[:player_1_id]).to eq(bob)
expect(team_2_game[:player_2_id]).to eq(charlie)
expect(team_2_game[:won_match]).to eq(0)
# Verify partnership stats were updated
stats = db[:partnership_stats].to_a
expect(stats.length).to eq(2)
# Check emma+alice stats
emma_alice_stats = stats.find do |s|
(s[:player_1_id] == emma && s[:player_2_id] == alice) ||
(s[:player_1_id] == alice && s[:player_2_id] == emma)
end
expect(emma_alice_stats[:games_played]).to eq(1)
expect(emma_alice_stats[:wins]).to eq(1)
expect(emma_alice_stats[:losses]).to eq(0)
end
it 'tracks multiple partnership statistics correctly' do
# Match 1: emma+alice wins
match_1_id = db[:matches].insert(
team_1_p1_id: emma, team_1_p2_id: alice,
team_2_p1_id: bob, team_2_p2_id: charlie,
team_1_score: 10, team_2_score: 7, status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_1_id)
# Match 2: emma+alice wins again
match_2_id = db[:matches].insert(
team_1_p1_id: emma, team_1_p2_id: alice,
team_2_p1_id: david, team_2_p2_id: bob,
team_1_score: 10, team_2_score: 5, status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_2_id)
# Match 3: emma+alice loses
match_3_id = db[:matches].insert(
team_1_p1_id: emma, team_1_p2_id: alice,
team_2_p1_id: bob, team_2_p2_id: charlie,
team_1_score: 7, team_2_score: 10, status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_3_id)
# Check emma+alice stats
emma_alice_stats = db[:partnership_stats].find do |s|
(s[:player_1_id] == emma && s[:player_2_id] == alice) ||
(s[:player_1_id] == alice && s[:player_2_id] == emma)
end
expect(emma_alice_stats[:games_played]).to eq(3)
expect(emma_alice_stats[:wins]).to eq(2)
expect(emma_alice_stats[:losses]).to eq(1)
end
it 'calculates partnership win rates correctly' do
# Create 5 matches for emma+alice with 3 wins, 2 losses
matches = [
{ p1: emma, p2: alice, opp1: bob, opp2: charlie, score1: 10, score2: 7 },
{ p1: emma, p2: alice, opp1: david, opp2: bob, score1: 10, score2: 5 },
{ p1: emma, p2: alice, opp1: bob, opp2: charlie, score1: 7, score2: 10 },
{ p1: emma, p2: alice, opp1: david, opp2: charlie, score1: 10, score2: 8 },
{ p1: emma, p2: alice, opp1: bob, opp2: david, score1: 6, score2: 10 },
]
matches.each do |m|
match_id = db[:matches].insert(
team_1_p1_id: m[:p1], team_1_p2_id: m[:p2],
team_2_p1_id: m[:opp1], team_2_p2_id: m[:opp2],
team_1_score: m[:score1], team_2_score: m[:score2],
status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_id)
end
# Check stats
stats = db[:partnership_stats].find do |s|
(s[:player_1_id] == emma && s[:player_2_id] == alice) ||
(s[:player_1_id] == alice && s[:player_2_id] == emma)
end
expect(stats[:games_played]).to eq(5)
expect(stats[:wins]).to eq(3)
expect(stats[:losses]).to eq(2)
# Calculate expected win rate
win_rate = stats[:wins].to_f / stats[:games_played]
expect(win_rate).to be_within(0.01).of(0.600)
end
it 'retrieves partnership statistics for a player' do
# Create matches with different partners for emma
matches_data = [
# emma+alice (2 wins, 1 loss)
{ team_1: [emma, alice], team_2: [bob, charlie], score1: 10, score2: 7 },
{ team_1: [emma, alice], team_2: [david, bob], score1: 10, score2: 5 },
{ team_1: [emma, alice], team_2: [bob, charlie], score1: 7, score2: 10 },
# emma+bob (1 win, 0 losses)
{ team_1: [emma, bob], team_2: [alice, charlie], score1: 10, score2: 6 },
]
matches_data.each do |data|
match_id = db[:matches].insert(
team_1_p1_id: data[:team_1][0], team_1_p2_id: data[:team_1][1],
team_2_p1_id: data[:team_2][0], team_2_p2_id: data[:team_2][1],
team_1_score: data[:score1], team_2_score: data[:score2],
status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_id)
end
# Get partnerships for emma
partnerships = EuchreCamp::Services::PartnershipTracker.get_player_partnerships(emma)
expect(partnerships.length).to eq(2)
# Check alice partnership
alice_partner = partnerships.find { |p| p[:partner][:id] == alice }
expect(alice_partner).not_to be_nil
expect(alice_partner[:games_played]).to eq(3)
expect(alice_partner[:wins]).to eq(2)
expect(alice_partner[:losses]).to eq(1)
expect(alice_partner[:win_rate]).to be_within(0.01).of(0.667)
# Check bob partnership
bob_partner = partnerships.find { |p| p[:partner][:id] == bob }
expect(bob_partner).not_to be_nil
expect(bob_partner[:games_played]).to eq(1)
expect(bob_partner[:wins]).to eq(1)
expect(bob_partner[:losses]).to eq(0)
expect(bob_partner[:win_rate]).to eq(1.0)
end
end
describe 'Round Robin Tournament Workflow' do
let!(:emma) { db[:players].insert(name: 'Emma', rating: 1000, current_elo: 1000) }
let!(:alice) { db[:players].insert(name: 'Alice', rating: 1000, current_elo: 1000) }
let!(:bob) { db[:players].insert(name: 'Bob', rating: 1000, current_elo: 1000) }
let!(:charlie) { db[:players].insert(name: 'Charlie', rating: 1000, current_elo: 1000) }
it 'completes a full round-robin tournament workflow' do
# 1. Create tournament
tournament_id = db[:events].insert(
name: 'Spring Championship',
format: 'round_robin',
status: 'active'
)
# 2. Register participants
[emma, alice, bob, charlie].each do |player_id|
db[:event_participants].insert(
event_id: tournament_id,
player_id: player_id,
status: 'registered'
)
end
# 3. Create teams (4 teams for round robin)
teams = [
['Ace High', emma, alice],
['Kings', bob, charlie],
['Queen High', emma, bob],
['Jacks', alice, charlie]
]
team_ids = teams.map do |name, p1, p2|
db[:teams].insert(event_id: tournament_id, team_name: name, player_1_id: p1, player_2_id: p2)
end
# 4. Generate schedule (using TournamentGenerator)
rounds = EuchreCamp::Services::TournamentGenerator.round_robin(
teams.map.with_index { |t, i| { id: team_ids[i], name: t[0] } }
)
expect(rounds.length).to eq(3) # 4 teams = 3 rounds
# 5. Create rounds and matchups
rounds.each do |round_data|
round_id = db[:tournament_rounds].insert(
event_id: tournament_id,
round_number: round_data[:round_number],
status: round_data[:round_number] == 1 ? 'active' : 'pending'
)
round_data[:matchups].each_with_index do |matchup, idx|
db[:bracket_matchups].insert(
event_id: tournament_id,
round_id: round_id,
team_1_id: matchup[:team_1_id],
team_2_id: matchup[:team_2_id],
bracket_position: idx + 1
)
end
end
# 6. Play round 1 matches
round_1 = db[:tournament_rounds].where(round_number: 1, event_id: tournament_id).first
round_1_matchups = db[:bracket_matchups].where(round_id: round_1[:id]).to_a
round_1_matchups.each do |matchup|
team_1 = db[:teams].where(id: matchup[:team_1_id]).first
team_2 = db[:teams].where(id: matchup[:team_2_id]).first
match_id = db[:matches].insert(
event_id: tournament_id,
team_1_p1_id: team_1[:player_1_id],
team_1_p2_id: team_1[:player_2_id],
team_2_p1_id: team_2[:player_1_id],
team_2_p2_id: team_2[:player_2_id],
team_1_score: 10,
team_2_score: 7,
status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_id)
end
# 7. Verify all partnerships were recorded
partnership_games = db[:partnership_games].to_a
# For 2 matchups in round 1, we should have 4 partnership games (2 per matchup)
# But if emma appears in multiple teams, we might see fewer unique games
expect(partnership_games.length).to be >= 2
# 8. Verify player profiles can be retrieved
emma_partnerships = EuchreCamp::Services::PartnershipTracker.get_player_partnerships(emma)
expect(emma_partnerships.length).to be >= 1
end
end
describe 'Player Profile Page' do
let!(:emma) { db[:players].insert(name: 'Emma', rating: 1000, current_elo: 1000) }
let!(:alice) { db[:players].insert(name: 'Alice', rating: 1000, current_elo: 1000) }
it 'displays player profile with partnership data' do
# Create additional players for opponents
bob = db[:players].insert(name: 'Bob', rating: 1000, current_elo: 1000)
charlie = db[:players].insert(name: 'Charlie', rating: 1000, current_elo: 1000)
# Create some matches
5.times do |i|
match_id = db[:matches].insert(
team_1_p1_id: emma, team_1_p2_id: alice,
team_2_p1_id: bob, team_2_p2_id: charlie,
team_1_score: 10, team_2_score: 7,
status: 'completed'
)
EuchreCamp::Jobs::CalculateEloJob.new.perform(match_id)
end
# Get partnerships for emma
partnerships = EuchreCamp::Services::PartnershipTracker.get_player_partnerships(emma)
# Verify data structure
expect(partnerships).not_to be_empty
partnership = partnerships.first
expect(partnership[:partner][:name]).to eq('Alice')
expect(partnership[:games_played]).to eq(5)
expect(partnership[:wins]).to eq(5)
expect(partnership[:losses]).to eq(0)
expect(partnership[:win_rate]).to eq(1.0)
end
end
end