Aggiunge i tornei con hub, pagina pubblica e tabellone per semifinali e finali.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -41,11 +41,15 @@ RSpec.describe "Admin club replays", type: :request do
|
||||
end
|
||||
|
||||
it "elimina un replay" do
|
||||
storage = instance_double(Recordings::Storage, delete: true)
|
||||
allow(Recordings::Storage).to receive(:new).and_return(storage)
|
||||
|
||||
delete admin_club_recording_path(club, recording)
|
||||
|
||||
expect(response).to redirect_to(admin_club_recordings_path(club))
|
||||
expect(recording.reload.status).to eq("expired")
|
||||
expect(recording.deleted_at).to be_present
|
||||
expect(storage).to have_received(:delete).at_least(:once)
|
||||
end
|
||||
|
||||
it "prolunga la scadenza con giorni aggiuntivi" do
|
||||
|
||||
@@ -145,9 +145,13 @@ RSpec.describe "Public regia", type: :request do
|
||||
set_always_available: true,
|
||||
set_path_recording: true,
|
||||
delete_path: true,
|
||||
list_paths: []
|
||||
list_paths: [],
|
||||
ensure_path_with_slate!: true,
|
||||
disable_recording_if_active!: true
|
||||
)
|
||||
allow(Mediamtx::Client).to receive(:new).and_return(mtx)
|
||||
allow(Mediamtx::Client).to receive(:for_session).and_return(mtx)
|
||||
allow(Mediamtx::PublisherSync).to receive(:remember_recording_off!)
|
||||
|
||||
token = Sessions::RegiaAccess.new(session).issue_token!
|
||||
post public_regia_pause_path(token)
|
||||
|
||||
@@ -0,0 +1,783 @@
|
||||
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: "<p>Testo personalizzato campo 2</p><p>{{link_invito}}</p>",
|
||||
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(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: "<p>Bozza salvata</p><p>{{link_invito}}</p>",
|
||||
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: %(<p>Testo bozza con {{link_invito}}</p><p><img src="https://www.example.com/logo.png" alt=""></p>)
|
||||
}
|
||||
}.not_to change { ActionMailer::Base.deliveries.size }
|
||||
|
||||
tournament.reload
|
||||
expect(tournament.invite_draft_note).to eq("Porta il treppiede")
|
||||
expect(tournament.invite_draft_email).to eq("dopo@test.it")
|
||||
expect(tournament.invite_draft_html).to include("Testo bozza")
|
||||
expect(tournament.invite_draft_html).to include("{{link_invito}}")
|
||||
expect(tournament.invite_draft_html).to include("logo.png")
|
||||
expect(tournament.invite_draft_saved_at).to be_present
|
||||
|
||||
get public_tournament_path(tournament, tab: "dirette")
|
||||
expect(response.body).to include("Testo bozza")
|
||||
expect(response.body).to include("Porta il treppiede")
|
||||
expect(response.body).to include("dopo@test.it")
|
||||
expect(response.body).to include("Salva bozza")
|
||||
expect(response.body).to include("Piano dirette")
|
||||
expect(response.body).to include("Testo della mail")
|
||||
expect(response.body).to include("Invita un operatore")
|
||||
end
|
||||
|
||||
it "elimina il torneo, le partite e il team interno" do
|
||||
assign_plan!("premium_full")
|
||||
login!
|
||||
tournament = Tournaments::Create.call(
|
||||
club: club,
|
||||
attrs: {
|
||||
name: "Memorial da cancellare",
|
||||
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: 10)
|
||||
}
|
||||
)
|
||||
session = StreamSession.create!(
|
||||
match: match, user: coach, platform: "matchlivetv", status: "ended", ended_at: 1.hour.ago
|
||||
)
|
||||
Recording.create!(
|
||||
stream_session: session, team: tournament.broadcast_team, status: "ready", privacy_status: "public",
|
||||
title: "Replay torneo",
|
||||
storage_key: "teams/#{tournament.broadcast_team_id}/sessions/#{session.id}/replay.mp4"
|
||||
)
|
||||
team_id = tournament.broadcast_team_id
|
||||
match_id = match.id
|
||||
|
||||
get public_tournament_path(tournament)
|
||||
expect(response.body).to include("Elimina")
|
||||
|
||||
expect {
|
||||
delete public_tournament_path(tournament)
|
||||
}.to change(Tournament, :count).by(-1)
|
||||
.and change(Match, :count).by(-1)
|
||||
.and change(Team, :count).by(-1)
|
||||
|
||||
expect(response).to redirect_to(public_club_tournaments_path(club))
|
||||
expect(Tournament.find_by(id: tournament.id)).to be_nil
|
||||
expect(Match.find_by(id: match_id)).to be_nil
|
||||
expect(Team.find_by(id: team_id)).to be_nil
|
||||
expect(Recording.find_by(stream_session_id: session.id)).to be_nil
|
||||
|
||||
get public_tournament_path(tournament.id)
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "non elimina il torneo se una diretta è in corso" do
|
||||
assign_plan!("premium_full")
|
||||
login!
|
||||
tournament = Tournaments::Create.call(
|
||||
club: club,
|
||||
attrs: {
|
||||
name: "Memorial live",
|
||||
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: 18)
|
||||
}
|
||||
)
|
||||
StreamSession.create!(match: match, user: coach, platform: "matchlivetv", status: "live")
|
||||
|
||||
expect {
|
||||
delete public_tournament_path(tournament)
|
||||
}.not_to change(Tournament, :count)
|
||||
expect(response).to redirect_to(public_tournament_path(tournament))
|
||||
follow_redirect!
|
||||
expect(response.body).to include("Chiudi le dirette")
|
||||
end
|
||||
|
||||
it "l'owner elimina il torneo anche senza Premium Full" do
|
||||
assign_plan!("premium_full")
|
||||
tournament = Tournaments::Create.call(
|
||||
club: club,
|
||||
attrs: {
|
||||
name: "Memorial declassato",
|
||||
sport_key: "pallavolo",
|
||||
starts_on: Date.current,
|
||||
ends_on: Date.current,
|
||||
format_kind: "free"
|
||||
}
|
||||
)
|
||||
assign_plan!("premium_light")
|
||||
login!
|
||||
|
||||
expect {
|
||||
delete public_tournament_path(tournament)
|
||||
}.to change(Tournament, :count).by(-1)
|
||||
expect(response).to redirect_to(public_club_tournaments_path(club))
|
||||
end
|
||||
|
||||
it "mostra sul cartellone i link a diretta e replay" do
|
||||
assign_plan!("premium_full")
|
||||
login!
|
||||
tournament = Tournaments::Create.call(
|
||||
club: club,
|
||||
attrs: {
|
||||
name: "Open Cartellone",
|
||||
sport_key: "pallavolo",
|
||||
starts_on: Date.current,
|
||||
ends_on: Date.current + 1,
|
||||
format_kind: "free",
|
||||
courts: "Campo 1"
|
||||
}
|
||||
)
|
||||
home = tournament.participants.create!(name: "Alfa")
|
||||
away = tournament.participants.create!(name: "Beta")
|
||||
match = Tournaments::ScheduleMatch.call(
|
||||
tournament: tournament,
|
||||
attrs: {
|
||||
home_participant_id: home.id,
|
||||
away_participant_id: away.id,
|
||||
court: "Campo 1",
|
||||
scheduled_at: 2.hours.from_now
|
||||
}
|
||||
)
|
||||
post public_publish_tournament_path(tournament)
|
||||
|
||||
session = StreamSession.create!(
|
||||
match: match, user: coach, platform: "matchlivetv", status: "live", privacy_status: "public"
|
||||
)
|
||||
get public_tournament_page_path(tournament.slug)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("Risultati")
|
||||
expect(response.body).to include("Guarda diretta")
|
||||
expect(response.body).to include(public_live_path(session))
|
||||
expect(response.body).to include("tournament-live-status--live")
|
||||
|
||||
session.update_columns(status: "ended", ended_at: Time.current)
|
||||
Recording.create!(
|
||||
stream_session: session, team: tournament.broadcast_team, status: "ready", privacy_status: "public",
|
||||
title: "Replay Alfa-Beta",
|
||||
storage_key: "teams/#{tournament.broadcast_team_id}/sessions/#{session.id}/replay.mp4"
|
||||
)
|
||||
|
||||
get public_tournament_page_path(tournament.slug)
|
||||
expect(response.body).to include("Guarda replay")
|
||||
expect(response.body).to include(public_replay_path(session.id))
|
||||
expect(response.body).to include("tournament-live-status--replay")
|
||||
expect(response.body).to include("Replay Alfa-Beta")
|
||||
|
||||
get public_club_tournaments_path(club)
|
||||
expect(response.body).to include(public_tournament_page_path(tournament.slug))
|
||||
end
|
||||
end
|
||||
@@ -35,6 +35,8 @@ RSpec.describe Recordings::CleanupLocal do
|
||||
stream_session: session, team: team, status: "ready", privacy_status: "public",
|
||||
storage_key: "teams/#{team.id}/sessions/#{session.id}/replay.mp4"
|
||||
)
|
||||
mtx = instance_double(Mediamtx::Client, delete_path: true)
|
||||
allow(Mediamtx::Client).to receive(:for_session).and_return(mtx)
|
||||
|
||||
result = described_class.new(online_paths: []).call
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ RSpec.describe Streams::Autoscaler do
|
||||
redis.del(Streams::DnsProviders::Lab::REDIS_KEY)
|
||||
StreamSession.where.not(stream_node_id: nil).update_all(stream_node_id: nil, status: "ended", ended_at: Time.current)
|
||||
StreamNode.where.not(slug: Streams::NodeRegistry::HOME_SLUG).delete_all
|
||||
ENV["STREAM_AUTOSCALE_QUIET_HOURS"] = "off"
|
||||
end
|
||||
|
||||
after do
|
||||
ENV.delete("STREAM_AUTOSCALE_QUIET_HOURS")
|
||||
end
|
||||
|
||||
it "is a no-op when disabled" do
|
||||
@@ -261,4 +266,34 @@ RSpec.describe Streams::Autoscaler do
|
||||
expect(node.reload.status).to eq("ready")
|
||||
end
|
||||
end
|
||||
|
||||
it "blocks scale-out and warm spare during quiet hours" do
|
||||
with_env(
|
||||
"STREAM_AUTOSCALE_ENABLED" => "1",
|
||||
"STREAM_AUTOSCALE_QUIET_HOURS" => "02:00-07:00",
|
||||
"STREAM_AUTOSCALE_SOFT_FREE_SLOTS" => "2",
|
||||
"STREAM_AUTOSCALE_WARM_SPARE" => "1",
|
||||
"STREAM_AUTOSCALE_KIND" => "lab",
|
||||
"STREAM_AUTOSCALE_MAX_NODES" => "5",
|
||||
"STREAM_NODE_HOME_MAX_PUBLISHERS" => "1",
|
||||
"MEDIAMTX_API_URL" => "http://mtx-home:9997",
|
||||
"MEDIAMTX_RTMP_URL" => "rtmp://home.example:1935",
|
||||
"HLS_PUBLIC_URL" => "https://home.example/hls"
|
||||
) do
|
||||
allow(Streams::QuietHours).to receive(:active?).and_return(true)
|
||||
home = Streams::NodeRegistry.ensure_home_from_env!
|
||||
user = User.create!(email: "qh@example.com", name: "Q", password: "Password123", role: "coach")
|
||||
club = Club.create!(name: "QH", sport: "volleyball")
|
||||
team = club.teams.create!(name: "T", sport: "volleyball", slug: "qh-t")
|
||||
match = team.matches.create!(opponent_name: "X", scheduled_at: 1.hour.from_now)
|
||||
StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "live", stream_node: home)
|
||||
|
||||
provisioner = instance_double(Streams::NodeProvisioner)
|
||||
expect(provisioner).not_to receive(:provision_lab!)
|
||||
|
||||
result = described_class.reconcile!(provisioner: provisioner)
|
||||
expect(result.actions).to include(:blocked_capacity)
|
||||
expect(result.metrics[:quiet_hours]).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Streams::NightCloudSweeper do
|
||||
def with_env(vars)
|
||||
previous = vars.keys.index_with { |k| ENV[k] }
|
||||
vars.each { |k, v| ENV[k] = v }
|
||||
yield
|
||||
ensure
|
||||
previous.each { |k, v| v.nil? ? ENV.delete(k) : ENV[k] = v }
|
||||
end
|
||||
|
||||
def cloud_node!(slug: "ingest-cloud-01")
|
||||
StreamNode.create!(
|
||||
slug: slug,
|
||||
hostname: "#{slug}.mltv-stream.net",
|
||||
role: "cloud",
|
||||
status: "ready",
|
||||
provider: "hetzner",
|
||||
provider_instance_id: "42",
|
||||
rtmp_base_url: "rtmp://#{slug}:1935",
|
||||
hls_base_url: "https://#{slug}/hls",
|
||||
api_base_url: "http://10.0.0.9:9997",
|
||||
max_publishers: 6,
|
||||
max_relays: 6,
|
||||
metadata: { "public_ip" => "1.2.3.4" }
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
StreamSession.where.not(stream_node_id: nil).update_all(stream_node_id: nil, status: "ended", ended_at: Time.current)
|
||||
StreamNode.where(role: "cloud").delete_all
|
||||
end
|
||||
|
||||
it "skips outside quiet hours" do
|
||||
with_env(
|
||||
"STREAM_NIGHT_SWEEP_ENABLED" => "1",
|
||||
"STREAM_AUTOSCALE_QUIET_HOURS" => "02:00-07:00",
|
||||
"STREAM_AUTOSCALE_QUIET_TZ" => "Europe/Rome"
|
||||
) do
|
||||
allow(Streams::QuietHours).to receive(:active?).and_return(false)
|
||||
cloud_node!
|
||||
result = described_class.sweep!
|
||||
expect(result.skipped).to eq(true)
|
||||
expect(StreamNode.where(role: "cloud").count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
it "decommissions idle cloud nodes during quiet hours" do
|
||||
with_env("STREAM_NIGHT_SWEEP_ENABLED" => "1") do
|
||||
allow(Streams::QuietHours).to receive(:active?).and_return(true)
|
||||
node = cloud_node!
|
||||
provisioner = instance_double(Streams::NodeProvisioner)
|
||||
allow(provisioner).to receive(:drain!) do |n|
|
||||
n.update!(status: "draining")
|
||||
n
|
||||
end
|
||||
allow(provisioner).to receive(:decommission!) do |n|
|
||||
n.destroy!
|
||||
true
|
||||
end
|
||||
|
||||
result = described_class.sweep!(provisioner: provisioner)
|
||||
expect(result.skipped).to eq(false)
|
||||
expect(result.actions).to include(:"decommission_#{node.slug}")
|
||||
expect(StreamNode.find_by(id: node.id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "alerts and keeps nodes with active sessions" do
|
||||
with_env("STREAM_NIGHT_SWEEP_ENABLED" => "1") do
|
||||
allow(Streams::QuietHours).to receive(:active?).and_return(true)
|
||||
node = cloud_node!
|
||||
user = User.create!(email: "night@example.com", name: "N", password: "Password123", role: "coach")
|
||||
club = Club.create!(name: "Night", sport: "volleyball")
|
||||
team = club.teams.create!(name: "T", sport: "volleyball", slug: "night-t")
|
||||
match = team.matches.create!(opponent_name: "X", scheduled_at: 1.hour.from_now)
|
||||
StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "live", stream_node: node)
|
||||
|
||||
allow(Ops::IncidentRecorder).to receive(:record)
|
||||
provisioner = instance_double(Streams::NodeProvisioner)
|
||||
expect(provisioner).not_to receive(:decommission!)
|
||||
|
||||
result = described_class.sweep!(provisioner: provisioner)
|
||||
expect(result.actions).to include(:"alert_active_#{node.slug}")
|
||||
expect(Ops::IncidentRecorder).to have_received(:record).with(hash_including(
|
||||
fingerprint: "stream_overflow:night_active:#{node.slug}"
|
||||
))
|
||||
expect(node.reload.status).to eq("ready")
|
||||
end
|
||||
end
|
||||
|
||||
it "does not touch lab nodes" do
|
||||
with_env("STREAM_NIGHT_SWEEP_ENABLED" => "1") do
|
||||
allow(Streams::QuietHours).to receive(:active?).and_return(true)
|
||||
StreamNode.create!(
|
||||
slug: "ingest-lab-night-01",
|
||||
hostname: "lab-night.local",
|
||||
role: "lab",
|
||||
status: "ready",
|
||||
provider: "local",
|
||||
rtmp_base_url: "rtmp://lab:1935",
|
||||
hls_base_url: "https://lab/hls",
|
||||
api_base_url: "http://lab:9997",
|
||||
max_publishers: 2,
|
||||
max_relays: 2
|
||||
)
|
||||
result = described_class.sweep!
|
||||
expect(result.actions).to eq([])
|
||||
expect(StreamNode.find_by(slug: "ingest-lab-night-01")).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Streams::QuietHours do
|
||||
def with_env(vars)
|
||||
previous = vars.keys.index_with { |k| ENV[k] }
|
||||
vars.each { |k, v| ENV[k] = v }
|
||||
yield
|
||||
ensure
|
||||
previous.each { |k, v| v.nil? ? ENV.delete(k) : ENV[k] = v }
|
||||
end
|
||||
|
||||
it "is active inside the Rome window" do
|
||||
with_env(
|
||||
"STREAM_AUTOSCALE_QUIET_HOURS" => "02:00-07:00",
|
||||
"STREAM_AUTOSCALE_QUIET_TZ" => "Europe/Rome"
|
||||
) do
|
||||
now = Time.find_zone!("Europe/Rome").local(2026, 9, 5, 3, 30)
|
||||
expect(described_class.active?(now: now)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
it "is inactive outside the window" do
|
||||
with_env(
|
||||
"STREAM_AUTOSCALE_QUIET_HOURS" => "02:00-07:00",
|
||||
"STREAM_AUTOSCALE_QUIET_TZ" => "Europe/Rome"
|
||||
) do
|
||||
now = Time.find_zone!("Europe/Rome").local(2026, 9, 5, 10, 0)
|
||||
expect(described_class.active?(now: now)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "can be disabled with off" do
|
||||
with_env("STREAM_AUTOSCALE_QUIET_HOURS" => "off") do
|
||||
now = Time.find_zone!("Europe/Rome").local(2026, 9, 5, 3, 0)
|
||||
expect(described_class.active?(now: now)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Tournaments::ComposeInviteEmail do
|
||||
it "sostituisce i token e rimuove HTML pericoloso" do
|
||||
html = <<~HTML
|
||||
<p>Ciao custom</p>
|
||||
<script>alert(1)</script>
|
||||
<p><a href="javascript:alert(1)">x</a></p>
|
||||
<p>{{link_invito}}</p>
|
||||
<p>Scade il {{scadenza}}</p>
|
||||
<img src="javascript:alert(1)">
|
||||
<img src="/rails/active_storage/blobs/redirect/abc/logo.png" width="120">
|
||||
HTML
|
||||
|
||||
out = described_class.call(
|
||||
html: html,
|
||||
invite_url: "https://www.example.com/join/token-ok",
|
||||
expires_on: "10 settembre 2026"
|
||||
)
|
||||
|
||||
expect(out).to include("Ciao custom")
|
||||
expect(out).to include("https://www.example.com/join/token-ok")
|
||||
expect(out).to include("10 settembre 2026")
|
||||
expect(out).to include("/rails/active_storage/blobs/redirect/abc/logo.png")
|
||||
expect(out).not_to include("<script")
|
||||
expect(out).not_to include("javascript:")
|
||||
end
|
||||
|
||||
it "aggiunge il link se l'utente ha cancellato il token" do
|
||||
out = described_class.call(html: "<p>Solo testo</p>", invite_url: "https://www.example.com/join/z")
|
||||
expect(out).to include("Solo testo")
|
||||
expect(out).to include("https://www.example.com/join/z")
|
||||
end
|
||||
|
||||
it "mantiene i token del modello quando sanitizza una bozza" do
|
||||
html = %(<p>Bozza</p><p><a href="{{link_invito}}">Apri</a></p><p data-invite-note="">Nota</p>)
|
||||
out = described_class.sanitize_html(html)
|
||||
expect(out).to include("Bozza")
|
||||
expect(out).to include("{{link_invito}}")
|
||||
expect(out).to include("Nota")
|
||||
expect(out).to include("data-invite-note")
|
||||
end
|
||||
|
||||
it "compone il modello predefinito senza chiavi I18n mancanti" do
|
||||
tournament = instance_double(Tournament, name: "Open", club: instance_double(Club, name: "Tigers"))
|
||||
invited_by = instance_double(User, name: "Owner")
|
||||
html = I18n.with_locale(:it) do
|
||||
described_class.default_html(tournament: tournament, invited_by: invited_by)
|
||||
end
|
||||
expect(html).not_to include("Translation missing")
|
||||
expect(html).to include("Open")
|
||||
expect(html).to include("Tigers")
|
||||
expect(html).to include("{{link_invito}}")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user