"""Publier un jeu en exécutable Windows autonome (bouton "Publier", voir routes/publish/publish_game.py et publish/build_package.py). Le vrai téléchargement du Python portable + Flask (publish/vendor_runtime.py) n'a JAMAIS lieu ici : `ensure_vendor_ready` est monkeypatché vers un dossier factice minuscule — ces tests vérifient l'ASSEMBLAGE du zip (fichiers présents, structure), jamais un vrai lancement du serveur embarqué (nécessite un environnement Windows réel, voir le plan).""" import os import zipfile import pytest import publish.build_package as build_package from tests.test_auth import anon_client # noqa: F401 @pytest.fixture def fake_vendor(tmp_path, monkeypatch): """Un python-embed/pylibs factice, juste assez pour que l'assemblage du zip (copie de fichiers) fonctionne sans jamais toucher au réseau.""" python_dir = tmp_path / "python-embed-amd64" python_dir.mkdir() (python_dir / "python.exe").write_bytes(b"fake-exe") pylibs_dir = tmp_path / "pylibs" pylibs_dir.mkdir() (pylibs_dir / "flask").mkdir() (pylibs_dir / "flask" / "__init__.py").write_text("# fake", encoding="utf-8") def _fake_ensure_vendor_ready(): return str(python_dir), str(pylibs_dir) monkeypatch.setattr(build_package.vendor_runtime, "ensure_vendor_ready", _fake_ensure_vendor_ready) return python_dir, pylibs_dir def test_build_game_zip_contains_the_expected_files(fake_vendor, game): zip_path, game_name = build_package.build_game_zip(game) try: assert game_name # nom du jeu (pytest_test_game par défaut, voir conftest.py) with zipfile.ZipFile(zip_path) as zf: names = set(zf.namelist()) for expected in [ "run.bat", "player_app.py", f"projects/{game}/game.db", "screens/__init__.py", "db/__init__.py", "filters/__init__.py", "core/__init__.py", "core/flask_app.py", "core/jinja_filters.py", "templates/play.html", "static/style.css", "static/csrf_fetch.js", "python-embed/python.exe", "python-embed/pylibs/flask/__init__.py", ]: assert expected in names, f"{expected} manquant du zip" finally: if os.path.isfile(zip_path): os.remove(zip_path) def test_build_game_zip_bakes_the_slug_into_player_app(fake_vendor, game): zip_path, _ = build_package.build_game_zip(game) try: with zipfile.ZipFile(zip_path) as zf: player_app_src = zf.read("player_app.py").decode("utf-8") assert f'SLUG = "{game}"' in player_app_src assert "_SLUG_PLACEHOLDER" not in player_app_src finally: if os.path.isfile(zip_path): os.remove(zip_path) def test_publish_route_returns_a_zip_attachment(fake_vendor, client, game): resp = client.post(f"/game/{game}/publish") assert resp.status_code == 200 assert resp.headers["Content-Type"] == "application/zip" assert "attachment" in resp.headers["Content-Disposition"] assert resp.data[:2] == b"PK" # signature d'un fichier zip def test_publish_route_is_isolated_like_other_game_routes(fake_vendor, anon_client): """Réutilise la garde d'accès existante (core/auth_guard.py) sans code supplémentaire — un compte non-admin ne doit pas pouvoir publier le projet d'un autre (même vérification que /game/ lui-même, voir tests/test_auth.py::test_non_admin_user_is_isolated_to_their_own_project).""" from tests.test_auth import _register, _confirm_2fa, _cleanup_project _register(anon_client, "publishisolation@example.com") _confirm_2fa(anon_client) try: resp = anon_client.post("/game/un-projet-qui-nest-pas-le-sien/publish") assert resp.status_code == 403 finally: _cleanup_project("publishisolation@example.com")