"""Раздача веб-приложения: GET /app… без токена (specs/hub-api «Раздача веб-приложения»).""" import http.client import threading from urllib.parse import urlparse import pytest from jira_hub import api def _get(base, path): """Сырой GET: http.client не нормализует путь (../ и %2e%2e доходят до хаба как есть).""" u = urlparse(base) c = http.client.HTTPConnection(u.hostname, u.port, timeout=10) try: c.request("GET", path) r = c.getresponse() return r.status, r, r.read() finally: c.close() def _serve(tmp_path, web_dir): h = api.Hub(tmp_path / "hub.sqlite", backups_dir=tmp_path / "backups", web_dir=web_dir) srv = api.make_server(h, "127.0.0.1", 0) th = threading.Thread(target=srv.serve_forever, daemon=True) th.start() return srv, th, f"http://127.0.0.1:{srv.server_port}" @pytest.fixture def app_hub(tmp_path): web = tmp_path / "app" (web / "assets").mkdir(parents=True) (web / "index.html").write_text("
", encoding="utf-8") (web / "assets" / "index-abc123.js").write_text("console.log('ok')", encoding="utf-8") (web / "assets" / "index-abc123.css").write_text("body{}", encoding="utf-8") (web / "assets" / "font.bin").write_bytes(b"\x00\x01") for name, data in (("logo.svg", b""), ("p.png", b"\x89PNG"), ("f.ico", b"\x00"), ("f.woff2", b"wOF2"), ("index-abc123.js.map", b"{}")): (web / "assets" / name).write_bytes(data) (tmp_path / "secret.txt").write_text("секрет", encoding="utf-8") srv, th, base = _serve(tmp_path, web) yield base srv.shutdown() srv.server_close() th.join(5) def test_asset_served_with_type_and_immutable_cache(app_hub): status, r, body = _get(app_hub, "/app/assets/index-abc123.js") assert status == 200 assert r.getheader("Content-Type") == "text/javascript; charset=utf-8" assert r.getheader("Cache-Control") == "public, max-age=31536000, immutable" assert body == b"console.log('ok')" status, r, _ = _get(app_hub, "/app/assets/index-abc123.css") assert r.getheader("Content-Type") == "text/css; charset=utf-8" status, r, _ = _get(app_hub, "/app/assets/font.bin") assert status == 200 and r.getheader("Content-Type") == "application/octet-stream" @pytest.mark.parametrize("path", ["/app", "/app/", "/app/board", "/app/issues/AURORA-1620", "/app/issues?sprint=x"]) def test_app_paths_get_index_without_token(app_hub, path): status, r, body = _get(app_hub, path) assert status == 200 assert r.getheader("Content-Type") == "text/html; charset=utf-8" assert r.getheader("Cache-Control") == "no-cache" assert body == b"" @pytest.mark.parametrize("path", ["/app/assets/../../secret.txt", "/app/assets/%2e%2e/%2e%2e/secret.txt", "/app/assets/..%2F..%2Fsecret.txt", "/app/assets/missing.js", "/app/assets/"]) def test_asset_outside_or_missing_is_404(app_hub, path): status, _, body = _get(app_hub, path) assert status == 404 assert "секрет".encode("utf-8") not in body def test_not_built_is_503_with_hint(tmp_path): srv, th, base = _serve(tmp_path, tmp_path / "no-build") try: for path in ("/app/board", "/app/assets/index-abc123.js"): status, r, body = _get(base, path) assert status == 503 assert r.getheader("Content-Type") == "text/html; charset=utf-8" assert "make web".encode("utf-8") in body finally: srv.shutdown() srv.server_close() th.join(5) def test_other_paths_are_not_app(app_hub): status, _, _ = _get(app_hub, "/application") assert status == 404 # не префикс /app/, обычный 404 API status, _, _ = _get(app_hub, "/board") assert status == 401 # API по-прежнему под токеном def test_default_web_dir_is_package_build(tmp_path): h = api.Hub(tmp_path / "hub.sqlite", backups_dir=tmp_path / "backups") assert h.web_dir == api.WEB_APP assert api.WEB_APP.parts[-3:] == ("jira_hub", "web", "app") @pytest.mark.parametrize("path", ["/", "/links"]) def test_old_board_addresses_redirect_to_app(app_hub, path): status, r, body = _get(app_hub, path) assert status == 302 assert r.getheader("Location") == "/app/board" assert body == b"" def test_links_page_is_gone(): assert not hasattr(api, "PAGE") assert not (api.WEB_APP.parent / "links.html").exists() @pytest.mark.parametrize("name,ctype", [("logo.svg", "image/svg+xml"), ("p.png", "image/png"), ("f.ico", "image/x-icon"), ("f.woff2", "font/woff2"), ("index-abc123.js.map", "application/json")]) def test_asset_content_types(app_hub, name, ctype): status, r, _ = _get(app_hub, "/app/assets/" + name) assert status == 200 and r.getheader("Content-Type") == ctype @pytest.mark.parametrize("path", ["/app/assets/%00", "/app/assets/a%00.js", "/app/assets/%00/../index-abc123.js"]) def test_nul_in_asset_path_is_404(app_hub, path): status, _, _ = _get(app_hub, path) assert status == 404