Merge pull request 'test(staging): smoke-suite for gameplay flow' (#32) from feature/test-smoke-fail-label into main
All checks were successful
CI / test-and-quality (push) Successful in 1m16s
All checks were successful
CI / test-and-quality (push) Successful in 1m16s
This commit was merged in pull request #32.
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