101 lines
4.0 KiB
Python
101 lines
4.0 KiB
Python
"""Раздача веб-приложения: 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("<!doctype html><div id=root></div>", 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")
|
||
(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"<!doctype html><div id=root></div>"
|
||
|
||
|
||
@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")
|