Expand backend HTTP E2E API coverage
This commit is contained in:
parent
236b43edce
commit
7f04e75649
|
|
@ -67,6 +67,10 @@ module TurniereE2E
|
||||||
request(:patch, path, body: body, params: params, headers: headers)
|
request(:patch, path, body: body, params: params, headers: headers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delete(path, params: nil, headers: {})
|
||||||
|
request(:delete, path, params: params, headers: headers)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def request(method, path, body: nil, params: nil, headers: {})
|
def request(method, path, body: nil, params: nil, headers: {})
|
||||||
|
|
@ -77,6 +81,7 @@ module TurniereE2E
|
||||||
when :get then Net::HTTP::Get
|
when :get then Net::HTTP::Get
|
||||||
when :post then Net::HTTP::Post
|
when :post then Net::HTTP::Post
|
||||||
when :patch then Net::HTTP::Patch
|
when :patch then Net::HTTP::Patch
|
||||||
|
when :delete then Net::HTTP::Delete
|
||||||
else
|
else
|
||||||
raise ArgumentError, "unsupported method #{method}"
|
raise ArgumentError, "unsupported method #{method}"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,409 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'securerandom'
|
||||||
|
require_relative '../../../e2e/lib/api_client'
|
||||||
|
|
||||||
|
RSpec.describe 'Backend API surface HTTP E2E' do
|
||||||
|
primary_env_vars = %w[TURNIERE_E2E_EMAIL TURNIERE_E2E_PASSWORD].freeze
|
||||||
|
|
||||||
|
before do
|
||||||
|
missing_vars = primary_env_vars.reject { |name| ENV.key?(name) }
|
||||||
|
skip("HTTP E2E requires #{missing_vars.join(', ')}") unless missing_vars.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:base_url) { ENV.fetch('TURNIERE_E2E_BASE_URL', 'http://127.0.0.1:3000') }
|
||||||
|
let(:owner_email) { ENV.fetch('TURNIERE_E2E_EMAIL') }
|
||||||
|
let(:owner_password) { ENV.fetch('TURNIERE_E2E_PASSWORD') }
|
||||||
|
let(:owner_username) { ENV.fetch('TURNIERE_E2E_USERNAME', owner_email) }
|
||||||
|
|
||||||
|
let(:owner_client) { login_client(email: owner_email, password: owner_password) }
|
||||||
|
let(:other_client) { secondary_client }
|
||||||
|
let(:anonymous_client) { TurniereE2E::ApiClient.new(base_url: base_url) }
|
||||||
|
|
||||||
|
it 'reports health' do
|
||||||
|
response = anonymous_client.get('/healthz')
|
||||||
|
|
||||||
|
expect(response[:status]).to eq(200)
|
||||||
|
expect(response.dig(:json, :errors)).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'supports registration for new users and still requires confirmation for login' do
|
||||||
|
unique = unique_suffix
|
||||||
|
email = "http-e2e-#{unique}@example.com"
|
||||||
|
password = 'password123'
|
||||||
|
username = "http-e2e-#{unique}"
|
||||||
|
client = TurniereE2E::ApiClient.new(base_url: base_url)
|
||||||
|
registration = client.post('/users', body: {
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
password_confirmation: password,
|
||||||
|
username: username
|
||||||
|
})
|
||||||
|
login = TurniereE2E::ApiClient.new(base_url: base_url).login!(email: email, password: password)
|
||||||
|
|
||||||
|
expect(registration[:status]).to be_between(200, 299)
|
||||||
|
expect(registration[:json]).not_to eq({})
|
||||||
|
expect(login[:status]).to eq(401)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'lists public and private tournaments and supports simple and full show responses' do
|
||||||
|
public_tournament = create_playoff_tournament(client: owner_client, public: true, name_prefix: 'Public Listing')
|
||||||
|
private_tournament = create_playoff_tournament(client: owner_client, public: false, name_prefix: 'Private Listing')
|
||||||
|
|
||||||
|
public_index = anonymous_client.get('/tournaments')
|
||||||
|
private_index = owner_client.get('/tournaments', params: { type: 'private' })
|
||||||
|
simple_show = anonymous_client.get("/tournaments/#{public_tournament.fetch(:id)}", params: { simple: 'true' })
|
||||||
|
full_show = anonymous_client.get("/tournaments/#{public_tournament.fetch(:id)}")
|
||||||
|
invalid_index = anonymous_client.get('/tournaments', params: { type: 'bogus' })
|
||||||
|
|
||||||
|
expect(public_index[:status]).to eq(200)
|
||||||
|
expect(public_index[:json].map { |tournament| tournament[:id] }).to include(public_tournament.fetch(:id))
|
||||||
|
expect(public_index[:json].map { |tournament| tournament[:id] }).not_to include(private_tournament.fetch(:id))
|
||||||
|
|
||||||
|
expect(private_index[:status]).to eq(200)
|
||||||
|
expect(private_index[:json].map { |tournament| tournament[:id] }).to include(private_tournament.fetch(:id))
|
||||||
|
expect(private_index[:json].map { |tournament| tournament[:id] }).not_to include(public_tournament.fetch(:id))
|
||||||
|
|
||||||
|
expect(simple_show[:status]).to eq(200)
|
||||||
|
expect(simple_show[:json][:id]).to eq(public_tournament.fetch(:id))
|
||||||
|
expect(simple_show[:json]).not_to have_key(:stages)
|
||||||
|
|
||||||
|
expect(full_show[:status]).to eq(200)
|
||||||
|
expect(full_show.dig(:json, :stages)).to be_an(Array)
|
||||||
|
expect(full_show.dig(:json, :teams)).to be_an(Array)
|
||||||
|
|
||||||
|
expect(invalid_index[:status]).to eq(400)
|
||||||
|
expect(invalid_index.dig(:json, :error)).to eq('invalid type')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates group-stage and playoff-only tournaments and rejects invalid creation payloads' do
|
||||||
|
group_stage = create_group_stage_tournament(client: owner_client, public: false, name_prefix: 'Create Group')
|
||||||
|
playoff_only = create_playoff_tournament(client: owner_client, public: false, name_prefix: 'Create Playoff')
|
||||||
|
|
||||||
|
invalid_teams = owner_client.post('/tournaments', body: {
|
||||||
|
name: "Invalid Teams #{unique_suffix}",
|
||||||
|
public: false,
|
||||||
|
teams: ['nope']
|
||||||
|
})
|
||||||
|
unauthenticated = anonymous_client.post('/tournaments', body: {
|
||||||
|
name: "Needs Auth #{unique_suffix}",
|
||||||
|
public: false,
|
||||||
|
teams: [{ name: 'Solo Team' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(group_stage.fetch(:stages).map { |stage| stage[:level] }).to include(-1)
|
||||||
|
expect(group_stage.fetch(:teams).count).to eq(16)
|
||||||
|
|
||||||
|
expect(playoff_only.fetch(:stages).map { |stage| stage[:level] }.sort).to eq([0, 1])
|
||||||
|
expect(playoff_only.fetch(:teams).count).to eq(4)
|
||||||
|
|
||||||
|
expect(invalid_teams[:status]).to eq(422)
|
||||||
|
expect(invalid_teams.dig(:json, :error)).to eq('Invalid teams array')
|
||||||
|
|
||||||
|
expect(unauthenticated[:status]).to eq(401)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates and destroys tournaments, enforces owner checks, and validates timer updates' do
|
||||||
|
tournament = create_group_stage_tournament(client: owner_client, public: false, name_prefix: 'Mutable Tournament')
|
||||||
|
|
||||||
|
update_response = owner_client.patch("/tournaments/#{tournament.fetch(:id)}", body: {
|
||||||
|
name: "Renamed #{unique_suffix}",
|
||||||
|
playoff_teams_amount: 8,
|
||||||
|
instant_finalists_amount: 8,
|
||||||
|
intermediate_round_participants_amount: 0
|
||||||
|
})
|
||||||
|
auto_recalc_response = owner_client.patch("/tournaments/#{tournament.fetch(:id)}", body: {
|
||||||
|
playoff_teams_amount: 4
|
||||||
|
})
|
||||||
|
invalid_update = owner_client.patch("/tournaments/#{tournament.fetch(:id)}", body: {
|
||||||
|
playoff_teams_amount: 8,
|
||||||
|
instant_finalists_amount: 7,
|
||||||
|
intermediate_round_participants_amount: 0
|
||||||
|
})
|
||||||
|
forbidden_update = other_client.patch("/tournaments/#{tournament.fetch(:id)}", body: { name: 'Not Allowed' })
|
||||||
|
unauthenticated_update = anonymous_client.patch("/tournaments/#{tournament.fetch(:id)}", body: { name: 'No Auth' })
|
||||||
|
|
||||||
|
timer_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timer_end_seconds: 120 })
|
||||||
|
timer_show = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/timer_end")
|
||||||
|
timer_double = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: {
|
||||||
|
timer_end_seconds: 120,
|
||||||
|
timer_end: (Time.now.utc + 240).iso8601
|
||||||
|
})
|
||||||
|
timer_invalid_seconds = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timer_end_seconds: 'nope' })
|
||||||
|
timer_past = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timer_end_seconds: 0 })
|
||||||
|
timer_forbidden = other_client.patch("/tournaments/#{tournament.fetch(:id)}/set_timer_end", body: { timer_end_seconds: 60 })
|
||||||
|
|
||||||
|
destroy_response = owner_client.delete("/tournaments/#{tournament.fetch(:id)}")
|
||||||
|
after_destroy = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}")
|
||||||
|
|
||||||
|
expect(update_response[:status]).to eq(200)
|
||||||
|
expect(update_response.dig(:json, :name)).to start_with('Renamed')
|
||||||
|
|
||||||
|
expect(auto_recalc_response[:status]).to eq(200)
|
||||||
|
expect(auto_recalc_response.dig(:json, :playoff_teams_amount)).to eq(4)
|
||||||
|
expect(auto_recalc_response.dig(:json, :instant_finalists_amount)).to eq(4)
|
||||||
|
expect(auto_recalc_response.dig(:json, :intermediate_round_participants_amount)).to eq(0)
|
||||||
|
|
||||||
|
expect(invalid_update[:status]).to eq(422)
|
||||||
|
expect(invalid_update.dig(:json, :error)).to include("don't match")
|
||||||
|
|
||||||
|
expect(forbidden_update[:status]).to eq(403)
|
||||||
|
expect(forbidden_update.dig(:json, :errors)).to include('Only the parent tournament owner can update this resource')
|
||||||
|
expect(unauthenticated_update[:status]).to eq(401)
|
||||||
|
|
||||||
|
expect(timer_seconds[:status]).to eq(200)
|
||||||
|
expect(timer_seconds.dig(:json, :timer_end)).to be_a(String)
|
||||||
|
expect(timer_show[:status]).to eq(200)
|
||||||
|
expect(timer_show.dig(:json, :timer_end)).to eq(timer_seconds.dig(:json, :timer_end))
|
||||||
|
|
||||||
|
expect(timer_double[:status]).to eq(422)
|
||||||
|
expect(timer_double.dig(:json, :error)).to eq('Only one of timer_end or timer_end_seconds is allowed')
|
||||||
|
expect(timer_invalid_seconds[:status]).to eq(422)
|
||||||
|
expect(timer_invalid_seconds.dig(:json, :error)).to eq('Invalid seconds format')
|
||||||
|
expect(timer_past[:status]).to eq(422)
|
||||||
|
expect(timer_past.dig(:json, :error)).to eq('Timer end must be in the future')
|
||||||
|
expect(timer_forbidden[:status]).to eq(403)
|
||||||
|
|
||||||
|
expect([200, 204]).to include(destroy_response[:status])
|
||||||
|
expect(after_destroy[:status]).to eq(404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'supports match, score, team, group, statistics, stage, and bet flows over HTTP' do
|
||||||
|
tournament = create_group_stage_tournament(client: owner_client, public: false, name_prefix: 'Surface Flow')
|
||||||
|
group_stage = tournament.fetch(:stages).find { |stage| stage.fetch(:level) == -1 }
|
||||||
|
group = group_stage.fetch(:groups).min_by { |item| item.fetch(:number) }
|
||||||
|
match = group.fetch(:matches).min_by { |item| item.fetch(:position) }
|
||||||
|
team = match.fetch(:match_scores).first.fetch(:team)
|
||||||
|
|
||||||
|
group_show = anonymous_client.get("/groups/#{group.fetch(:id)}")
|
||||||
|
team_show = anonymous_client.get("/teams/#{team.fetch(:id)}")
|
||||||
|
team_update = owner_client.patch("/teams/#{team.fetch(:id)}", body: { name: "#{team.fetch(:name)} Renamed" })
|
||||||
|
invalid_team_update = owner_client.patch("/teams/#{team.fetch(:id)}", body: { name: '' })
|
||||||
|
forbidden_team_update = other_client.patch("/teams/#{team.fetch(:id)}", body: { name: 'Other User Name' })
|
||||||
|
|
||||||
|
statistics_before = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/statistics")
|
||||||
|
matches_all = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/matches")
|
||||||
|
matches_not_started = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/matches", params: { state: 'not_started' })
|
||||||
|
matches_upcoming = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/matches", params: { state: 'upcoming' })
|
||||||
|
match_show = anonymous_client.get("/matches/#{match.fetch(:id)}")
|
||||||
|
invalid_match_update = owner_client.patch("/matches/#{match.fetch(:id)}", body: { state: 'bogus' })
|
||||||
|
forbidden_match_update = other_client.patch("/matches/#{match.fetch(:id)}", body: { state: 'in_progress' })
|
||||||
|
unauthenticated_match_update = anonymous_client.patch("/matches/#{match.fetch(:id)}", body: { state: 'in_progress' })
|
||||||
|
stage_not_over = owner_client.patch("/stages/#{group_stage.fetch(:id)}", body: { state: 'finished' })
|
||||||
|
invalid_stage_update = owner_client.patch("/stages/#{group_stage.fetch(:id)}", body: { state: 'in_progress' })
|
||||||
|
forbidden_stage_update = other_client.patch("/stages/#{group_stage.fetch(:id)}", body: { state: 'finished' })
|
||||||
|
|
||||||
|
playoff_only = create_playoff_tournament(client: owner_client, public: false, name_prefix: 'Playoff Statistics')
|
||||||
|
unrelated_team = playoff_only.fetch(:teams).first
|
||||||
|
|
||||||
|
bet_create = other_client.post("/matches/#{match.fetch(:id)}/bets", body: { team: team.fetch(:id) })
|
||||||
|
bet_duplicate = other_client.post("/matches/#{match.fetch(:id)}/bets", body: { team: team.fetch(:id) })
|
||||||
|
bet_invalid_team = owner_client.post("/matches/#{match.fetch(:id)}/bets", body: { team: unrelated_team.fetch(:id) })
|
||||||
|
bets_index = anonymous_client.get("/matches/#{match.fetch(:id)}/bets")
|
||||||
|
|
||||||
|
start_match = owner_client.patch("/matches/#{match.fetch(:id)}", body: { state: 'in_progress' })
|
||||||
|
started_match = anonymous_client.get("/matches/#{match.fetch(:id)}")
|
||||||
|
score_to_update = started_match.fetch(:json).fetch(:match_scores).first
|
||||||
|
score_show = anonymous_client.get("/match_scores/#{score_to_update.fetch(:id)}")
|
||||||
|
score_update = owner_client.patch("/match_scores/#{score_to_update.fetch(:id)}", body: { points: 7 })
|
||||||
|
forbidden_score_update = other_client.patch("/match_scores/#{score_to_update.fetch(:id)}", body: { points: 3 })
|
||||||
|
unauthenticated_score_update = anonymous_client.patch("/match_scores/#{score_to_update.fetch(:id)}", body: { points: 3 })
|
||||||
|
bet_while_running = owner_client.post("/matches/#{match.fetch(:id)}/bets", body: { team: team.fetch(:id) })
|
||||||
|
restart_started_match = owner_client.patch("/matches/#{match.fetch(:id)}", body: { state: 'in_progress' })
|
||||||
|
playoff_stats = anonymous_client.get("/tournaments/#{playoff_only.fetch(:id)}/statistics")
|
||||||
|
tied_playoff_match = playoff_only.fetch(:stages)
|
||||||
|
.flat_map { |stage| stage.fetch(:matches) }
|
||||||
|
.find { |playoff_match| playoff_match.fetch(:state) == 'not_started' }
|
||||||
|
owner_client.patch("/matches/#{tied_playoff_match.fetch(:id)}", body: { state: 'in_progress' })
|
||||||
|
tied_match_after_start = anonymous_client.get("/matches/#{tied_playoff_match.fetch(:id)}")
|
||||||
|
tied_match_after_start.fetch(:json).fetch(:match_scores).each do |score|
|
||||||
|
owner_client.patch("/match_scores/#{score.fetch(:id)}", body: { points: 5 })
|
||||||
|
end
|
||||||
|
tied_finish = owner_client.patch("/matches/#{tied_playoff_match.fetch(:id)}", body: { state: 'finished' })
|
||||||
|
|
||||||
|
finish_group_match_with_winner!(match_id: match.fetch(:id))
|
||||||
|
refreshed_tournament = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}")[:json]
|
||||||
|
refreshed_group_stage = refreshed_tournament.fetch(:stages).find { |stage| stage.fetch(:level) == -1 }
|
||||||
|
refreshed_group_stage.fetch(:groups).each do |stage_group|
|
||||||
|
stage_group.fetch(:matches).each do |stage_match|
|
||||||
|
next if stage_match.fetch(:state) == 'finished'
|
||||||
|
|
||||||
|
finish_group_match_with_winner!(match_id: stage_match.fetch(:id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
finished_stage = owner_client.patch("/stages/#{group_stage.fetch(:id)}", body: { state: 'finished' })
|
||||||
|
stage_show = anonymous_client.get("/stages/#{group_stage.fetch(:id)}")
|
||||||
|
statistics_after = anonymous_client.get("/tournaments/#{tournament.fetch(:id)}/statistics")
|
||||||
|
|
||||||
|
expect(group_show[:status]).to eq(200)
|
||||||
|
expect(group_show.dig(:json, :matches)).to be_an(Array)
|
||||||
|
|
||||||
|
expect(team_show[:status]).to eq(200)
|
||||||
|
expect(team_update[:status]).to eq(200)
|
||||||
|
expect(team_update.dig(:json, :name)).to end_with('Renamed')
|
||||||
|
expect(invalid_team_update[:status]).to eq(422)
|
||||||
|
expect(forbidden_team_update[:status]).to eq(403)
|
||||||
|
|
||||||
|
expect(statistics_before[:status]).to eq(200)
|
||||||
|
expect(statistics_before.dig(:json, :group_scores)).to be_an(Array)
|
||||||
|
expect(matches_all[:status]).to eq(200)
|
||||||
|
expect(matches_all[:json].count).to eq(tournament_match_count(tournament))
|
||||||
|
expect(matches_not_started[:status]).to eq(200)
|
||||||
|
expect(matches_not_started[:json].map { |item| item[:id] }).to include(match.fetch(:id))
|
||||||
|
expect(matches_upcoming[:status]).to eq(200)
|
||||||
|
expect(matches_upcoming[:json].map { |item| item[:id] }).to include(match.fetch(:id))
|
||||||
|
expect(match_show[:status]).to eq(200)
|
||||||
|
expect(invalid_match_update[:status]).to eq(422)
|
||||||
|
expect(invalid_match_update.dig(:json, :error)).to eq('Invalid target state')
|
||||||
|
expect(forbidden_match_update[:status]).to eq(403)
|
||||||
|
expect(unauthenticated_match_update[:status]).to eq(403)
|
||||||
|
|
||||||
|
expect(stage_not_over[:status]).to eq(422)
|
||||||
|
expect(stage_not_over.dig(:json, :error)).to include('Group Stage still has some matches')
|
||||||
|
expect(invalid_stage_update[:status]).to eq(422)
|
||||||
|
expect(invalid_stage_update.dig(:json, :error)).to eq('The state attribute may only be changed to finished')
|
||||||
|
expect(forbidden_stage_update[:status]).to eq(403)
|
||||||
|
|
||||||
|
expect(bet_create[:status]).to eq(200)
|
||||||
|
expect(bet_create.dig(:json, :team, :id)).to eq(team.fetch(:id))
|
||||||
|
expect(bet_duplicate[:status]).to eq(422)
|
||||||
|
expect(bet_duplicate.dig(:json, :error)).to eq('This user already created a bet on this match')
|
||||||
|
expect(bet_invalid_team[:status]).to eq(422)
|
||||||
|
expect(bet_invalid_team.dig(:json, :error)).to eq('The given team is not involved in the given match')
|
||||||
|
expect(bets_index[:status]).to eq(200)
|
||||||
|
expect(bets_index[:json].count).to eq(1)
|
||||||
|
|
||||||
|
expect(start_match[:status]).to eq(200)
|
||||||
|
expect(score_show[:status]).to eq(200)
|
||||||
|
expect(score_update[:status]).to eq(200)
|
||||||
|
expect(score_update.dig(:json, :points)).to eq(7)
|
||||||
|
expect(forbidden_score_update[:status]).to eq(403)
|
||||||
|
expect(unauthenticated_score_update[:status]).to eq(401)
|
||||||
|
expect(bet_while_running[:status]).to eq(422)
|
||||||
|
expect(bet_while_running.dig(:json, :error)).to eq('Betting is not allowed while match is in_progress')
|
||||||
|
expect(restart_started_match[:status]).to eq(422)
|
||||||
|
expect(restart_started_match.dig(:json, :error)).to eq("Match can't start in this state")
|
||||||
|
|
||||||
|
expect(playoff_stats[:status]).to eq(501)
|
||||||
|
expect(playoff_stats[:json]).to eq({})
|
||||||
|
expect(tied_finish[:status]).to eq(422)
|
||||||
|
expect(tied_finish.dig(:json, :error)).to eq("Stopping undecided Matches isn't allowed in playoff stage")
|
||||||
|
|
||||||
|
expect(finished_stage[:status]).to eq(200)
|
||||||
|
expect(stage_show[:status]).to eq(200)
|
||||||
|
expect(stage_show.dig(:json, :state)).to eq('finished')
|
||||||
|
expect(statistics_after[:status]).to eq(200)
|
||||||
|
expect(statistics_after.dig(:json, :group_scores).count).to eq(16)
|
||||||
|
expect(statistics_after.dig(:json, :most_dominant_score, :id)).to be_a(Integer)
|
||||||
|
expect(statistics_after.dig(:json, :least_dominant_score, :id)).to be_a(Integer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def login_client(email:, password:)
|
||||||
|
client = TurniereE2E::ApiClient.new(base_url: base_url)
|
||||||
|
response = client.login!(email: email, password: password)
|
||||||
|
|
||||||
|
expect(response[:status]).to eq(200)
|
||||||
|
expect(client.authenticated?).to eq(true)
|
||||||
|
client
|
||||||
|
end
|
||||||
|
|
||||||
|
def secondary_client
|
||||||
|
alt_email = ENV['TURNIERE_E2E_ALT_EMAIL']
|
||||||
|
alt_password = ENV['TURNIERE_E2E_ALT_PASSWORD']
|
||||||
|
if alt_email && alt_password
|
||||||
|
client = TurniereE2E::ApiClient.new(base_url: base_url)
|
||||||
|
response = client.login!(email: alt_email, password: alt_password)
|
||||||
|
return client if response[:status] == 200 && client.authenticated?
|
||||||
|
end
|
||||||
|
|
||||||
|
client = TurniereE2E::ApiClient.new(base_url: base_url)
|
||||||
|
unique = unique_suffix
|
||||||
|
response = client.post('/users', body: {
|
||||||
|
email: "http-e2e-secondary-#{unique}@example.com",
|
||||||
|
password: 'password123',
|
||||||
|
password_confirmation: 'password123',
|
||||||
|
username: "http-e2e-secondary-#{unique}"
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response[:status]).to be_between(200, 299)
|
||||||
|
expect(client.authenticated?).to eq(true)
|
||||||
|
client
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_group_stage_tournament(client:, public:, name_prefix:)
|
||||||
|
payload = {
|
||||||
|
name: "#{name_prefix} #{unique_suffix}",
|
||||||
|
description: 'HTTP API surface E2E group-stage tournament',
|
||||||
|
public: public,
|
||||||
|
group_stage: true,
|
||||||
|
playoff_teams_amount: 8,
|
||||||
|
teams: 4.times.flat_map do |group_index|
|
||||||
|
4.times.map do |seed_index|
|
||||||
|
{
|
||||||
|
name: "#{name_prefix.tr(' ', '')}-G#{group_index + 1}-S#{seed_index + 1}",
|
||||||
|
group: group_index
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post('/tournaments', body: payload)
|
||||||
|
expect(response[:status]).to eq(201)
|
||||||
|
fetch_tournament(client: client, tournament_id: response.dig(:json, :id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_playoff_tournament(client:, public:, name_prefix:)
|
||||||
|
payload = {
|
||||||
|
name: "#{name_prefix} #{unique_suffix}",
|
||||||
|
description: 'HTTP API surface E2E playoff tournament',
|
||||||
|
public: public,
|
||||||
|
teams: 4.times.map do |index|
|
||||||
|
{ name: "#{name_prefix.tr(' ', '')}-S#{index + 1}" }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post('/tournaments', body: payload)
|
||||||
|
expect(response[:status]).to eq(201)
|
||||||
|
fetch_tournament(client: client, tournament_id: response.dig(:json, :id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_tournament(client:, tournament_id:)
|
||||||
|
response = client.get("/tournaments/#{tournament_id}")
|
||||||
|
expect(response[:status]).to eq(200)
|
||||||
|
response.fetch(:json)
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish_group_match_with_winner!(match_id:)
|
||||||
|
match = anonymous_client.get("/matches/#{match_id}")
|
||||||
|
expect(match[:status]).to eq(200)
|
||||||
|
|
||||||
|
unless match.dig(:json, :state) == 'in_progress'
|
||||||
|
start_response = owner_client.patch("/matches/#{match_id}", body: { state: 'in_progress' })
|
||||||
|
expect(start_response[:status]).to eq(200)
|
||||||
|
match = anonymous_client.get("/matches/#{match_id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
ordered_scores = match.fetch(:json).fetch(:match_scores).sort_by { |score| score.fetch(:id) }
|
||||||
|
ordered_scores.each_with_index do |score, index|
|
||||||
|
response = owner_client.patch("/match_scores/#{score.fetch(:id)}", body: { points: index.zero? ? 10 : 1 })
|
||||||
|
expect(response[:status]).to eq(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
finish_response = owner_client.patch("/matches/#{match_id}", body: { state: 'finished' })
|
||||||
|
expect(finish_response[:status]).to eq(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tournament_match_count(tournament)
|
||||||
|
tournament.fetch(:stages).sum do |stage|
|
||||||
|
stage.fetch(:matches, []).count + stage.fetch(:groups, []).sum { |group| group.fetch(:matches).count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unique_suffix
|
||||||
|
@unique_suffix_counter ||= 0
|
||||||
|
@unique_suffix_counter += 1
|
||||||
|
"#{SecureRandom.hex(3)}-#{@unique_suffix_counter}"
|
||||||
|
end
|
||||||
|
end
|
||||||
36
tasks.py
36
tasks.py
|
|
@ -8,6 +8,7 @@ import subprocess
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
from urllib.parse import urlparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,6 +21,9 @@ BLACKBOX_INTERNAL_BASE_URL = "http://app:3000"
|
||||||
E2E_EMAIL = "e2e@example.com"
|
E2E_EMAIL = "e2e@example.com"
|
||||||
E2E_PASSWORD = "password123"
|
E2E_PASSWORD = "password123"
|
||||||
E2E_USERNAME = "e2e-user"
|
E2E_USERNAME = "e2e-user"
|
||||||
|
E2E_ALT_EMAIL = "e2e-alt@example.com"
|
||||||
|
E2E_ALT_PASSWORD = "password123"
|
||||||
|
E2E_ALT_USERNAME = "e2e-alt-user"
|
||||||
|
|
||||||
PRODUCTION_TAG = "registry.gitlab.com/turniere/turniere-backend/production/commits:local"
|
PRODUCTION_TAG = "registry.gitlab.com/turniere/turniere-backend/production/commits:local"
|
||||||
DEVELOPMENT_TAG = "registry.gitlab.com/turniere/turniere-backend/development/commits:local"
|
DEVELOPMENT_TAG = "registry.gitlab.com/turniere/turniere-backend/development/commits:local"
|
||||||
|
|
@ -277,9 +281,9 @@ def _start_server(command, env, log_path="/tmp/turniere-e2e-server.log"):
|
||||||
return process, log_handle
|
return process, log_handle
|
||||||
|
|
||||||
|
|
||||||
def _start_test_server():
|
def _start_test_server(host=SERVER_HOST, port=SERVER_PORT):
|
||||||
return _start_server(
|
return _start_server(
|
||||||
f"{_bundle_exec('rails', 's')} -e test -b {SERVER_HOST} -p {SERVER_PORT}",
|
f"{_bundle_exec('rails', 's')} -e test -b {host} -p {port}",
|
||||||
_env(),
|
_env(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -425,6 +429,9 @@ def test_http_e2e(
|
||||||
email=E2E_EMAIL,
|
email=E2E_EMAIL,
|
||||||
password=E2E_PASSWORD,
|
password=E2E_PASSWORD,
|
||||||
username=E2E_USERNAME,
|
username=E2E_USERNAME,
|
||||||
|
alt_email=E2E_ALT_EMAIL,
|
||||||
|
alt_password=E2E_ALT_PASSWORD,
|
||||||
|
alt_username=E2E_ALT_USERNAME,
|
||||||
):
|
):
|
||||||
"""Run HTTP E2E specs against an already running backend."""
|
"""Run HTTP E2E specs against an already running backend."""
|
||||||
c.run(
|
c.run(
|
||||||
|
|
@ -434,6 +441,9 @@ def test_http_e2e(
|
||||||
TURNIERE_E2E_EMAIL=email,
|
TURNIERE_E2E_EMAIL=email,
|
||||||
TURNIERE_E2E_PASSWORD=password,
|
TURNIERE_E2E_PASSWORD=password,
|
||||||
TURNIERE_E2E_USERNAME=username,
|
TURNIERE_E2E_USERNAME=username,
|
||||||
|
TURNIERE_E2E_ALT_EMAIL=alt_email,
|
||||||
|
TURNIERE_E2E_ALT_PASSWORD=alt_password,
|
||||||
|
TURNIERE_E2E_ALT_USERNAME=alt_username,
|
||||||
),
|
),
|
||||||
pty=True,
|
pty=True,
|
||||||
)
|
)
|
||||||
|
|
@ -446,15 +456,31 @@ def verify_http(
|
||||||
email=E2E_EMAIL,
|
email=E2E_EMAIL,
|
||||||
password=E2E_PASSWORD,
|
password=E2E_PASSWORD,
|
||||||
username=E2E_USERNAME,
|
username=E2E_USERNAME,
|
||||||
|
alt_email=E2E_ALT_EMAIL,
|
||||||
|
alt_password=E2E_ALT_PASSWORD,
|
||||||
|
alt_username=E2E_ALT_USERNAME,
|
||||||
):
|
):
|
||||||
"""Boot a local test server, then run the HTTP E2E suite against it."""
|
"""Boot a local test server, then run the HTTP E2E suite against it."""
|
||||||
db_migrate(c)
|
db_migrate(c, env="test")
|
||||||
bootstrap_e2e_user(c, email=email, password=password, username=username)
|
bootstrap_e2e_user(c, email=email, password=password, username=username)
|
||||||
|
bootstrap_e2e_user(c, email=alt_email, password=alt_password, username=alt_username)
|
||||||
|
parsed_base_url = urlparse(base_url)
|
||||||
|
server_host = parsed_base_url.hostname or "127.0.0.1"
|
||||||
|
server_port = parsed_base_url.port or 3000
|
||||||
|
|
||||||
process, log_handle = _start_test_server()
|
process, log_handle = _start_test_server(host=server_host, port=server_port)
|
||||||
try:
|
try:
|
||||||
_wait_for_http(base_url=base_url, timeout=60)
|
_wait_for_http(base_url=base_url, timeout=60)
|
||||||
test_http_e2e(c, base_url=base_url, email=email, password=password, username=username)
|
test_http_e2e(
|
||||||
|
c,
|
||||||
|
base_url=base_url,
|
||||||
|
email=email,
|
||||||
|
password=password,
|
||||||
|
username=username,
|
||||||
|
alt_email=alt_email,
|
||||||
|
alt_password=alt_password,
|
||||||
|
alt_username=alt_username,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
_stop_process(process, log_handle)
|
_stop_process(process, log_handle)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue