require "rails_helper" require "base64" RSpec.describe "Tournaments", type: :request do let!(:coach) do User.create!(email: "torneo-owner@test.it", name: "Owner", password: "Password123", role: "coach") end let!(:club) do c = Club.create!(name: "Torneo Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") ClubMembership.create!(user: coach, club: c, role: "owner") c end let!(:team) { club.teams.create!(name: "U15", sport: "volleyball") } def login! post public_login_path, params: { email: coach.email, password: "Password123" } end def assign_plan!(slug) Billing::AssignPlan.call(club: club, plan_slug: slug) club.reload end it "non crea tornei senza Premium Full" do assign_plan!("premium_light") login! expect { post public_club_tournaments_path(club), params: { tournament: { name: "Memorial", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current + 1, format_kind: "mixed" } } }.not_to change(Tournament, :count) expect(response).to redirect_to(public_club_billing_path(club)) end it "sulla società Free il pulsante Tornei ha il nastrino FULL" do assign_plan!("free") login! get public_club_path(club) expect(response).to have_http_status(:ok) expect(response.body).to include("btn-gated__ribbon") expect(response.body).to include(">FULL<") expect(response.body).to include(public_club_billing_path(club)) expect(response.body).not_to include(%(href="#{public_club_tournaments_path(club)}")) end it "dalla società apre l'elenco tornei, non in dashboard" do assign_plan!("premium_full") login! tournament = Tournaments::Create.call( club: club, attrs: { name: "Open Elenco", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current + 1, format_kind: "free" } ) get public_club_path(club) expect(response.body).to include(public_club_tournaments_path(club)) expect(response.body).not_to include("btn-gated__ribbon") expect(response.body).not_to include("Open Elenco") expect(response.body).not_to include(public_new_club_tournament_path(club)) get public_club_tournaments_path(club) expect(response).to have_http_status(:ok) expect(response.body).to include("Open Elenco") expect(response.body).to include(public_new_club_tournament_path(club)) expect(response.body).to include(public_tournament_path(tournament)) end it "crea il torneo, iscrive squadre, genera gironi e pubblica la pagina" do assign_plan!("premium_full") login! expect { post public_club_tournaments_path(club), params: { tournament: { name: "Memorial Estate", sport_key: "pallavolo", venue: "Palestra A", starts_on: Date.current, ends_on: Date.current + 1, format_kind: "mixed", courts: "Campo 1\nCampo 2", knockout_size: 4 } } }.to change(Tournament, :count).by(1) tournament = Tournament.order(:created_at).last expect(tournament.broadcast_team).to be_present expect(tournament.broadcast_team.tournament_broadcast?).to be(true) expect(tournament.groups.count).to eq(2) expect(response).to redirect_to(public_tournament_path(tournament)) a = tournament.groups.order(:position).first b = tournament.groups.order(:position).last post public_tournament_participants_path(tournament), params: { tournament_participant: { name: "Alfa", group_id: a.id } } post public_tournament_participants_path(tournament), params: { tournament_participant: { name: "Beta", group_id: a.id } } post public_tournament_participants_path(tournament), params: { tournament_participant: { name: "Gamma", group_id: b.id } } post public_tournament_participants_path(tournament), params: { tournament_participant: { name: "Delta", group_id: b.id } } expect(tournament.participants.count).to eq(4) post public_generate_group_matches_tournament_path(tournament) expect(tournament.matches.count).to be > 0 expect(club.teams.visible.map(&:id)).to include(team.id) expect(club.teams.visible.map(&:id)).not_to include(tournament.broadcast_team_id) match = tournament.matches.first home = match.home_participant away = match.away_participant post public_tournament_match_result_path(tournament, match), params: { home_score: 2, away_score: 0, tab: "calendario" } expect(match.reload.played?).to be(true) expect(match.winner_participant_id).to eq(home.id) standings = Tournaments::Standings.call(match.tournament_group) expect(standings.first.participant).to eq(home) expect(standings.first.points).to eq(3) tournament.matches.where.associated(:tournament_group).where.not(id: match.id).find_each do |other| post public_tournament_match_result_path(tournament, other), params: { home_score: 2, away_score: 0, tab: "calendario" } end post public_propose_knockout_tournament_path(tournament) final = tournament.rounds.find_by(kind: "final").matches.first expect(final).to be_present expect(final.home_source_kind).to eq("winner_match") expect(final.away_source_kind).to eq("winner_match") get public_tournament_path(tournament, tab: "tabellone") expect(response.body).to include("Semifinali e finali") expect(response.body).to include("Compila tabellone") expect(response.body).to include("match[home_participant_id]") semi = tournament.rounds.find_by(kind: "semifinal").matches.first patch public_tournament_match_path(tournament, semi), params: { tab: "tabellone", match: { home_participant_id: tournament.participants.find_by!(name: "Alfa").id, away_participant_id: tournament.participants.find_by!(name: "Gamma").id } } expect(response).to redirect_to(public_tournament_path(tournament, tab: "tabellone")) expect(semi.reload.home_participant.name).to eq("Alfa") expect(semi.away_participant.name).to eq("Gamma") tournament.rounds.find_by(kind: "semifinal").matches.each do |semi| post public_tournament_match_result_path(tournament, semi), params: { home_score: 2, away_score: 0, tab: "tabellone" } end expect(final.reload.home_participant).to be_present expect(final.away_participant).to be_present expect(final.home_participant_id).not_to eq(final.away_participant_id) delete public_logout_path get public_tournament_page_path(tournament.slug) expect(response).to have_http_status(:not_found) login! post public_publish_tournament_path(tournament) get public_tournament_page_path(tournament.slug) expect(response).to have_http_status(:ok) expect(response.body).to include("Memorial Estate") expect(response.body).to include(home.name) expect(response.body).to include("Risultati") expect(response.body).to include("Tabellone") get public_tournament_page_path(tournament.slug, tab: "tabellone") expect(response.body).to include("Classifica") expect(response.body).to include(home.name) get public_tournament_pages_path expect(response).to have_http_status(:ok) expect(response.body).to include("Memorial Estate") expect(response.body).to include(public_tournament_page_path(tournament.slug)) get public_live_index_path expect(response.body).to include("Memorial Estate") expect(response.body).to include("Segui il cartellone") end it "delega per campo/giornata e accetta l'invito" do assign_plan!("premium_full") login! post public_club_tournaments_path(club), params: { tournament: { name: "Open Day", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free", courts: "Campo 2" } } tournament = Tournament.order(:created_at).last home = tournament.participants.create!(name: "Casa") away = tournament.participants.create!(name: "Ospite") match = Tournaments::ScheduleMatch.call( tournament: tournament, attrs: { home_participant_id: home.id, away_participant_id: away.id, court: "Campo 2", scheduled_at: Time.zone.now.change(hour: 10) } ) expect { post public_tournament_invitations_path(tournament), params: { email: "volontario@test.it", note: "Porta il treppiede", email_html: "
Testo personalizzato campo 2
{{link_invito}}
", scope_kind: "court_day", court: "Campo 2", on_date: Date.current } }.to change { tournament.broadcast_invitations.count }.by(1) .and change { ActionMailer::Base.deliveries.size }.by(1) invitation = tournament.broadcast_invitations.last expect(invitation.note).to eq("Porta il treppiede") expect(invitation.email_html).to include("Testo personalizzato campo 2") mail = ActionMailer::Base.deliveries.last html = (mail.html_part || mail).body.decoded expect(html).to include("Testo personalizzato campo 2") expect(html).to include("/join/") token = nil follow_redirect! expect(response.body).to include("/join/") token = flash[:invite_url].to_s.split("/join/").last token = CGI.unescapeHTML(response.body[/\/join\/([^"<\s]+)/, 1]) if token.blank? expect(token).to be_present volunteer = User.create!(email: "volontario@test.it", name: "Volo", password: "Password123", role: "volunteer") invitation = tournament.broadcast_invitations.last invitation.accept!(volunteer) expect(volunteer.can_broadcast_match?(match)).to be(true) expect(volunteer.can_broadcast_match?(Match.new(team: team))).to be(false) end it "aggiorna orario, campo e logo squadra" do assign_plan!("premium_full") login! post public_club_tournaments_path(club), params: { tournament: { name: "Memorial Logo", sport_key: "pallavolo", venue: "Palestra A", starts_on: Date.current, ends_on: Date.current + 1, format_kind: "groups", courts: "Campo 1\nCampo 2" } } tournament = Tournament.order(:created_at).last group = tournament.groups.order(:position).first alfa = tournament.participants.create!(name: "Alfa", group: group) beta = tournament.participants.create!(name: "Beta", group: group) post public_generate_group_matches_tournament_path(tournament) match = tournament.matches.first new_at = Time.zone.local(Date.current.year, Date.current.month, Date.current.day, 16, 30) patch public_tournament_match_path(tournament, match), params: { match: { court: "Campo 2", scheduled_at: new_at.strftime("%Y-%m-%dT%H:%M") } } match.reload expect(match.court).to eq("Campo 2") expect(match.scheduled_at).to eq(new_at) expect(match.location).to include("Campo 2") png = nil png = Tempfile.new(["logo", ".png"]) png.binmode png.write(Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")) png.rewind patch public_tournament_participant_path(tournament, alfa), params: { tournament_participant: { name: "Alfa Blu", group_id: group.id, logo_file: Rack::Test::UploadedFile.new(png.path, "image/png", original_filename: "logo.png") } } alfa.reload expect(alfa.name).to eq("Alfa Blu") expect(alfa.logo_file).to be_attached expect(alfa.effective_logo_url).to be_present if match.away_participant_id == alfa.id expect(match.reload.opponent_name).to eq("Alfa Blu") end ensure png&.close! end it "carica il logo del torneo e lo mostra in pubblico" do assign_plan!("premium_full") login! tournament = Tournaments::Create.call( club: club, attrs: { name: "Open Logo", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free" } ) png = Tempfile.new(["torneo-logo", ".png"]) png.binmode png.write(Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")) png.rewind patch public_tournament_path(tournament), params: { tournament: { logo_file: Rack::Test::UploadedFile.new(png.path, "image/png", original_filename: "torneo.png") } } expect(tournament.reload.logo_file).to be_attached expect(tournament.effective_logo_url).to be_present post public_publish_tournament_path(tournament) get public_tournament_page_path(tournament.slug) expect(response.body).to include("tournament-public-hero__logo") expect(response.body).to include(tournament.effective_logo_url) get public_tournament_pages_path expect(response.body).to include("tournament-directory-card") expect(response.body).to include(tournament.effective_logo_url) ensure png&.close! end it "inverte casa e ospite con un click" do assign_plan!("premium_full") login! tournament = Tournaments::Create.call( club: club, attrs: { name: "Memorial Swap", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free", courts: "Campo 1" } ) home = tournament.participants.create!(name: "Casa") away = tournament.participants.create!(name: "Ospite") match = Tournaments::ScheduleMatch.call( tournament: tournament, attrs: { home_participant_id: home.id, away_participant_id: away.id, court: "Campo 1", scheduled_at: Time.zone.now.change(hour: 11) } ) Tournaments::RecordResult.call(match: match, home_score: 2, away_score: 0, source: "manual") get public_tournament_path(tournament, tab: "calendario") expect(response.body).to include("tournament-match-swap__btn") expect(response.body).to include("tournament-match-row") expect(response.body).to include(public_swap_tournament_match_path(tournament, match)) expect(response.body).to include("tournament-calendar.js") post public_swap_tournament_match_path(tournament, match), as: :json expect(response).to have_http_status(:ok) expect(response).not_to be_redirect json = JSON.parse(response.body) expect(json["ok"]).to be(true) expect(json["matchup_label"]).to include("Ospite") expect(json["matchup_label"]).to include("Casa") match.reload expect(match.home_participant_id).to eq(away.id) expect(match.away_participant_id).to eq(home.id) expect(match.home_score).to eq(0) expect(match.away_score).to eq(2) expect(match.winner_participant_id).to eq(home.id) expect(match.opponent_name).to eq("Casa") end it "invita e gestisce lo streaming dal piano dirette" do assign_plan!("premium_full") login! tournament = Tournaments::Create.call( club: club, attrs: { name: "Memorial Streaming", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free", courts: "Campo 1" } ) tournament.update!( invite_draft_email: "bozza@test.it", invite_draft_html: "Bozza salvata
{{link_invito}}
", invite_draft_saved_at: Time.current ) home = tournament.participants.create!(name: "Casa") away = tournament.participants.create!(name: "Ospite") match = Tournaments::ScheduleMatch.call( tournament: tournament, attrs: { home_participant_id: home.id, away_participant_id: away.id, court: "Campo 1", scheduled_at: Time.zone.now.change(hour: 11) } ) other = Tournaments::ScheduleMatch.call( tournament: tournament, attrs: { home_participant_id: away.id, away_participant_id: home.id, court: "Campo 1", scheduled_at: Time.zone.now.change(hour: 13) } ) get public_tournament_path(tournament, tab: "delega") expect(response).to redirect_to(public_tournament_path(tournament, tab: "dirette")) get public_tournament_path(tournament, tab: "dirette") expect(response.body).to include("Piano dirette") expect(response.body).to include("Invita selezionate") expect(response.body).to include("plan_match_#{match.id}") expect(response.body).to include("2 da coprire") get public_tournament_path(tournament, tab: "calendario") expect(response.body).not_to include("Da coprire") expect(response.body).not_to include(">Streaming<") expect { post public_tournament_invitations_path(tournament), params: { email: "op.cal@test.it", tab: "dirette", match_ids: [match.id] } }.to change { tournament.broadcast_invitations.count }.by(1) .and change { ActionMailer::Base.deliveries.size }.by(1) invitation = tournament.broadcast_invitations.last expect(invitation.scope_kind).to eq("matches") expect(invitation.email_html).to include("Bozza salvata") expect(invitation.covers_match?(match)).to be(true) expect(invitation.covers_match?(other)).to be(false) follow_redirect! expect(response.body).to include("Piano dirette") expect(response.body).to include("tournament-streaming-chip--pending") expect(response.body).to include("tournament-live-status--waiting_operator") expect(response.body).not_to include("plan_match_#{match.id}") expect(response.body).to include("plan_match_#{other.id}") volunteer = User.create!(email: "op.cal@test.it", name: "Operatore Cal", password: "Password123", role: "volunteer") invitation.accept!(volunteer) expect(volunteer.can_broadcast_match?(match)).to be(true) expect(volunteer.can_broadcast_match?(other)).to be(false) get public_tournament_path(tournament, tab: "dirette") expect(response.body).to include("Operatore Cal") expect(response.body).not_to include("tournament-streaming-chip--pending") session = StreamSession.create!(match: match, user: volunteer, platform: "matchlivetv", status: "live") get public_tournament_path(tournament, tab: "dirette") expect(response.body).to include("tournament-live-status--live") expect(response.body).to include("In diretta") session.update_columns(status: "ended", ended_at: Time.current) get public_tournament_path(tournament, tab: "dirette") expect(response.body).to include("tournament-live-status--ended") get public_tournament_path(tournament, tab: "calendario") expect(response.body).not_to include("Operatore Cal") expect(response.body).not_to include("Invita selezionate") assignment = match.broadcast_assignments.find_by!(user: volunteer) delete public_tournament_assignment_path(tournament, assignment) follow_redirect! expect(volunteer.reload.can_broadcast_match?(match)).to be(false) expect(response.body).to include("Piano dirette") expect(response.body).to include("tournament-live-status--ended") expect(response.body).not_to include("Operatore Cal") end it "revoca un assegnamento senza togliere le altre gare della stessa delega" do assign_plan!("premium_full") login! tournament = Tournaments::Create.call( club: club, attrs: { name: "Memorial Court Day", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free", courts: "Campo 2" } ) home = tournament.participants.create!(name: "Casa") away = tournament.participants.create!(name: "Ospite") match = Tournaments::ScheduleMatch.call( tournament: tournament, attrs: { home_participant_id: home.id, away_participant_id: away.id, court: "Campo 2", scheduled_at: Time.zone.now.change(hour: 10) } ) other = Tournaments::ScheduleMatch.call( tournament: tournament, attrs: { home_participant_id: away.id, away_participant_id: home.id, court: "Campo 2", scheduled_at: Time.zone.now.change(hour: 12) } ) post public_tournament_invitations_path(tournament), params: { email: "campo@test.it", scope_kind: "court_day", court: "Campo 2", on_date: Date.current } volunteer = User.create!(email: "campo@test.it", name: "Campo Op", password: "Password123", role: "volunteer") invitation = tournament.broadcast_invitations.last invitation.accept!(volunteer) expect(volunteer.can_broadcast_match?(match)).to be(true) expect(volunteer.can_broadcast_match?(other)).to be(true) assignment = match.broadcast_assignments.find_by!(user: volunteer) delete public_tournament_assignment_path(tournament, assignment) expect(volunteer.reload.can_broadcast_match?(match)).to be(false) expect(volunteer.can_broadcast_match?(other)).to be(true) expect(invitation.reload.scope_kind).to eq("matches") expect(invitation.match_ids.map(&:to_s)).to contain_exactly(other.id.to_s) end it "carica un'immagine per la mail di delega" do assign_plan!("premium_full") login! post public_club_tournaments_path(club), params: { tournament: { name: "Memorial Mail", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free", courts: "Campo 1" } } tournament = Tournament.order(:created_at).last png = Tempfile.new(["invite", ".png"]) png.binmode png.write(Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")) png.rewind post public_tournament_invite_images_path(tournament), params: { file: Rack::Test::UploadedFile.new(png.path, "image/png", original_filename: "logo.png") } expect(response).to have_http_status(:ok) json = JSON.parse(response.body) expect(json["url"]).to include("/rails/active_storage") expect(tournament.reload.invite_email_images).to be_attached ensure png&.close! end it "salva la bozza della mail senza inviarla" do assign_plan!("premium_full") login! post public_club_tournaments_path(club), params: { tournament: { name: "Memorial Bozza", sport_key: "pallavolo", starts_on: Date.current, ends_on: Date.current, format_kind: "free", courts: "Campo 1" } } tournament = Tournament.order(:created_at).last expect { post public_tournament_invite_draft_path(tournament), params: { email: "dopo@test.it", note: "Porta il treppiede", email_html: %(Testo bozza con {{link_invito}}
