Expand backend HTTP E2E API coverage

This commit is contained in:
Daniel Schädler 2026-04-12 16:06:23 +02:00
parent 236b43edce
commit 7f04e75649
3 changed files with 445 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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)