Add staging gameplay smoke artifact output
This commit is contained in:
@@ -22,6 +22,8 @@ Forventet:
|
|||||||
- service er active
|
- service er active
|
||||||
- healthz returnerer JSON med ok=true
|
- healthz returnerer JSON med ok=true
|
||||||
|
|
||||||
|
Smoke-suite skriver nu et gameplay-artifact som JSON under `/opt/wpp-staging/app/artifacts/smoke/` (kan overrides via `ARTIFACT_DIR`/`ARTIFACT_FILE`).
|
||||||
|
|
||||||
Efter deploy validerer scriptet, at `DB_ENGINE` ikke er `django.db.backends.sqlite3` før migrations køres.
|
Efter deploy validerer scriptet, at `DB_ENGINE` ikke er `django.db.backends.sqlite3` før migrations køres.
|
||||||
|
|
||||||
Deploy-scriptet bruger en release-candidate mappe og promoverer først til `/opt/wpp-staging/app` efter succesfuld `migrate`. Det reducerer schema/code drift ved afbrudte deploys (issue #130) og understøtter release-readiness gate (issue #90).
|
Deploy-scriptet bruger en release-candidate mappe og promoverer først til `/opt/wpp-staging/app` efter succesfuld `migrate`. Det reducerer schema/code drift ved afbrudte deploys (issue #130) og understøtter release-readiness gate (issue #90).
|
||||||
|
|||||||
@@ -69,7 +69,11 @@ run_manage() {
|
|||||||
echo "[smoke] migration consistency check"
|
echo "[smoke] migration consistency check"
|
||||||
run_manage "migrate --check --noinput" || { SMOKE_FAIL_MESSAGE="schema drift: unapplied migrations in staging" fail "schema drift: unapplied migrations in staging"; }
|
run_manage "migrate --check --noinput" || { SMOKE_FAIL_MESSAGE="schema drift: unapplied migrations in staging" fail "schema drift: unapplied migrations in staging"; }
|
||||||
|
|
||||||
echo "[smoke] gameplay flow via management command"
|
ARTIFACT_DIR="${ARTIFACT_DIR:-${APP_DIR}/artifacts/smoke}"
|
||||||
run_manage "smoke_staging" || { SMOKE_FAIL_MESSAGE="manage.py smoke_staging failed" fail "manage.py smoke_staging failed"; }
|
ARTIFACT_FILE="${ARTIFACT_FILE:-${ARTIFACT_DIR}/smoke-$(date -u +%Y%m%dT%H%M%SZ).json}"
|
||||||
|
|
||||||
|
echo "[smoke] gameplay flow via management command"
|
||||||
|
run_manage "smoke_staging --artifact ${ARTIFACT_FILE}" || { SMOKE_FAIL_MESSAGE="manage.py smoke_staging failed" fail "manage.py smoke_staging failed"; }
|
||||||
|
|
||||||
|
echo "[smoke] artifact: ${ARTIFACT_FILE}"
|
||||||
echo "[smoke] OK"
|
echo "[smoke] OK"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
@@ -10,6 +12,12 @@ from fupogfakta.models import Category, GameSession, Player, Question, RoundQues
|
|||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Run minimal staging smoke flow for lobby gameplay"
|
help = "Run minimal staging smoke flow for lobby gameplay"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--artifact",
|
||||||
|
help="Optional path to write smoke result artifact as JSON",
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
GameSession.objects.all().delete()
|
GameSession.objects.all().delete()
|
||||||
Player.objects.all().delete()
|
Player.objects.all().delete()
|
||||||
@@ -127,4 +135,30 @@ class Command(BaseCommand):
|
|||||||
if finish_res.status_code != 200:
|
if finish_res.status_code != 200:
|
||||||
raise CommandError(f"finish_game failed: {finish_res.status_code}")
|
raise CommandError(f"finish_game failed: {finish_res.status_code}")
|
||||||
|
|
||||||
|
artifact_path = options.get("artifact")
|
||||||
|
if artifact_path:
|
||||||
|
artifact = {
|
||||||
|
"ok": True,
|
||||||
|
"command": "smoke_staging",
|
||||||
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"session_code": code,
|
||||||
|
"players": [player["nickname"] for player in players],
|
||||||
|
"round_question_id": round_question_id,
|
||||||
|
"steps": [
|
||||||
|
"create_session",
|
||||||
|
"join_players",
|
||||||
|
"start_round",
|
||||||
|
"show_question",
|
||||||
|
"submit_lies",
|
||||||
|
"mix_answers",
|
||||||
|
"submit_guesses",
|
||||||
|
"calculate_scores",
|
||||||
|
"reveal_scoreboard",
|
||||||
|
"finish_game",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
output_path = Path(artifact_path)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS(f"Smoke flow OK for session {code}"))
|
self.stdout.write(self.style.SUCCESS(f"Smoke flow OK for session {code}"))
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import json
|
||||||
|
import tempfile
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
@@ -972,3 +975,31 @@ class SmokeStagingCommandTests(TestCase):
|
|||||||
session = GameSession.objects.latest("created_at")
|
session = GameSession.objects.latest("created_at")
|
||||||
self.assertEqual(session.status, GameSession.Status.FINISHED)
|
self.assertEqual(session.status, GameSession.Status.FINISHED)
|
||||||
self.assertEqual(Player.objects.filter(session=session).count(), 3)
|
self.assertEqual(Player.objects.filter(session=session).count(), 3)
|
||||||
|
|
||||||
|
def test_smoke_staging_writes_artifact_when_requested(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
artifact_path = Path(tmp_dir) / "smoke.json"
|
||||||
|
call_command("smoke_staging", artifact=str(artifact_path))
|
||||||
|
|
||||||
|
self.assertTrue(artifact_path.exists())
|
||||||
|
payload = json.loads(artifact_path.read_text(encoding="utf-8"))
|
||||||
|
self.assertTrue(payload["ok"])
|
||||||
|
self.assertEqual(payload["command"], "smoke_staging")
|
||||||
|
self.assertEqual(payload["players"], ["P1", "P2", "P3"])
|
||||||
|
self.assertIn("generated_at", payload)
|
||||||
|
self.assertIn("session_code", payload)
|
||||||
|
self.assertEqual(
|
||||||
|
payload["steps"],
|
||||||
|
[
|
||||||
|
"create_session",
|
||||||
|
"join_players",
|
||||||
|
"start_round",
|
||||||
|
"show_question",
|
||||||
|
"submit_lies",
|
||||||
|
"mix_answers",
|
||||||
|
"submit_guesses",
|
||||||
|
"calculate_scores",
|
||||||
|
"reveal_scoreboard",
|
||||||
|
"finish_game",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user