checkpoint: add Paper Dynasty API smoke test script
Quick/full mode smoke test for deployment verification. Covers all API endpoint groups with response validation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9fa256e0e0
commit
25722c5164
867
skills/paper-dynasty/scripts/smoke_test.py
Normal file
867
skills/paper-dynasty/scripts/smoke_test.py
Normal file
@ -0,0 +1,867 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Paper Dynasty Smoke Test
|
||||
|
||||
Comprehensive deployment verification for the Database API and Discord Bot.
|
||||
Tests endpoint availability, data integrity, and key features like card rendering
|
||||
and the Refractor (evolution) system.
|
||||
|
||||
Usage:
|
||||
python smoke_test.py # Test dev environment
|
||||
python smoke_test.py --env prod # Test production
|
||||
python smoke_test.py --env dev --verbose # Show response details
|
||||
python smoke_test.py --category core # Run only core tests
|
||||
|
||||
Exit codes:
|
||||
0 = all tests passed
|
||||
1 = one or more tests failed
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
from api_client import PaperDynastyAPI
|
||||
|
||||
# ANSI colors
|
||||
GREEN = "\033[92m"
|
||||
RED = "\033[91m"
|
||||
YELLOW = "\033[93m"
|
||||
CYAN = "\033[96m"
|
||||
DIM = "\033[2m"
|
||||
BOLD = "\033[1m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestResult:
|
||||
name: str
|
||||
category: str
|
||||
passed: bool
|
||||
status_code: Optional[int] = None
|
||||
detail: str = ""
|
||||
duration_ms: float = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class SmokeTestRunner:
|
||||
env: str = "dev"
|
||||
verbose: bool = False
|
||||
categories: Optional[list] = None
|
||||
mode: str = "quick"
|
||||
results: list = field(default_factory=list)
|
||||
api: PaperDynastyAPI = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
self.api = PaperDynastyAPI(environment=self.env, verbose=self.verbose)
|
||||
|
||||
def check(
|
||||
self,
|
||||
name: str,
|
||||
category: str,
|
||||
endpoint: str,
|
||||
*,
|
||||
params: Optional[list] = None,
|
||||
expect_list: bool = False,
|
||||
min_count: int = 0,
|
||||
expect_keys: Optional[list] = None,
|
||||
timeout: int = 30,
|
||||
requires_auth: bool = False,
|
||||
) -> Optional[TestResult]:
|
||||
"""Run a single endpoint check."""
|
||||
if self.categories and category not in self.categories:
|
||||
return None
|
||||
|
||||
import requests
|
||||
|
||||
if requires_auth and not self.api.token:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=True,
|
||||
detail="skipped (no API_TOKEN)",
|
||||
duration_ms=0,
|
||||
)
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
url = self.api._build_url(endpoint, params=params)
|
||||
raw = requests.get(url, headers=self.api.headers, timeout=timeout)
|
||||
elapsed = (time.time() - start) * 1000
|
||||
status = raw.status_code
|
||||
|
||||
if status != 200:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=status,
|
||||
detail=f"HTTP {status}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
data = raw.json()
|
||||
|
||||
# API returns {"count": N, "<resource>": [...]} — unwrap
|
||||
if expect_list and isinstance(data, dict):
|
||||
# Find the list value in the response dict
|
||||
lists = [v for v in data.values() if isinstance(v, list)]
|
||||
if lists:
|
||||
data = lists[0]
|
||||
else:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=status,
|
||||
detail=f"no list found in response keys: {list(data.keys())}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
# Validate response shape
|
||||
if expect_list:
|
||||
if not isinstance(data, list):
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=status,
|
||||
detail=f"expected list, got {type(data).__name__}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
if len(data) < min_count:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=status,
|
||||
detail=f"expected >= {min_count} items, got {len(data)}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
detail = f"{len(data)} items"
|
||||
elif expect_keys:
|
||||
if isinstance(data, list):
|
||||
obj = data[0] if data else {}
|
||||
else:
|
||||
obj = data
|
||||
missing = [k for k in expect_keys if k not in obj]
|
||||
if missing:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=status,
|
||||
detail=f"missing keys: {missing}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
detail = "schema ok"
|
||||
else:
|
||||
detail = "ok"
|
||||
|
||||
if self.verbose and isinstance(data, list):
|
||||
detail += (
|
||||
f" (first: {json.dumps(data[0], default=str)[:80]}...)"
|
||||
if data
|
||||
else ""
|
||||
)
|
||||
elif self.verbose and isinstance(data, dict):
|
||||
detail += f" ({json.dumps(data, default=str)[:80]}...)"
|
||||
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=True,
|
||||
status_code=status,
|
||||
detail=detail,
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
detail=f"timeout after {timeout}s",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
except requests.exceptions.ConnectionError:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
detail="connection refused",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
except Exception as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
detail=str(e)[:100],
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
def check_url(
|
||||
self,
|
||||
name: str,
|
||||
category: str,
|
||||
url: str,
|
||||
*,
|
||||
timeout: int = 30,
|
||||
expect_content_type: Optional[str] = None,
|
||||
) -> Optional[TestResult]:
|
||||
"""Check a raw URL (not through the API client)."""
|
||||
if self.categories and category not in self.categories:
|
||||
return None
|
||||
|
||||
import requests
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
raw = requests.get(url, timeout=timeout)
|
||||
elapsed = (time.time() - start) * 1000
|
||||
|
||||
if raw.status_code != 200:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=raw.status_code,
|
||||
detail=f"HTTP {raw.status_code}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
detail = f"{len(raw.content)} bytes"
|
||||
if expect_content_type:
|
||||
ct = raw.headers.get("content-type", "")
|
||||
if expect_content_type not in ct:
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
status_code=raw.status_code,
|
||||
detail=f"expected {expect_content_type}, got {ct}",
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=True,
|
||||
status_code=raw.status_code,
|
||||
detail=detail,
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
except Exception as e:
|
||||
elapsed = (time.time() - start) * 1000
|
||||
return TestResult(
|
||||
name=name,
|
||||
category=category,
|
||||
passed=False,
|
||||
detail=str(e)[:100],
|
||||
duration_ms=elapsed,
|
||||
)
|
||||
|
||||
def _fetch_id(
|
||||
self, endpoint: str, params: Optional[list] = None, requires_auth: bool = False
|
||||
) -> Optional[int]:
|
||||
"""Fetch the first item's ID from a list endpoint."""
|
||||
import requests
|
||||
|
||||
if requires_auth and not self.api.token:
|
||||
return None
|
||||
try:
|
||||
url = self.api._build_url(endpoint, params=params)
|
||||
raw = requests.get(url, headers=self.api.headers, timeout=10)
|
||||
if raw.status_code != 200:
|
||||
return None
|
||||
data = raw.json()
|
||||
if isinstance(data, dict):
|
||||
lists = [v for v in data.values() if isinstance(v, list)]
|
||||
data = lists[0] if lists else []
|
||||
if data and isinstance(data, list) and "id" in data[0]:
|
||||
return data[0]["id"]
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def run_all(self, mode: str = "quick"):
|
||||
"""Run smoke test checks. mode='quick' for core, 'full' for everything."""
|
||||
base = self.api.base_url
|
||||
t = 10 if mode == "quick" else 30 # quick should be fast
|
||||
|
||||
# Pre-fetch IDs for by-ID lookups (full mode only)
|
||||
if mode == "full":
|
||||
team_id = self._fetch_id("teams", params=[("limit", 1)])
|
||||
player_id = self._fetch_id("players", params=[("limit", 1)])
|
||||
card_id = self._fetch_id("cards", params=[("limit", 1)])
|
||||
game_id = self._fetch_id("games", params=[("limit", 1)])
|
||||
result_id = self._fetch_id("results", params=[("limit", 1)])
|
||||
track_id = self._fetch_id("evolution/tracks", requires_auth=True)
|
||||
else:
|
||||
team_id = player_id = card_id = game_id = result_id = track_id = None
|
||||
|
||||
# ── QUICK: fast, reliable endpoints — deployment canary ──
|
||||
tests = [
|
||||
self.check_url("API docs", "core", f"{base}/docs", timeout=5),
|
||||
self.check(
|
||||
"Current season/week", "core", "current", expect_keys=["season", "week"]
|
||||
),
|
||||
self.check("Rarities", "core", "rarities", expect_list=True, min_count=5),
|
||||
self.check("Cardsets", "core", "cardsets", expect_list=True, min_count=1),
|
||||
self.check(
|
||||
"Pack types", "core", "packtypes", expect_list=True, min_count=1
|
||||
),
|
||||
self.check_url(
|
||||
"OpenAPI schema",
|
||||
"core",
|
||||
f"{base}/openapi.json",
|
||||
expect_content_type="application/json",
|
||||
),
|
||||
self.check(
|
||||
"Teams",
|
||||
"teams",
|
||||
"teams",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Team by abbrev",
|
||||
"teams",
|
||||
"teams",
|
||||
params=[("abbrev", "SKB")],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Players",
|
||||
"players",
|
||||
"players",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Player search",
|
||||
"players",
|
||||
"players/search",
|
||||
params=[("q", "Judge"), ("limit", 3)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Batting cards",
|
||||
"cards",
|
||||
"battingcards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Pitching cards",
|
||||
"cards",
|
||||
"pitchingcards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Packs",
|
||||
"economy",
|
||||
"packs",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Events", "economy", "events", params=[("limit", 5)], expect_list=True
|
||||
),
|
||||
self.check(
|
||||
"Scout opportunities",
|
||||
"scouting",
|
||||
"scout_opportunities",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Evolution tracks",
|
||||
"refractor",
|
||||
"evolution/tracks",
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
requires_auth=True,
|
||||
),
|
||||
]
|
||||
|
||||
# ── FULL: all endpoints, by-ID lookups, sub-resources ──
|
||||
if mode == "full":
|
||||
tests.extend(
|
||||
[
|
||||
# Core extras
|
||||
self.check(
|
||||
"Cardset search",
|
||||
"core",
|
||||
"cardsets/search",
|
||||
params=[("q", "Live"), ("limit", 3)],
|
||||
expect_list=True,
|
||||
),
|
||||
# Team sub-resources
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
f"Team by ID ({team_id})",
|
||||
"teams",
|
||||
f"teams/{team_id}",
|
||||
expect_keys=["id", "abbrev"],
|
||||
),
|
||||
self.check(
|
||||
"Team cards",
|
||||
"teams",
|
||||
f"teams/{team_id}/cards",
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Team evolutions",
|
||||
"teams",
|
||||
f"teams/{team_id}/evolutions",
|
||||
expect_list=True,
|
||||
requires_auth=True,
|
||||
),
|
||||
self.check(
|
||||
"Team lineup",
|
||||
"teams",
|
||||
f"teams/{team_id}/lineup/default",
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Team SP lineup",
|
||||
"teams",
|
||||
f"teams/{team_id}/sp/default",
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Team RP lineup",
|
||||
"teams",
|
||||
f"teams/{team_id}/rp/default",
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Team season record",
|
||||
"teams",
|
||||
f"teams/{team_id}/season-record/11",
|
||||
),
|
||||
]
|
||||
if team_id
|
||||
else []
|
||||
),
|
||||
# Player extras
|
||||
self.check(
|
||||
"Random player",
|
||||
"players",
|
||||
"players/random",
|
||||
expect_keys=["id", "p_name"],
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
f"Player by ID ({player_id})",
|
||||
"players",
|
||||
f"players/{player_id}",
|
||||
expect_keys=["id", "p_name"],
|
||||
),
|
||||
]
|
||||
if player_id
|
||||
else []
|
||||
),
|
||||
# Card extras
|
||||
self.check(
|
||||
"Cards list",
|
||||
"cards",
|
||||
"cards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Batting card ratings",
|
||||
"cards",
|
||||
"battingcardratings",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
requires_auth=True,
|
||||
),
|
||||
self.check(
|
||||
"Pitching card ratings",
|
||||
"cards",
|
||||
"pitchingcardratings",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
requires_auth=True,
|
||||
),
|
||||
self.check(
|
||||
"Card positions",
|
||||
"cards",
|
||||
"cardpositions",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
f"Card by ID ({card_id})",
|
||||
"cards",
|
||||
f"cards/{card_id}",
|
||||
expect_keys=["id"],
|
||||
),
|
||||
]
|
||||
if card_id
|
||||
else []
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
"Batting card by player",
|
||||
"cards",
|
||||
f"battingcards/player/{player_id}",
|
||||
expect_list=True,
|
||||
),
|
||||
]
|
||||
if player_id
|
||||
else []
|
||||
),
|
||||
# Games & results
|
||||
self.check(
|
||||
"Games list",
|
||||
"games",
|
||||
"games",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Results list",
|
||||
"games",
|
||||
"results",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Plays list",
|
||||
"games",
|
||||
"plays",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Plays batting agg",
|
||||
"games",
|
||||
"plays/batting",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Plays pitching agg",
|
||||
"games",
|
||||
"plays/pitching",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
f"Game by ID ({game_id})",
|
||||
"games",
|
||||
f"games/{game_id}",
|
||||
expect_keys=["id"],
|
||||
),
|
||||
self.check(
|
||||
"Game summary", "games", f"plays/game-summary/{game_id}"
|
||||
),
|
||||
]
|
||||
if game_id
|
||||
else []
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
f"Result by ID ({result_id})",
|
||||
"games",
|
||||
f"results/{result_id}",
|
||||
expect_keys=["id"],
|
||||
),
|
||||
]
|
||||
if result_id
|
||||
else []
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
"Team W/L record",
|
||||
"games",
|
||||
f"results/team/{team_id}",
|
||||
params=[("season", 11)],
|
||||
),
|
||||
]
|
||||
if team_id
|
||||
else []
|
||||
),
|
||||
# Economy extras
|
||||
self.check(
|
||||
"Rewards",
|
||||
"economy",
|
||||
"rewards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Game rewards",
|
||||
"economy",
|
||||
"gamerewards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
min_count=1,
|
||||
),
|
||||
self.check(
|
||||
"Gauntlet rewards",
|
||||
"economy",
|
||||
"gauntletrewards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Gauntlet runs",
|
||||
"economy",
|
||||
"gauntletruns",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Awards",
|
||||
"economy",
|
||||
"awards",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Notifications",
|
||||
"economy",
|
||||
"notifs",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
# Scouting extras
|
||||
self.check(
|
||||
"Scout claims",
|
||||
"scouting",
|
||||
"scout_claims",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"MLB players",
|
||||
"scouting",
|
||||
"mlbplayers",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Paperdex",
|
||||
"scouting",
|
||||
"paperdex",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
"Scouting player keys",
|
||||
"scouting",
|
||||
"scouting/playerkeys",
|
||||
params=[("player_id", player_id)],
|
||||
expect_list=True,
|
||||
requires_auth=True,
|
||||
),
|
||||
]
|
||||
if player_id
|
||||
else []
|
||||
),
|
||||
# Stats
|
||||
self.check(
|
||||
"Batting stats",
|
||||
"stats",
|
||||
"batstats",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Pitching stats",
|
||||
"stats",
|
||||
"pitstats",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Decisions",
|
||||
"stats",
|
||||
"decisions",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
self.check(
|
||||
"Decisions rest",
|
||||
"stats",
|
||||
"decisions/rest",
|
||||
params=[("limit", 5)],
|
||||
expect_list=True,
|
||||
),
|
||||
# Refractor extras
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
f"Evolution track ({track_id})",
|
||||
"refractor",
|
||||
f"evolution/tracks/{track_id}",
|
||||
expect_keys=["id", "name"],
|
||||
requires_auth=True,
|
||||
),
|
||||
]
|
||||
if track_id
|
||||
else []
|
||||
),
|
||||
*(
|
||||
[
|
||||
self.check(
|
||||
"Evolution card state",
|
||||
"refractor",
|
||||
f"evolution/cards/{card_id}",
|
||||
requires_auth=True,
|
||||
),
|
||||
]
|
||||
if card_id
|
||||
else []
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# Filter out None results (skipped categories)
|
||||
self.results = [t for t in tests if t is not None]
|
||||
|
||||
def print_results(self):
|
||||
"""Print formatted test results."""
|
||||
passed = sum(1 for r in self.results if r.passed)
|
||||
failed = sum(1 for r in self.results if not r.passed)
|
||||
total = len(self.results)
|
||||
|
||||
print(
|
||||
f"\n{BOLD}Paper Dynasty Smoke Test — {self.env.upper()} ({self.mode}){RESET}"
|
||||
)
|
||||
print(f"{DIM}{self.api.base_url}{RESET}\n")
|
||||
|
||||
current_category = None
|
||||
for r in self.results:
|
||||
if r.category != current_category:
|
||||
current_category = r.category
|
||||
print(f" {CYAN}{current_category.upper()}{RESET}")
|
||||
|
||||
icon = f"{GREEN}PASS{RESET}" if r.passed else f"{RED}FAIL{RESET}"
|
||||
timing = f"{DIM}{r.duration_ms:6.0f}ms{RESET}"
|
||||
|
||||
if "skipped" in r.detail:
|
||||
icon = f"{YELLOW}SKIP{RESET}"
|
||||
|
||||
print(f" {icon} {r.name:<30} {timing} {DIM}{r.detail}{RESET}")
|
||||
|
||||
print(f"\n {BOLD}Results:{RESET} ", end="")
|
||||
if failed == 0:
|
||||
print(f"{GREEN}{passed}/{total} passed{RESET}")
|
||||
else:
|
||||
print(
|
||||
f"{RED}{failed} failed{RESET}, {GREEN}{passed} passed{RESET} of {total}"
|
||||
)
|
||||
|
||||
return failed == 0
|
||||
|
||||
def as_json(self) -> str:
|
||||
"""Return results as JSON for programmatic use."""
|
||||
return json.dumps(
|
||||
[
|
||||
{
|
||||
"name": r.name,
|
||||
"category": r.category,
|
||||
"passed": r.passed,
|
||||
"status_code": r.status_code,
|
||||
"detail": r.detail,
|
||||
"duration_ms": round(r.duration_ms, 1),
|
||||
}
|
||||
for r in self.results
|
||||
],
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Paper Dynasty deployment smoke test")
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
default="dev",
|
||||
choices=["dev", "prod"],
|
||||
help="Environment to test (default: dev)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose", "-v", action="store_true", help="Show response details"
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="Output results as JSON")
|
||||
parser.add_argument(
|
||||
"mode",
|
||||
nargs="?",
|
||||
default="quick",
|
||||
choices=["quick", "full"],
|
||||
help="Test mode: quick (16 checks, ~5s) or full (50+ checks, ~2min)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--category",
|
||||
"-c",
|
||||
action="append",
|
||||
choices=[
|
||||
"core",
|
||||
"teams",
|
||||
"players",
|
||||
"cards",
|
||||
"games",
|
||||
"economy",
|
||||
"scouting",
|
||||
"stats",
|
||||
"refractor",
|
||||
],
|
||||
help="Run only specific categories (can repeat)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
runner = SmokeTestRunner(
|
||||
env=args.env,
|
||||
verbose=args.verbose,
|
||||
categories=args.category,
|
||||
mode=args.mode,
|
||||
)
|
||||
runner.run_all(mode=args.mode)
|
||||
|
||||
if args.json:
|
||||
print(runner.as_json())
|
||||
all_passed = all(r.passed for r in runner.results)
|
||||
else:
|
||||
all_passed = runner.print_results()
|
||||
|
||||
sys.exit(0 if all_passed else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user