test(staging): add smoke suite script and gameplay smoke command (closes #22)
This commit is contained in:
59
infra/staging/smoke_suite.sh
Executable file
59
infra/staging/smoke_suite.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8000}"
|
||||
APP_DIR="${APP_DIR:-/opt/wpp-staging/app}"
|
||||
ISSUE_ON_FAIL="${ISSUE_ON_FAIL:-1}"
|
||||
|
||||
fail() {
|
||||
local message="$1"
|
||||
echo "[smoke] FAIL: ${message}" >&2
|
||||
|
||||
if [[ "${ISSUE_ON_FAIL}" == "1" ]] && [[ -n "${GITEA_BASE:-}" ]] && [[ -n "${GITEA_REPO:-}" ]] && [[ -n "${GITEA_USER:-}" ]] && [[ -n "${GITEA_TOKEN:-}" ]]; then
|
||||
python3 - <<PY || true
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import base64
|
||||
from datetime import datetime, timezone
|
||||
|
||||
base = os.environ["GITEA_BASE"].rstrip("/")
|
||||
repo = os.environ["GITEA_REPO"]
|
||||
user = os.environ["GITEA_USER"]
|
||||
token = os.environ["GITEA_TOKEN"]
|
||||
message = os.environ.get("SMOKE_FAIL_MESSAGE", "unknown")
|
||||
when = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
||||
|
||||
payload = {
|
||||
"title": f"[smoke-fail] staging smoke failed ({when})",
|
||||
"body": (
|
||||
"Automatisk oprettet af smoke-suite.\n\n"
|
||||
f"Fejl: `{message}`\n"
|
||||
"Kontekst: issue #22 (staging smoke-suite)"
|
||||
),
|
||||
"labels": ["smoke-fail", "need-to-have", "staging"],
|
||||
}
|
||||
|
||||
url = f"{base}/api/v1/repos/{repo}/issues"
|
||||
req = urllib.request.Request(url, data=json.dumps(payload).encode(), method="POST")
|
||||
auth = base64.b64encode(f"{user}:{token}".encode()).decode()
|
||||
req.add_header("Authorization", f"Basic {auth}")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
with urllib.request.urlopen(req) as r:
|
||||
print(f"[smoke] Created fail issue: HTTP {r.status}")
|
||||
PY
|
||||
fi
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "[smoke] healthz check: ${BASE_URL}/healthz"
|
||||
curl -fsS "${BASE_URL}/healthz" >/dev/null || { SMOKE_FAIL_MESSAGE="healthz check failed" fail "healthz check failed"; }
|
||||
|
||||
echo "[smoke] gameplay flow via management command"
|
||||
(
|
||||
cd "${APP_DIR}"
|
||||
.venv/bin/python manage.py smoke_staging
|
||||
) || { SMOKE_FAIL_MESSAGE="manage.py smoke_staging failed" fail "manage.py smoke_staging failed"; }
|
||||
|
||||
echo "[smoke] OK"
|
||||
1
lobby/management/__init__.py
Normal file
1
lobby/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
lobby/management/commands/__init__.py
Normal file
1
lobby/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
118
lobby/management/commands/smoke_staging.py
Normal file
118
lobby/management/commands/smoke_staging.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import json
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.test import Client
|
||||
|
||||
from fupogfakta.models import Category, GameSession, Player, Question, RoundQuestion
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Run minimal staging smoke flow for lobby gameplay"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
GameSession.objects.all().delete()
|
||||
Player.objects.all().delete()
|
||||
RoundQuestion.objects.all().delete()
|
||||
|
||||
category, _ = Category.objects.get_or_create(
|
||||
slug="smoke",
|
||||
defaults={"name": "Smoke", "is_active": True},
|
||||
)
|
||||
category.is_active = True
|
||||
category.save(update_fields=["is_active"])
|
||||
|
||||
Question.objects.get_or_create(
|
||||
category=category,
|
||||
prompt="Smoke prompt?",
|
||||
defaults={"correct_answer": "Correct", "is_active": True},
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
host, _ = User.objects.get_or_create(username="smoke-host")
|
||||
host.set_password("smoke-pass")
|
||||
host.is_staff = True
|
||||
host.save()
|
||||
|
||||
host_client = Client()
|
||||
host_client.force_login(host)
|
||||
|
||||
create_res = host_client.post("/lobby/sessions/create", content_type="application/json")
|
||||
if create_res.status_code != 201:
|
||||
raise CommandError(f"create_session failed: {create_res.status_code} {create_res.content!r}")
|
||||
|
||||
code = create_res.json()["session"]["code"]
|
||||
|
||||
players = []
|
||||
for nickname in ["P1", "P2", "P3"]:
|
||||
join_res = Client().post(
|
||||
"/lobby/sessions/join",
|
||||
data=json.dumps({"code": code, "nickname": nickname}),
|
||||
content_type="application/json",
|
||||
)
|
||||
if join_res.status_code != 201:
|
||||
raise CommandError(f"join_session failed for {nickname}: {join_res.status_code}")
|
||||
players.append(join_res.json()["player"])
|
||||
|
||||
start_res = host_client.post(
|
||||
f"/lobby/sessions/{code}/rounds/start",
|
||||
data=json.dumps({"category_slug": category.slug}),
|
||||
content_type="application/json",
|
||||
)
|
||||
if start_res.status_code != 201:
|
||||
raise CommandError(f"start_round failed: {start_res.status_code}")
|
||||
|
||||
show_res = host_client.post(f"/lobby/sessions/{code}/questions/show", content_type="application/json")
|
||||
if show_res.status_code != 201:
|
||||
raise CommandError(f"show_question failed: {show_res.status_code}")
|
||||
|
||||
round_question_id = show_res.json()["round_question"]["id"]
|
||||
|
||||
for player in players:
|
||||
nick = player["nickname"]
|
||||
lie_res = Client().post(
|
||||
f"/lobby/sessions/{code}/questions/{round_question_id}/lies/submit",
|
||||
data=json.dumps({"player_id": player["id"], "text": f"Lie from {nick}"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
if lie_res.status_code != 201:
|
||||
raise CommandError(f"submit_lie failed for {nick}: {lie_res.status_code}")
|
||||
|
||||
mix_res = host_client.post(
|
||||
f"/lobby/sessions/{code}/questions/{round_question_id}/answers/mix",
|
||||
content_type="application/json",
|
||||
)
|
||||
if mix_res.status_code != 200:
|
||||
raise CommandError(f"mix_answers failed: {mix_res.status_code}")
|
||||
|
||||
answers = mix_res.json().get("answers", [])
|
||||
if not answers:
|
||||
raise CommandError("mix_answers returned empty answers")
|
||||
|
||||
for player in players:
|
||||
nick = player["nickname"]
|
||||
selected = next((a for a in answers if a.get("player_id") != player["id"]), answers[0])
|
||||
guess_res = Client().post(
|
||||
f"/lobby/sessions/{code}/questions/{round_question_id}/guesses/submit",
|
||||
data=json.dumps({"player_id": player["id"], "selected_text": selected["text"]}),
|
||||
content_type="application/json",
|
||||
)
|
||||
if guess_res.status_code != 201:
|
||||
raise CommandError(f"submit_guess failed for {nick}: {guess_res.status_code}")
|
||||
|
||||
calc_res = host_client.post(
|
||||
f"/lobby/sessions/{code}/questions/{round_question_id}/scores/calculate",
|
||||
content_type="application/json",
|
||||
)
|
||||
if calc_res.status_code != 200:
|
||||
raise CommandError(f"calculate_scores failed: {calc_res.status_code}")
|
||||
|
||||
board_res = host_client.get(f"/lobby/sessions/{code}/scoreboard")
|
||||
if board_res.status_code != 200:
|
||||
raise CommandError(f"reveal_scoreboard failed: {board_res.status_code}")
|
||||
|
||||
finish_res = host_client.post(f"/lobby/sessions/{code}/finish", content_type="application/json")
|
||||
if finish_res.status_code != 200:
|
||||
raise CommandError(f"finish_game failed: {finish_res.status_code}")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Smoke flow OK for session {code}"))
|
||||
Reference in New Issue
Block a user