"""Disposable local UI fixture. Run seed/cleanup only on the pgames-test stand.""" import argparse import asyncio import json import tempfile from pathlib import Path from uuid import uuid4 from sqlalchemy import delete from app.config import get_settings from app.converter.pipeline import convert_archive from app.db import SessionLocal from app.maintenance import storage_access from app.models import Game, GameVersion, Role, User from app.security import hash_password from tests.make_sample import build async def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("action", choices=("seed", "check", "cleanup")) args = parser.parse_args() record = get_settings().data_dir / "mod-ui-fixture.json" if args.action == "check": import http.cookiejar import urllib.error import urllib.request data = json.loads(record.read_text()) host = "http://web" opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar())) payload = json.dumps({"username": data["username"], "password": data["password"]}).encode() opener.open(urllib.request.Request(host + "/api/auth/login", data=payload, headers={"Content-Type": "application/json"})) manifest = json.load(opener.open(host + f'/api/versions/{data["version_id"]}/manifest')) key = manifest["files"]["game.json"]["hash"] file_url = host + f'/api/versions/{data["version_id"]}/files/{key}' response = opener.open(file_url) assert response.status == 200 and "private" in response.headers["Cache-Control"] assert json.load(response)["format"] == "pgvn/1" assert opener.open(urllib.request.Request(file_url, method="HEAD")).status == 200 audio = next(f["hash"] for p, f in manifest["files"].items() if p.endswith(".ogg")) response = opener.open(urllib.request.Request(host + f'/api/versions/{data["version_id"]}/files/{audio}', headers={"Range": "bytes=0-9"})) assert response.status == 206 and len(response.read()) == 10 for url, status in [(file_url, 401), (host + f"/_protected_blobs/{key[:2]}/{key[2:4]}/{key}", 404)]: try: urllib.request.urlopen(url) except urllib.error.HTTPError as error: assert error.code == status else: raise AssertionError("Unprotected media route") print("nginx: authenticated JSON/HEAD/Range OK; anonymous and direct internal access denied") return async with storage_access(): async with SessionLocal() as db: if args.action == "cleanup": if not record.exists(): return data = json.loads(record.read_text()) user = await db.get(User, data["user_id"]) if not user or user.username != data["username"] or not user.username.startswith("mod-ui-"): raise RuntimeError("Fixture identity mismatch; cleanup aborted") await db.execute(delete(Game).where(Game.created_by_id == user.id)) await db.delete(user) await db.commit() await db.execute(delete(Role).where(Role.id == data["role_id"])) await db.commit() record.unlink() print("Disposable UI user and games removed") return if record.exists(): print(record.read_text()) return suffix = uuid4().hex[:10] password = uuid4().hex role = Role(name="mod-ui-" + suffix, permissions=["games.play", "versions.fork", "games.upload", "games.edit", "versions.publish"]) db.add(role) await db.flush() user = User(username="mod-ui-" + suffix, password_hash=hash_password(password), role_id=role.id, must_change_password=False) db.add(user) await db.flush() with tempfile.TemporaryDirectory() as folder: root = Path(folder) result = await asyncio.to_thread(convert_archive, build(root / "sample.zip"), "sample.zip", root / "work", lambda _: None) game = Game(title="Проверка редактора модов", created_by_id=user.id) db.add(game) await db.flush() original = GameVersion(game_id=game.id, owner_id=user.id, name="Оригинал для проверки", status="ready", manifest=result.manifest) db.add(original) await db.flush() fork = GameVersion(game_id=game.id, parent_id=original.id, owner_id=user.id, name="Мой тестовый мод", kind="mod", visibility="draft", status="ready", manifest=result.manifest, base_revision_id=original.revision_id) fork._revision_parents = [original.revision_id] db.add(fork) await db.commit() data = dict(username=user.username, password=password, user_id=user.id, role_id=role.id, version_id=fork.id, game_id=game.id) record.write_text(json.dumps(data)) print(json.dumps(data)) if __name__ == "__main__": asyncio.run(main())