Limita una diretta attiva per account e valorizza la copertina Premium Full.
Blocca la seconda diretta sullo stesso utente, registra gli abusi in admin e presenta la copertina come grafica caricata dalla società, con demo Team MLTV. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe MatchLiveTv::Demo do
|
||||
it "usa Team MLTV come squadra demo ufficiale" do
|
||||
expect(described_class.home_team).to eq("Team MLTV")
|
||||
expect(described_class.category).to eq("U15 MASCHILE")
|
||||
expect(described_class.venue).to eq("Pala MLTV")
|
||||
end
|
||||
|
||||
it "localizza l'avversario senza nomi di società reali" do
|
||||
I18n.with_locale(:it) do
|
||||
expect(described_class.away_team).to eq("Squadra ospite")
|
||||
expect(described_class.match_title).to eq("Team MLTV vs Squadra ospite")
|
||||
end
|
||||
I18n.with_locale(:en) do
|
||||
expect(described_class.away_team).to eq("Team Guest")
|
||||
expect(described_class.match_title).to eq("Team MLTV vs Team Guest")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Admin stream concurrency violations", type: :request do
|
||||
let!(:admin) { AdminAccount.create!(username: "ops-abuse", password: "Password123") }
|
||||
let!(:user) { User.create!(email: "abuse@test.it", name: "Abuser", password: "Password123", role: "coach") }
|
||||
let!(:club) { Club.create!(name: "Abuse Club", sport: "volleyball") }
|
||||
let!(:team) { club.teams.create!(name: "U16", sport: "volleyball") }
|
||||
let!(:match_a) { team.matches.create!(opponent_name: "Rival A", sport: "volleyball") }
|
||||
let!(:match_b) { team.matches.create!(opponent_name: "Rival B", sport: "volleyball") }
|
||||
let!(:live) do
|
||||
StreamSession.create!(match: match_a, user: user, platform: "matchlivetv", status: "live")
|
||||
end
|
||||
let!(:idle) do
|
||||
StreamSession.create!(match: match_b, user: user, platform: "matchlivetv", status: "idle")
|
||||
end
|
||||
let!(:violation) do
|
||||
StreamConcurrencyViolation.record!(attempted: idle, occupying: live, action: "start")
|
||||
end
|
||||
|
||||
before do
|
||||
post admin_login_path, params: { username: admin.username, password: "Password123" }
|
||||
end
|
||||
|
||||
it "mostra l'elenco abusi" do
|
||||
get admin_stream_concurrency_violations_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("abuse@test.it")
|
||||
expect(response.body).to include("Abuse Club")
|
||||
expect(response.body).to include("Rival A")
|
||||
expect(response.body).to include("Rival B")
|
||||
end
|
||||
|
||||
it "mostra il badge in dashboard" do
|
||||
get admin_root_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("abuse@test.it")
|
||||
end
|
||||
|
||||
it "mostra i tentativi nella scheda società" do
|
||||
get admin_club_path(club)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("abuse@test.it")
|
||||
expect(response.body).to include("Rival B")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "API start seconda diretta stesso account", type: :request do
|
||||
let!(:user) { User.create!(email: "conc@test.it", name: "C", password: "Password123", role: "coach") }
|
||||
let!(:club) { Club.create!(name: "Conc Club", sport: "volleyball") }
|
||||
let!(:membership) { club.club_memberships.create!(user: user, role: "owner") }
|
||||
let!(:team) { club.teams.create!(name: "Tigers", sport: "volleyball") }
|
||||
let!(:match_a) { team.matches.create!(opponent_name: "A", sport: "volleyball") }
|
||||
let!(:match_b) { team.matches.create!(opponent_name: "B", sport: "volleyball") }
|
||||
let!(:live) do
|
||||
StreamSession.create!(
|
||||
match: match_a,
|
||||
user: user,
|
||||
platform: "matchlivetv",
|
||||
status: "live",
|
||||
started_at: 5.minutes.ago,
|
||||
device_model: "Pixel 8",
|
||||
client_os: "android"
|
||||
)
|
||||
end
|
||||
let!(:idle) do
|
||||
StreamSession.create!(
|
||||
match: match_b,
|
||||
user: user,
|
||||
platform: "matchlivetv",
|
||||
status: "idle",
|
||||
device_model: "iPhone 15",
|
||||
client_os: "ios"
|
||||
)
|
||||
end
|
||||
|
||||
def auth_headers
|
||||
post "/api/v1/auth/login", params: { email: user.email, password: "Password123" }
|
||||
token = response.parsed_body["access_token"]
|
||||
{ "Authorization" => "Bearer #{token}", "Content-Type" => "application/json" }
|
||||
end
|
||||
|
||||
before do
|
||||
plan = Plan.find_or_initialize_by(slug: "premium_full")
|
||||
plan.name ||= "Premium Full"
|
||||
plan.features = (plan.features || {}).merge(
|
||||
"platforms" => %w[matchlivetv],
|
||||
"concurrent_streams_limit" => 10
|
||||
)
|
||||
plan.save!
|
||||
club.create_subscription!(plan: plan, status: "active") if club.subscription.blank?
|
||||
allow(SessionChannel).to receive(:broadcast_message)
|
||||
end
|
||||
|
||||
it "restituisce 403 user_concurrent_stream e salva l'evidenza" do
|
||||
patch "/api/v1/sessions/#{idle.id}/start", headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
body = response.parsed_body
|
||||
expect(body["error_code"]).to eq("user_concurrent_stream")
|
||||
expect(body["error"]).to be_present
|
||||
expect(StreamConcurrencyViolation.where(attempted_session_id: idle.id, occupying_session_id: live.id)).to exist
|
||||
end
|
||||
end
|
||||
@@ -16,7 +16,17 @@ RSpec.describe "Public features page", type: :request do
|
||||
expect(response.body).to include("Chi filma, filma")
|
||||
expect(response.body).to include("Accesso con link")
|
||||
expect(response.body).to include("La diretta resta della tua società")
|
||||
expect(response.body).to include("La partita finisce. Il ricordo resta.")
|
||||
expect(response.body).to include("Dai visibilità ai tuoi sponsor")
|
||||
expect(response.body).to include("Copertina caricata per ogni partita")
|
||||
expect(response.body).to include("Scopri Premium Full")
|
||||
expect(response.body).to include("Team MLTV")
|
||||
expect(response.body).to include("Squadra ospite")
|
||||
expect(response.body).to include("Copertina personalizzata")
|
||||
expect(response.body).not_to include("Lupi Santa Croce")
|
||||
expect(response.body).not_to include("Rossi vs Neri")
|
||||
expect(response.body).not_to include("Tigers Volley")
|
||||
expect(response.body).not_to include("MAIN SPONSOR")
|
||||
expect(response.body).not_to include("CON IL SUPPORTO DI")
|
||||
expect(response.body).to include("Inizia gratis")
|
||||
expect(response.body).to include("Confronta i piani")
|
||||
expect(response.body).to include(public_signup_path)
|
||||
|
||||
@@ -10,6 +10,10 @@ RSpec.describe "Public home page", type: :request do
|
||||
expect(response.body).to include(I18n.t("home.stores_label", locale: :it))
|
||||
expect(response.body).to include(I18n.t("home.store_app_store_name", locale: :it))
|
||||
expect(response.body).to include(I18n.t("home.store_play_name", locale: :it))
|
||||
expect(response.body).to include("Dai visibilità ai tuoi sponsor")
|
||||
expect(response.body).to include("Team MLTV")
|
||||
expect(response.body).not_to include("Lupi Santa Croce")
|
||||
expect(response.body).to include("Scopri Premium Full")
|
||||
expect(response.body.scan(MatchLiveTv.app_store_url).size).to eq(3)
|
||||
expect(response.body.scan(MatchLiveTv.play_store_url).size).to eq(3)
|
||||
end
|
||||
|
||||
@@ -26,6 +26,14 @@ RSpec.describe "Public pricing page", type: :request do
|
||||
expect(response.body).to include("Account trasmettitori")
|
||||
expect(response.body).to include("Niente password condivise")
|
||||
expect(response.body).to include("plan-card--featured")
|
||||
expect(response.body).to include("Copertina personalizzabile")
|
||||
expect(response.body).to include("con sponsor")
|
||||
expect(response.body).to include("Copertina personalizzata")
|
||||
expect(response.body).to include("Dai visibilità ai tuoi sponsor")
|
||||
expect(response.body).to include("Team MLTV")
|
||||
expect(response.body).not_to include("Lupi Santa Croce")
|
||||
expect(response.body).not_to include("MAIN SPONSOR")
|
||||
expect(response.body).not_to include("CON IL SUPPORTO DI")
|
||||
expect(response.body).to include("Soddisfatti o rimborsati")
|
||||
expect(response.body).to include("30 giorni soddisfatti o rimborsati")
|
||||
expect(response.body).to include("ti rimborsiamo il 100%")
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Public team page", type: :request do
|
||||
let(:club) { Club.create!(name: "ASD Eagles", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
|
||||
let(:club) { Club.create!(name: "Team MLTV", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
|
||||
let!(:team) do
|
||||
club.teams.create!(
|
||||
name: "Tigers Volley U17",
|
||||
name: "Team MLTV U15",
|
||||
sport_key: "pallavolo",
|
||||
description: "Squadra giovanile under 17.",
|
||||
description: "Squadra giovanile under 15.",
|
||||
roster_public: true
|
||||
)
|
||||
end
|
||||
@@ -15,9 +15,9 @@ RSpec.describe "Public team page", type: :request do
|
||||
get public_team_page_path(team.slug)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("Tigers Volley U17")
|
||||
expect(response.body).to include("ASD Eagles")
|
||||
expect(response.body).to include("Squadra giovanile under 17")
|
||||
expect(response.body).to include("Team MLTV U15")
|
||||
expect(response.body).to include("Team MLTV")
|
||||
expect(response.body).to include("Squadra giovanile under 15")
|
||||
end
|
||||
|
||||
it "restituisce 404 per slug inesistente" do
|
||||
@@ -41,13 +41,13 @@ RSpec.describe "Public team page", type: :request do
|
||||
end
|
||||
|
||||
RSpec.describe "Public team directory", type: :request do
|
||||
let(:club) { Club.create!(name: "ASD Eagles", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
|
||||
let(:club) { Club.create!(name: "Team MLTV", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
|
||||
let(:user) { User.create!(email: "dir2@test.com", name: "Coach", password: "Password123", role: "coach") }
|
||||
let!(:team) { club.teams.create!(name: "Tigers Volley U17", sport_key: "pallavolo") }
|
||||
let!(:team) { club.teams.create!(name: "Team MLTV U15", sport_key: "pallavolo") }
|
||||
|
||||
before do
|
||||
club.club_memberships.create!(user: user, role: "owner")
|
||||
match = team.matches.create!(opponent_name: "Rival", sport_key: "pallavolo", scheduled_at: 1.day.from_now)
|
||||
match = team.matches.create!(opponent_name: MatchLiveTv::Demo::AWAY_TEAM, sport_key: "pallavolo", scheduled_at: 1.day.from_now)
|
||||
StreamSession.create!(
|
||||
match: match, user: user, status: "live", platform: "matchlivetv",
|
||||
privacy_status: "public", publish_token: "tok-dir", started_at: Time.current
|
||||
@@ -58,13 +58,13 @@ RSpec.describe "Public team directory", type: :request do
|
||||
get public_team_pages_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("Tigers Volley U17")
|
||||
expect(response.body).to include("Team MLTV U15")
|
||||
expect(response.body).to include("In diretta")
|
||||
end
|
||||
|
||||
it "filtra per sport via query string" do
|
||||
get public_team_pages_path(sport: "basket")
|
||||
|
||||
expect(response.body).not_to include("Tigers Volley U17")
|
||||
expect(response.body).not_to include("Team MLTV U15")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Sessions::Start, "one live per user" do
|
||||
let(:user) { User.create!(email: "tx@test.it", name: "Tx", password: "Password123", role: "coach") }
|
||||
let(:club) { Club.create!(name: "Club A", sport: "volleyball") }
|
||||
let(:team) { club.teams.create!(name: "Team A", sport: "volleyball") }
|
||||
let(:match_a) { team.matches.create!(opponent_name: "Opp A", sport: "volleyball") }
|
||||
let(:match_b) { team.matches.create!(opponent_name: "Opp B", sport: "volleyball") }
|
||||
|
||||
before do
|
||||
club.club_memberships.create!(user: user, role: "owner")
|
||||
plan = Plan.find_or_initialize_by(slug: "premium_full")
|
||||
plan.name ||= "Premium Full"
|
||||
plan.features = (plan.features || {}).merge(
|
||||
"platforms" => %w[matchlivetv youtube],
|
||||
"concurrent_streams_limit" => 10
|
||||
)
|
||||
plan.save!
|
||||
club.create_subscription!(plan: plan, status: "active") if club.subscription.blank?
|
||||
allow(SessionChannel).to receive(:broadcast_message)
|
||||
end
|
||||
|
||||
def session_for(match, status:, device_model: "Pixel 8")
|
||||
StreamSession.create!(
|
||||
match: match,
|
||||
user: user,
|
||||
platform: "matchlivetv",
|
||||
status: status,
|
||||
device_manufacturer: "Google",
|
||||
device_model: device_model,
|
||||
client_os: "android"
|
||||
)
|
||||
end
|
||||
|
||||
it "avvia la prima diretta" do
|
||||
session = session_for(match_a, status: "idle")
|
||||
described_class.new(session).call
|
||||
expect(session.reload.status).to eq("connecting")
|
||||
expect(StreamConcurrencyViolation.count).to eq(0)
|
||||
end
|
||||
|
||||
it "blocca la seconda diretta dello stesso account e registra l'abuso" do
|
||||
live = session_for(match_a, status: "live", device_model: "Pixel 8")
|
||||
live.update!(started_at: 10.minutes.ago)
|
||||
second = session_for(match_b, status: "idle", device_model: "iPhone 15")
|
||||
second.update!(client_os: "ios", device_manufacturer: "Apple")
|
||||
|
||||
expect {
|
||||
described_class.new(second).call
|
||||
}.to raise_error(Teams::EntitlementError) { |e|
|
||||
expect(e.code).to eq("user_concurrent_stream")
|
||||
}
|
||||
|
||||
expect(second.reload.status).to eq("idle")
|
||||
violation = StreamConcurrencyViolation.last
|
||||
expect(violation).to be_present
|
||||
expect(violation.user_email).to eq(user.email)
|
||||
expect(violation.occupying_session_id).to eq(live.id)
|
||||
expect(violation.attempted_session_id).to eq(second.id)
|
||||
expect(violation.devices_differ).to be(true)
|
||||
expect(violation.attempt_action).to eq("start")
|
||||
end
|
||||
|
||||
it "non blocca un secondo account sulla stessa società" do
|
||||
other = User.create!(email: "tx2@test.it", name: "Tx2", password: "Password123", role: "coach")
|
||||
club.club_memberships.create!(user: other, role: "owner")
|
||||
session_for(match_a, status: "live")
|
||||
other_session = StreamSession.create!(
|
||||
match: match_b,
|
||||
user: other,
|
||||
platform: "matchlivetv",
|
||||
status: "idle"
|
||||
)
|
||||
|
||||
described_class.new(other_session).call
|
||||
expect(other_session.reload.status).to eq("connecting")
|
||||
end
|
||||
|
||||
it "considera occupante anche una sessione in pausa" do
|
||||
session_for(match_a, status: "paused")
|
||||
second = session_for(match_b, status: "idle")
|
||||
|
||||
expect {
|
||||
described_class.new(second).call
|
||||
}.to raise_error(Teams::EntitlementError) { |e|
|
||||
expect(e.code).to eq("user_concurrent_stream")
|
||||
}
|
||||
end
|
||||
|
||||
it "non considera occupante una sessione idle" do
|
||||
session_for(match_a, status: "idle")
|
||||
second = session_for(match_b, status: "idle")
|
||||
|
||||
described_class.new(second).call
|
||||
expect(second.reload.status).to eq("connecting")
|
||||
end
|
||||
end
|
||||
@@ -4,13 +4,13 @@ RSpec.describe Teams::GenerateSlug do
|
||||
let(:club) { Club.create!(name: "Test Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
|
||||
|
||||
it "genera uno slug dal nome" do
|
||||
team = club.teams.build(name: "Crazy Volley U17B", sport_key: "pallavolo")
|
||||
expect(described_class.call(team)).to eq("crazy-volley-u17b")
|
||||
team = club.teams.build(name: "Team MLTV U15", sport_key: "pallavolo")
|
||||
expect(described_class.call(team)).to eq("team-mltv-u15")
|
||||
end
|
||||
|
||||
it "aggiunge un suffisso se lo slug esiste già" do
|
||||
club.teams.create!(name: "Tigers", sport_key: "pallavolo")
|
||||
team = club.teams.build(name: "Tigers", sport_key: "pallavolo")
|
||||
expect(described_class.call(team)).to eq("tigers-2")
|
||||
club.teams.create!(name: "Team MLTV", sport_key: "pallavolo")
|
||||
team = club.teams.build(name: "Team MLTV", sport_key: "pallavolo")
|
||||
expect(described_class.call(team)).to eq("team-mltv-2")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user