up
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# Проверки площадки для разработчика
|
||||
|
||||
Для работы площадки эти файлы не нужны. Обычный запуск: `make venue`.
|
||||
|
||||
- `deploy.py` — проверяет первый запуск, запуск без интернета и обновление без Docker.
|
||||
- `smoke.py` — проверяет работающую площадку через API. Без `--verify` создаёт
|
||||
тестовые данные; с `--verify` проверяет ранее созданные данные.
|
||||
|
||||
Команды из корня проекта:
|
||||
|
||||
```sh
|
||||
python3 tests/venue/deploy.py
|
||||
python3 tests/venue/smoke.py --verify
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Check first, offline and update runs without Docker or production writes."""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
source = Path(__file__).resolve().parents[2] / "venue" / "deploy.sh"
|
||||
for name, cold, update in [("first", True, False), ("offline", False, False), ("update", False, True)]:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
venue = root / "venue"
|
||||
venue.mkdir()
|
||||
shutil.copy(source, venue / "deploy.sh")
|
||||
log = root / "commands.log"
|
||||
bin_dir = root / "bin"
|
||||
bin_dir.mkdir()
|
||||
for command in ["docker", "make", "npm"]:
|
||||
stub = bin_dir / command
|
||||
stub.write_text(
|
||||
'#!/bin/sh\n'
|
||||
'echo "$(basename "$0") $*" >> "$TEST_LOG"\n'
|
||||
'if [ "$1 $2" = "image inspect" ] && [ "$TEST_COLD" = 1 ]; then exit 1; fi\n'
|
||||
)
|
||||
stub.chmod(0o755)
|
||||
if not cold:
|
||||
(venue / "migrations").mkdir()
|
||||
(venue / ".env").write_text("EXISTING_SECRET=unchanged\n")
|
||||
for artifact in ["backend/evening_detective_server", "backend/goose", "backend/dev-user.sql", "frontend/dist/index.html"]:
|
||||
path = venue / artifact
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.touch()
|
||||
env = {**os.environ, "PATH": str(bin_dir) + os.pathsep + os.environ["PATH"], "TEST_LOG": str(log), "TEST_COLD": str(int(cold))}
|
||||
subprocess.run(["bash", str(venue / "deploy.sh"), *(["--build"] if update else [])], env=env, check=True, capture_output=True)
|
||||
commands = log.read_text()
|
||||
assert "volume create evening-detective-local_postgres_data" in commands, name
|
||||
assert "volume create evening-detective-local_rustfs_data" in commands, name
|
||||
assert "compose up -d --no-build --pull never" in commands, name
|
||||
assert "compose --profile setup run --rm bootstrap" in commands, name
|
||||
assert ("make -C .. build" in commands) == (cold or update), name
|
||||
assert ("compose pull" in commands) == (cold or update), name
|
||||
assert ("compose build --pull=false" in commands) == (cold or update), name
|
||||
if cold:
|
||||
config = dict(line.split("=", 1) for line in (venue / ".env").read_text().splitlines())
|
||||
assert len(config["JWT_SECRET"]) == 64
|
||||
assert len(config["VENUE_USER_PASSWORD"]) == 16
|
||||
assert (venue / ".env").stat().st_mode & 0o777 == 0o600
|
||||
else:
|
||||
assert (venue / ".env").read_text() == "EXISTING_SECRET=unchanged\n", name
|
||||
print("PASS: automatic first setup, offline reuse, explicit update and preserved secrets")
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Create a venue test game, then verify persistence with --verify after restart."""
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
root = Path(__file__).resolve().parents[2] / "venue"
|
||||
assert (root / "backend" / "evening_detective_server").is_file(), "Backend artifact missing"
|
||||
assert (root / "backend" / "goose").is_file(), "Migration tool missing"
|
||||
assert (root / "backend" / "dev-user.sql").is_file(), "Account bootstrap SQL missing"
|
||||
assert (root / "frontend" / "dist" / "index.html").is_file(), "Frontend artifact missing"
|
||||
assert list((root / "migrations").glob("*.sql")), "Migrations missing"
|
||||
config = dict(
|
||||
line.split("=", 1)
|
||||
for line in (root / ".env").read_text().splitlines()
|
||||
if line and not line.startswith("#")
|
||||
)
|
||||
bind = config.get("VENUE_BIND_IP", "127.0.0.1")
|
||||
if bind == "0.0.0.0":
|
||||
bind = "127.0.0.1"
|
||||
base = f'http://{bind}:{config.get("VENUE_PORT", "8088")}'
|
||||
token = ""
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
|
||||
|
||||
def request(path, body=None, raw=False):
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if token:
|
||||
headers["Authorization"] = "Bearer " + token
|
||||
req = urllib.request.Request(
|
||||
base + path,
|
||||
data=None if body is None else json.dumps(body).encode(),
|
||||
headers=headers,
|
||||
)
|
||||
with opener.open(req, timeout=10) as response:
|
||||
data = response.read()
|
||||
return data if raw else json.loads(data)
|
||||
|
||||
|
||||
def verify(state):
|
||||
game = request(f'/api/games/{state["gameID"]}')["game"]
|
||||
teams = game["teams"]
|
||||
assert any(
|
||||
team["ID"] == state["teamID"]
|
||||
and team["participationStatus"] == "confirmed"
|
||||
for team in teams
|
||||
), "Test team or confirmed participation was not preserved"
|
||||
assert request("/api/files/" + state["filename"], raw=True) == base64.b64decode(
|
||||
state["data"]
|
||||
), "Uploaded material was not preserved"
|
||||
assert b'<div id="app">' in request("/games", raw=True), "SPA route failed"
|
||||
|
||||
|
||||
try:
|
||||
token = request(
|
||||
"/api/auth/login",
|
||||
{
|
||||
"email": config["VENUE_USER_EMAIL"],
|
||||
"password": config["VENUE_USER_PASSWORD"],
|
||||
},
|
||||
)["accessToken"]
|
||||
state_file = root / "packages" / "smoke.json"
|
||||
if "--verify" in sys.argv:
|
||||
state = json.loads(state_file.read_text())
|
||||
else:
|
||||
scenario = request("/api/scenario", {"name": "TEST venue smoke"})["ID"]
|
||||
start = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=2)
|
||||
game = request(
|
||||
"/api/games",
|
||||
{
|
||||
"name": "TEST venue smoke",
|
||||
"description": "Disposable offline environment check",
|
||||
"startAt": start.isoformat(),
|
||||
"scenarioID": scenario,
|
||||
"format": "online",
|
||||
},
|
||||
)["ID"]
|
||||
team = request(
|
||||
"/api/teams",
|
||||
{"gameID": game, "name": "TEST team", "participantsCount": 2},
|
||||
)["ID"]
|
||||
data = base64.b64encode(b"%PDF-1.4\n%venue-smoke\n%%EOF\n").decode()
|
||||
filename = request(
|
||||
"/api/files/upload", {"filename": "venue-smoke.pdf", "data": data}
|
||||
)["filename"]
|
||||
state = {"gameID": game, "teamID": team, "filename": filename, "data": data}
|
||||
state_file.parent.mkdir(exist_ok=True)
|
||||
state_file.write_text(json.dumps(state))
|
||||
verify(state)
|
||||
for path, body in [
|
||||
("/api/auth/signup", {"username": "Test", "email": "test@venue.invalid", "acceptTerms": True, "acceptPrivacy": True}),
|
||||
("/api/auth/refresh-password", {"email": config["VENUE_USER_EMAIL"]}),
|
||||
]:
|
||||
try:
|
||||
request(path, body)
|
||||
except urllib.error.HTTPError as error:
|
||||
assert error.code == 400, "Unexpected disabled operation status"
|
||||
assert json.loads(error.read())["code"] == 9, "Expected FailedPrecondition"
|
||||
else:
|
||||
raise AssertionError("Email-based account operation succeeded locally")
|
||||
print("PASS: login, test game, confirmed team, material, SPA and disabled email operations")
|
||||
except urllib.error.HTTPError as error:
|
||||
# Do not print response bodies: authentication responses may contain credentials.
|
||||
print(f"FAIL: venue API returned HTTP {error.code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user