cspresso/tests/test_cspresso.py
Miguel Jacq 0f6c2a3458
Some checks failed
CI / test (push) Successful in 2m28s
Lint / test (push) Failing after 29s
Trivy / test (push) Successful in 23s
black
2026-01-02 10:03:39 +11:00

111 lines
4.4 KiB
Python

import asyncio
import json
from typing import Dict, List
import pytest
from cspresso.crawl import crawl_and_generate_csp, main
EXPECTED_DEFAULT = {
"csp": "base-uri 'self'; connect-src 'self' https://asciinema.org; default-src 'self'; font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com; form-action 'self'; frame-ancestors 'self'; frame-src 'self' https://asciinema.org; object-src 'none'; script-src 'self' 'sha256-RgHx7FbVPh+gGLwA0wcJoRi+CfxNF3XcFY/jrcB0gHE=' 'sha256-rnSilqx70ysJSINaYijSOVvGOzG2uzSgg0sneaLAnns=' https://asciinema.org https://cdn.jsdelivr.net; style-src 'self' 'sha256-4hlN/4xO4xlov7xyb72ScvzOD/JFatd+xEAodkxYce8=' 'sha256-Gl5UiI26mHesRnN1ntr0nSHHoAOSYh6XOZx19azQ0Fk=' 'sha256-QuPEWNp+rjVYSLUmS8MFUfVHy/Fh3081C+aZBHEczfA=' 'sha256-SGlitudZ76fvsMgZ/sIf2NcPXwhTuwOaTeB2nvZ0OjA=' 'sha256-emAdQwdjLp3d7bWxgW041mgnWTwEzDtokllulE67Ugc=' 'sha256-eyENtx/C0Tm21pZwh9zjr5u4q+H/WMxZL0zxGk1/M4w=' 'sha256-q/F11RqjQHAMnbYF6csaGyctuo2K9/fRKvGpUsGdeVQ=' 'unsafe-hashes' https://asciinema.org https://cdn.jsdelivr.net https://fonts.googleapis.com; style-src-attr 'sha256-4hlN/4xO4xlov7xyb72ScvzOD/JFatd+xEAodkxYce8=' 'sha256-Gl5UiI26mHesRnN1ntr0nSHHoAOSYh6XOZx19azQ0Fk=' 'sha256-QuPEWNp+rjVYSLUmS8MFUfVHy/Fh3081C+aZBHEczfA=' 'sha256-SGlitudZ76fvsMgZ/sIf2NcPXwhTuwOaTeB2nvZ0OjA=' 'sha256-emAdQwdjLp3d7bWxgW041mgnWTwEzDtokllulE67Ugc=' 'sha256-eyENtx/C0Tm21pZwh9zjr5u4q+H/WMxZL0zxGk1/M4w=' 'sha256-q/F11RqjQHAMnbYF6csaGyctuo2K9/fRKvGpUsGdeVQ=' 'unsafe-hashes';",
}
def parse_csp(csp: str) -> Dict[str, List[str]]:
"""
Parse: "directive value value; directive value; upgrade-insecure-requests; ..."
into: {"directive": ["value", ...], "upgrade-insecure-requests": []}
"""
out: Dict[str, List[str]] = {}
for part in csp.split(";"):
part = part.strip()
if not part:
continue
toks = part.split()
out[toks[0]] = toks[1:]
return out
@pytest.mark.network
@pytest.mark.slow
def test_cspresso_default_snapshot_csp_only():
"""
Hits https://enroll.sh with default args and asserts the exact CSP string.
NOTE: This is an integration snapshot; it will fail if the site changes.
"""
res = asyncio.run(
crawl_and_generate_csp(
"https://enroll.sh",
timeout_ms=60000,
# defaults:
# max_pages=10, include_sourcemaps=False, upgrade_insecure_requests=False
)
)
got = {"csp": res.csp}
assert got == EXPECTED_DEFAULT
@pytest.mark.network
@pytest.mark.slow
def test_include_sourcemaps_adds_jsdelivr_to_connect_src():
"""
With --include-sourcemaps, ensure https://cdn.jsdelivr.net appears in connect-src.
"""
res = asyncio.run(
crawl_and_generate_csp(
"https://enroll.sh",
max_pages=1, # faster; bootstrap assets are on the homepage
timeout_ms=60000,
include_sourcemaps=True,
)
)
d = parse_csp(res.csp)
assert "connect-src" in d
assert "https://cdn.jsdelivr.net" in d["connect-src"]
@pytest.mark.network
@pytest.mark.slow
def test_upgrade_inseure_requests_present():
"""
With --upgrade-insecure-requests, ensure the directive is emitted.
"""
res = asyncio.run(
crawl_and_generate_csp(
"https://enroll.sh",
max_pages=1,
timeout_ms=60000,
upgrade_insecure_requests=True,
)
)
assert "upgrade-insecure-requests;" in res.csp
assert "upgrade-insecure-requests" in parse_csp(res.csp)
@pytest.mark.network
@pytest.mark.slow
def test_cli_json_output_valid(capsys):
"""
Smoke test: running the CLI with --json prints valid JSON (even if other lines slip in).
"""
main(["https://enroll.sh", "--json", "--max-pages", "1", "--timeout-ms", "60000"])
out = capsys.readouterr().out.strip()
# Robust JSON extraction in case of stray log lines.
start = out.find("{")
end = out.rfind("}") + 1
assert start != -1 and end > start, f"stdout did not contain JSON: {out!r}"
payload = json.loads(out[start:end])
assert "csp" in payload
assert isinstance(payload["csp"], str)
assert payload["csp"].endswith(";")
assert payload.get("visited") # should be non-empty
def test_parse_csp_smoke():
d = parse_csp("default-src 'self'; object-src 'none'; upgrade-insecure-requests;")
assert d["default-src"] == ["'self'"]
assert d["object-src"] == ["'none'"]
assert d["upgrade-insecure-requests"] == []