"""Deployment trust gates and deterministic bundle checks; no remote writes."""

import copy
import hashlib
import json
import shutil
import subprocess
import sys
from types import SimpleNamespace
from urllib.parse import parse_qs, urlsplit

import pytest
from test_hf_recipe_hub import repository as repository_fixture

from hessian_recipes.hf_recipe_hub import build
from hessian_recipes import hub_deploy
from hessian_recipes.hub_deploy import check_eligibility, verify_bundle, verify_delivery


@pytest.fixture
def repository(tmp_path):
    return repository_fixture.__wrapped__(tmp_path)


REPOSITORY = "ChTauchmann/hessian-recipes"
SHA = "a" * 40
APP_JWT = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmaXh0dXJlIn0.c2lnbmF0dXJl"
WORKFLOW = {"id": 301, "name": "checks", "path": ".github/workflows/ci.yml"}
RUN = {
    "id": 701,
    "workflow_id": WORKFLOW["id"],
    "name": "checks",
    "path": ".github/workflows/ci.yml",
    "event": "push",
    "status": "completed",
    "conclusion": "success",
    "head_sha": SHA,
    "head_branch": "main",
    "head_repository": {"full_name": REPOSITORY},
    "repository": {"full_name": REPOSITORY},
    "run_attempt": 1,
}
QUERY = f"actions/workflows/ci.yml/runs?head_sha={SHA}&branch=main&per_page=20"


class GitHubFixture:
    """Only the allowed read endpoints exist; credentials/network are absent."""

    def __init__(self, *, run=None, workflow=None, head=SHA, runs=None):
        self.run = copy.deepcopy(RUN if run is None else run)
        self.workflow = copy.deepcopy(WORKFLOW if workflow is None else workflow)
        self.head = head
        self.runs = copy.deepcopy([self.run] if runs is None else runs)
        self.calls = []

    def __call__(self, endpoint):
        self.calls.append(endpoint)
        values = {
            "commits/main": {"sha": self.head},
            "actions/workflows/ci.yml": self.workflow,
            "actions/runs/701": self.run,
            QUERY: {"workflow_runs": self.runs, "total_count": len(self.runs)},
        }
        if endpoint not in values:
            raise AssertionError(f"Unexpected GitHub endpoint: {endpoint}")
        return copy.deepcopy(values[endpoint])


def event(run=None):
    return {
        "action": "completed",
        "repository": {"full_name": REPOSITORY, "default_branch": "main"},
        "workflow_run": copy.deepcopy(RUN if run is None else run),
    }


def eligible(*, github=None, payload=None, event_name="workflow_run", sha=SHA, ref="refs/heads/main"):
    return check_eligibility(
        sha, event_name=event_name, event=event() if payload is None else payload,
        ref=ref, github_get=github or GitHubFixture(),
    )


def test_successful_main_push_is_bound_to_live_run_and_exact_workflow():
    github = GitHubFixture()
    result = eligible(github=github)
    assert result["run_id"] == RUN["id"]
    assert result["source_commit"] == SHA
    assert "actions/runs/701" in github.calls
    assert "actions/workflows/ci.yml" in github.calls
    assert "commits/main" in github.calls


def test_public_app_is_rejected_before_any_remote_write(tmp_path, monkeypatch):
    monkeypatch.setattr(sys, "argv", ["deploy", "publish", "--expected-sha", SHA,
                                     "--bundle", str(tmp_path / "bundle"),
                                     "--receipt", str(tmp_path / "receipt.json")])
    monkeypatch.setenv("HF_OIDC_RESOURCE", hub_deploy.RESOURCE)
    monkeypatch.setattr(hub_deploy, "_gate", lambda sha: {"source_commit": sha})
    monkeypatch.setattr(hub_deploy, "_source", lambda *args: None)
    monkeypatch.setattr(hub_deploy, "verify_bundle", lambda *args: {})
    api = SimpleNamespace(space_info=lambda _: SimpleNamespace(
        id=hub_deploy.SPACE, private=True, protected=False, sdk="static",
        host="https://ctauchmann-hessian-recipes.static.hf.space"))
    monkeypatch.setitem(sys.modules, "huggingface_hub", SimpleNamespace(
        HfApi=lambda **kwargs: api, get_token=lambda: "hf_jwt_fixture"))
    monkeypatch.setattr(hub_deploy, "_read", lambda *args: b"public website bytes")
    writes = []
    monkeypatch.setattr(hub_deploy, "publish", lambda *args, **kwargs: writes.append(args))
    with pytest.raises(SystemExit, match="anonymous access"):
        hub_deploy.main()
    assert not writes and not (tmp_path / "receipt.json").exists()


@pytest.mark.parametrize("outcome", [
    "failure", "cancelled", "timed_out", "action_required", "neutral", "skipped", None,
])
def test_completion_event_never_promotes_unsuccessful_checks(outcome):
    run = RUN | {"conclusion": outcome}
    with pytest.raises(ValueError):
        eligible(github=GitHubFixture(run=run), payload=event(run))


@pytest.mark.parametrize("patch", [
    {"status": "queued"}, {"status": "in_progress"},
    {"event": "pull_request"}, {"event": "workflow_dispatch"},
    {"head_branch": "feature"}, {"head_sha": "b" * 40},
    {"head_repository": {"full_name": "someone/hessian-recipes"}},
    {"head_repository": None},
    {"name": "checks-lookalike"}, {"path": ".github/workflows/lookalike.yml"},
    {"workflow_id": 302}, {"id": 702},
])
def test_live_run_metadata_cannot_be_replaced_by_a_success_looking_event(patch):
    # The webhook claims a valid run. The independently fetched run disagrees.
    with pytest.raises(ValueError):
        eligible(github=GitHubFixture(run=RUN | patch))


@pytest.mark.parametrize("patch", [
    {"event": "pull_request"}, {"head_sha": "b" * 40},
    {"workflow_id": 302}, {"head_branch": "feature"},
    {"head_repository": {"full_name": "fork/hessian-recipes"}},
])
def test_webhook_metadata_must_also_be_trusted_and_match_the_fetched_run(patch):
    with pytest.raises(ValueError):
        eligible(payload=event(RUN | patch))


@pytest.mark.parametrize("patch", [
    {"id": 302}, {"name": "not-checks"}, {"path": ".github/workflows/other.yml"},
])
def test_same_workflow_name_is_not_sufficient_identity(patch):
    with pytest.raises(ValueError):
        eligible(github=GitHubFixture(workflow=WORKFLOW | patch))


def test_stale_success_cannot_replace_the_current_main_site():
    with pytest.raises(ValueError):
        eligible(github=GitHubFixture(head="b" * 40))


@pytest.mark.parametrize("sha", ["a" * 39, "a" * 41, "A" * 40, "z" * 40, " " + SHA, "main", "$(touch bad)"])
def test_expected_revision_is_a_full_lowercase_commit_hash(sha):
    with pytest.raises(ValueError):
        eligible(sha=sha)


@pytest.mark.parametrize("ref", ["refs/heads/feature", "refs/tags/main", "main", ""])
def test_only_default_branch_ref_is_eligible(ref):
    with pytest.raises(ValueError):
        eligible(ref=ref)


@pytest.mark.parametrize("name,payload", [
    ("push", {}), ("pull_request", {}), ("workflow_run", {}),
    ("workflow_run", event() | {"action": "requested"}),
    ("workflow_run", event() | {"repository": {"full_name": "other/repo"}}),
])
def test_untrusted_or_incomplete_trigger_is_refused(name, payload):
    with pytest.raises(ValueError):
        eligible(event_name=name, payload=payload)


def test_manual_run_requires_a_real_successful_main_push_for_the_same_sha():
    failed = RUN | {"id": 699, "conclusion": "failure"}
    pr = RUN | {"id": 700, "event": "pull_request"}
    github = GitHubFixture(runs=[failed, pr, RUN])
    result = eligible(github=github, event_name="workflow_dispatch", payload={})
    assert result["run_id"] == RUN["id"]
    assert QUERY in github.calls


@pytest.mark.parametrize("runs", [[], [RUN | {"conclusion": "failure"}],
                                 [RUN | {"event": "pull_request"}],
                                 [RUN | {"head_sha": "b" * 40}],
                                 [RUN | {"workflow_id": 999}]])
def test_manual_deployment_cannot_bypass_checks(runs):
    with pytest.raises(ValueError):
        eligible(github=GitHubFixture(runs=runs), event_name="workflow_dispatch", payload={})


def git_sha(repository):
    return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repository, text=True).strip()


def bundle(repository, tmp_path):
    output = tmp_path / "bundle"
    build(repository, output)
    return output


def test_clean_bundle_rebuild_matches_the_accepted_git_commit(repository, tmp_path):
    output = bundle(repository, tmp_path)
    result = verify_bundle(repository, output, git_sha(repository))
    assert result["source_commit"] == git_sha(repository)
    assert result["uncommitted_source_paths"] == []


@pytest.mark.parametrize("mutation", ["changed", "missing", "extra", "symlink", "directory-symlink"])
def test_bundle_inventory_and_regular_file_boundary_are_enforced(repository, tmp_path, mutation):
    output = bundle(repository, tmp_path)
    if mutation == "changed":
        (output / "index.html").write_text("tampered output")
    elif mutation == "missing":
        (output / "assets/site.js").unlink()
    elif mutation == "extra":
        (output / "unreviewed.txt").write_text("unreviewed output")
    elif mutation == "symlink":
        link = output / "index.html"
        outside = tmp_path / "outside.html"
        outside.write_bytes(link.read_bytes())
        link.unlink()
        link.symlink_to(outside)
    else:
        outside = tmp_path / "outside-assets"
        shutil.move(str(output / "assets"), outside)
        (output / "assets").symlink_to(outside, target_is_directory=True)
    with pytest.raises(ValueError):
        verify_bundle(repository, output, git_sha(repository))


def test_rehashing_a_poisoned_bundle_cannot_forge_a_valid_build(repository, tmp_path):
    output = bundle(repository, tmp_path)
    index = output / "index.html"
    index.write_text("<h1>Attacker-controlled but consistently hashed site</h1>")
    manifest = json.loads((output / "hub-manifest.json").read_text())
    manifest["files_sha256"]["index.html"] = hashlib.sha256(index.read_bytes()).hexdigest()
    (output / "hub-manifest.json").write_text(json.dumps(manifest))
    with pytest.raises(ValueError):
        verify_bundle(repository, output, git_sha(repository))


@pytest.mark.parametrize("field,value", [
    ("source_commit", "b" * 40), ("intended_visibility", "public"),
    ("uncommitted_source_paths", ["docs/README.md"]),
    ("source_sha256", {"README.md": "0" * 64}),
])
def test_manifest_claims_must_match_the_clean_rebuild(repository, tmp_path, field, value):
    output = bundle(repository, tmp_path)
    manifest = json.loads((output / "hub-manifest.json").read_text())
    manifest[field] = value
    (output / "hub-manifest.json").write_text(json.dumps(manifest))
    with pytest.raises(ValueError):
        verify_bundle(repository, output, git_sha(repository))


@pytest.mark.parametrize("staged", [False, True])
def test_source_changes_after_checks_are_not_published(repository, tmp_path, staged):
    output = bundle(repository, tmp_path)
    source = repository / "src/hessian_recipes/__init__.py"
    source.write_text('VERSION = "changed after checks"\n')
    if staged:
        subprocess.run(["git", "add", str(source)], cwd=repository, check=True)
    with pytest.raises(ValueError):
        verify_bundle(repository, output, git_sha(repository))


def test_untracked_review_source_cannot_enter_a_ci_snapshot(repository, tmp_path):
    # Builder supports selected first-review files locally; CI must require clean committed bytes.
    source = repository / "src/hessian_recipes/hub_theme.py"
    source.write_text("# uncommitted review source\n")
    output = bundle(repository, tmp_path)
    with pytest.raises(ValueError):
        verify_bundle(repository, output, git_sha(repository))


def test_checkout_head_must_be_the_commit_that_passed_checks(repository, tmp_path):
    output = bundle(repository, tmp_path)
    with pytest.raises(ValueError):
        verify_bundle(repository, output, "b" * 40)


class DeliveryFixture:
    """A complete tiny Git tree plus an authenticated App; never contacts HTTP."""

    host = "https://ctauchmann-hessian-recipes.static.hf.space"
    commit = "c" * 40

    def __init__(self, folder, *, lfs=False):
        from types import SimpleNamespace

        self.folder = folder
        self.reads = []
        self.jwt_calls = []
        self.sleep_calls = []
        self.anonymous_status = 401
        self.anonymous_leak = None
        self.served_manifests = []
        folder.mkdir()
        for name, raw in {
            "hub-manifest.json": json.dumps({"source_commit": SHA}).encode(),
            "index.html": b"<!doctype html><h1>Private fixture</h1>",
            "assets/site.css": b"body { color: purple; }",
            "sources/docs/validation.md": b"# Private fixture evidence\n",
        }.items():
            target = folder / name
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_bytes(raw)
        entries = []
        for path in sorted(folder.rglob("*")):
            if not path.is_file():
                continue
            raw = path.read_bytes()
            entries.append(SimpleNamespace(
                rfilename=str(path.relative_to(folder)),
                blob_id=hashlib.sha1(b"blob " + str(len(raw)).encode() + b"\0" + raw).hexdigest(),
                lfs=SimpleNamespace(sha256=hashlib.sha256(raw).hexdigest()) if lfs else None,
            ))
        # HF can add this file independently; it is not website payload.
        entries.append(SimpleNamespace(rfilename=".gitattributes", blob_id="managed", lfs=None))
        self.info = SimpleNamespace(
            id="ctauchmann/hessian-recipes", sha=self.commit, private=True,
            protected=False, visibility="private", sdk="static", siblings=entries,
        )
        self.space = SimpleNamespace(host=self.host)

    def repo_info(self, repo_id, **kwargs):
        assert repo_id == "ctauchmann/hessian-recipes"
        assert kwargs == {"repo_type": "space", "files_metadata": True}
        return self.info

    def space_info(self, repo_id):
        assert repo_id == "ctauchmann/hessian-recipes"
        return self.space

    def read(self, url, headers):
        from urllib.error import HTTPError
        from urllib.parse import urlsplit

        self.reads.append({"url": url, "authenticated": bool(headers)})
        if headers:
            assert headers == {"Cache-Control": "no-cache"}
            assert parse_qs(urlsplit(url).query) == {"__sign": [APP_JWT]}
            assert urlsplit(url).path == "/hub-manifest.json"
            if self.served_manifests:
                return self.served_manifests.pop(0)
            return (self.folder / "hub-manifest.json").read_bytes()
        if urlsplit(url).path == self.anonymous_leak:
            return b"private bytes exposed"
        raise HTTPError(url, self.anonymous_status, "fixture denied", {}, None)

    def install(self, monkeypatch):
        monkeypatch.setattr(hub_deploy, "_read", self.read)
        monkeypatch.setattr(hub_deploy.time, "sleep", self.sleep_calls.append)

        def exchange(token):
            assert token == "fixture-publisher-token"
            self.jwt_calls.append(token)
            return APP_JWT

        monkeypatch.setattr(hub_deploy, "_space_jwt", exchange)

    def verify(self, **kwargs):
        return verify_delivery(
            self, self.folder, {"commit": self.commit}, "fixture-publisher-token", **kwargs,
        )


@pytest.mark.parametrize("lfs", [False, True])
def test_delivery_verifies_all_git_or_lfs_bytes_live_manifest_and_privacy(tmp_path, monkeypatch, lfs):
    remote = DeliveryFixture(tmp_path / "delivery", lfs=lfs)
    remote.install(monkeypatch)
    result = remote.verify(attempts=1)
    assert result == {
        "remote_files_verified": 4, "served_manifest_matches": True,
        "anonymous_access_denied": True, "private": True,
    }
    assert urlsplit(remote.reads[0]["url"]).path == "/hub-manifest.json"
    assert parse_qs(urlsplit(remote.reads[0]["url"]).query) == {"__sign": [APP_JWT]}
    assert remote.reads[0]["authenticated"] is True
    assert remote.jwt_calls == ["fixture-publisher-token"]
    assert {urlsplit(read["url"]).path for read in remote.reads[1:]} == {
        "/", "/index.html", "/assets/site.css", "/sources/docs/validation.md",
    }
    assert all(urlsplit(read["url"]).query.startswith("private-check=") for read in remote.reads[1:])
    assert not any(read["authenticated"] for read in remote.reads[1:])


@pytest.mark.parametrize("patch", [
    {"private": False}, {"protected": True}, {"visibility": "protected"},
    {"id": "other/hessian-recipes"}, {"sdk": "docker"}, {"sha": "d" * 40},
])
def test_changed_remote_owner_visibility_sdk_or_head_stops_delivery_verification(tmp_path, monkeypatch, patch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    for key, value in patch.items():
        setattr(remote.info, key, value)
    with pytest.raises(ValueError):
        remote.verify(attempts=1)
    assert not remote.reads


@pytest.mark.parametrize("mutation", ["extra", "missing", "wrong-git-blob", "wrong-lfs-digest"])
def test_remote_inventory_and_content_must_exactly_match_the_approved_bundle(tmp_path, monkeypatch, mutation):
    from types import SimpleNamespace

    remote = DeliveryFixture(tmp_path / "delivery", lfs=mutation == "wrong-lfs-digest")
    remote.install(monkeypatch)
    if mutation == "extra":
        remote.info.siblings.append(SimpleNamespace(rfilename="sources/removed-private.md", blob_id="old", lfs=None))
    elif mutation == "missing":
        remote.info.siblings.pop(0)
    elif mutation == "wrong-git-blob":
        remote.info.siblings[0].blob_id = "0" * 40
    else:
        remote.info.siblings[0].lfs.sha256 = "0" * 64
    with pytest.raises(ValueError):
        remote.verify(attempts=1)
    assert not remote.reads


@pytest.mark.parametrize("host", [
    "http://ctauchmann-hessian-recipes.static.hf.space", "https://example.com",
    "https://someone-else.static.hf.space", "https://user@ctauchmann-hessian-recipes.static.hf.space",
    "https://ctauchmann-hessian-recipes.static.hf.space:444",
    "https://ctauchmann-hessian-recipes.static.hf.space/other-app",
    "https://ctauchmann-hessian-recipes.static.hf.space?query=changed", "",
])
def test_publisher_authorization_is_never_forwarded_to_an_unapproved_app_origin(tmp_path, monkeypatch, host):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.space.host = host
    with pytest.raises(ValueError):
        remote.verify(attempts=1)
    assert not remote.reads


def test_deployment_waits_for_the_exact_new_manifest_instead_of_accepting_old_content(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    expected = (remote.folder / "hub-manifest.json").read_bytes()
    remote.served_manifests = [b"old manifest", expected]
    assert remote.verify(attempts=2)["served_manifest_matches"] is True
    assert len([r for r in remote.reads if r["authenticated"]]) == 2
    assert remote.sleep_calls == [2]
    assert remote.jwt_calls == ["fixture-publisher-token"]


def test_bounded_deployment_poll_fails_when_old_manifest_never_updates(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.served_manifests = [b"old manifest", b"old manifest"]
    with pytest.raises(ValueError):
        remote.verify(attempts=2)
    assert len(remote.reads) == 2
    assert remote.sleep_calls == [2]
    assert remote.jwt_calls == ["fixture-publisher-token"]


def test_zero_poll_attempts_cannot_claim_the_app_manifest_was_verified(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    with pytest.raises(ValueError):
        remote.verify(attempts=0)


@pytest.mark.parametrize("path", ["/", "/assets/site.css", "/sources/docs/validation.md"])
def test_any_anonymous_private_content_response_fails_the_privacy_gate(tmp_path, monkeypatch, path):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.anonymous_leak = path
    with pytest.raises(ValueError):
        remote.verify(attempts=1)


@pytest.mark.parametrize("status", [401, 403, 404])
def test_anonymous_denial_does_not_require_disclosing_private_repository_existence(tmp_path, monkeypatch, status):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.anonymous_status = status
    assert remote.verify(attempts=1)["anonymous_access_denied"] is True


def test_anonymous_server_error_is_not_misreported_as_privacy_proof(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.anonymous_status = 500
    with pytest.raises(ValueError):
        remote.verify(attempts=1)


def test_manual_retry_cannot_skip_a_newer_failed_execution_of_the_same_commit():
    failed_newer = RUN | {"id": 702, "conclusion": "failure"}
    with pytest.raises(ValueError):
        eligible(github=GitHubFixture(runs=[RUN, failed_newer]), event_name="workflow_dispatch", payload={})


def test_private_app_auth_failure_reports_only_status(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    from urllib.error import HTTPError

    def denied(url, headers):
        raise HTTPError(url, 401, "Bearer secret-response-content", {}, None)

    monkeypatch.setattr(hub_deploy, "_read", denied)
    with pytest.raises(ValueError, match=r"manifest \(HTTP 401\)") as error:
        remote.verify(attempts=1)
    assert "secret-response-content" not in str(error.value)
    assert "fixture-publisher-token" not in str(error.value)
    assert APP_JWT not in str(error.value)


def test_delivery_reuses_the_preflight_app_token_without_a_second_exchange(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    assert remote.verify(attempts=1, app_token=APP_JWT)["served_manifest_matches"] is True
    assert remote.jwt_calls == []
    assert parse_qs(urlsplit(remote.reads[0]["url"]).query) == {"__sign": [APP_JWT]}


def test_space_jwt_exchange_uses_the_fixed_hub_resource_and_expiry(monkeypatch):
    calls = []

    def read(url, headers):
        calls.append((url, headers))
        return json.dumps({"token": APP_JWT}).encode()

    monkeypatch.setattr(hub_deploy, "_read", read)
    assert hub_deploy._space_jwt("fixture-publisher-token") == APP_JWT
    assert len(calls) == 1
    url, headers = calls[0]
    assert url == "https://huggingface.co/api/spaces/ctauchmann/hessian-recipes/jwt?expires_in=300"
    assert headers["Authorization"] == "Bearer fixture-publisher-token"
    assert "x-hf-authorization" not in headers
    assert urlsplit(url).hostname == "huggingface.co"
    assert "fixture-publisher-token" not in url


@pytest.mark.parametrize("response", [
    b"not json: sensitive response", b"\xff", b"[]", b"null", b"123", b'"text"',
    b"{}", b'{"token":null}', b'{"token":123}', b'{"token":true}',
    b'{"token":[]}', b'{"token":{}}', b'{"token":""}',
    b'{"token":"one.two"}', b'{"token":"one.two.three.four"}',
    b'{"token":".two.three"}', b'{"token":"one..three"}',
    b'{"token":"one.two."}', b'{"token":"one.two.invalid+base64"}',
    b'{"token":"one.two.invalid/base64"}', b'{"token":"one.two.invalid="}',
    b'{"token":"one.two.invalid space"}', b'{"token":"one.two.invalid\\nvalue"}',
])
def test_space_jwt_exchange_rejects_malformed_responses_without_echoing_them(monkeypatch, response):
    monkeypatch.setattr(hub_deploy, "_read", lambda *args: response)
    with pytest.raises(ValueError) as error:
        hub_deploy._space_jwt("fixture-publisher-token")
    assert "sensitive response" not in str(error.value)
    assert "fixture-publisher-token" not in str(error.value)
    assert response.decode(errors="replace") not in str(error.value)


@pytest.mark.parametrize("status", [401, 403, 429, 500])
def test_space_jwt_exchange_reports_only_safe_http_status(monkeypatch, status):
    from urllib.error import HTTPError

    def denied(url, headers):
        raise HTTPError(url, status, "sensitive-response-content fixture-publisher-token", {}, None)

    monkeypatch.setattr(hub_deploy, "_read", denied)
    with pytest.raises(ValueError, match="HTTP " + str(status)) as error:
        hub_deploy._space_jwt("fixture-publisher-token")
    assert "sensitive-response-content" not in str(error.value)
    assert "fixture-publisher-token" not in str(error.value)


def test_space_jwt_transport_failure_does_not_echo_the_request_exception(monkeypatch):
    from urllib.error import URLError

    def unavailable(url, headers):
        raise URLError("sensitive-response-content fixture-publisher-token")

    monkeypatch.setattr(hub_deploy, "_read", unavailable)
    with pytest.raises(ValueError) as error:
        hub_deploy._space_jwt("fixture-publisher-token")
    assert "sensitive-response-content" not in str(error.value)
    assert "fixture-publisher-token" not in str(error.value)


def test_app_jwt_exchange_failure_prevents_all_remote_writes(tmp_path, monkeypatch):
    monkeypatch.setattr(sys, "argv", ["deploy", "publish", "--expected-sha", SHA,
                                     "--bundle", str(tmp_path / "bundle"),
                                     "--receipt", str(tmp_path / "receipt.json")])
    monkeypatch.setenv("HF_OIDC_RESOURCE", hub_deploy.RESOURCE)
    monkeypatch.setattr(hub_deploy, "_gate", lambda sha: {"source_commit": sha})
    monkeypatch.setattr(hub_deploy, "_source", lambda *args: None)
    monkeypatch.setattr(hub_deploy, "verify_bundle", lambda *args: {})
    monkeypatch.setattr(hub_deploy, "verify_privacy", lambda *args: None)
    api = SimpleNamespace(space_info=lambda _: SimpleNamespace(
        id=hub_deploy.SPACE, private=True, protected=False, sdk="static",
        host="https://ctauchmann-hessian-recipes.static.hf.space"))
    monkeypatch.setitem(sys.modules, "huggingface_hub", SimpleNamespace(
        HfApi=lambda **kwargs: api, get_token=lambda: "hf_jwt_fixture"))
    exchanges = []

    def denied(token):
        exchanges.append(token)
        raise ValueError("Private App authentication failed (HTTP 401)")

    monkeypatch.setattr(hub_deploy, "_space_jwt", denied)
    writes = []
    monkeypatch.setattr(hub_deploy, "publish", lambda *args, **kwargs: writes.append(args))
    with pytest.raises(SystemExit, match="HTTP 401"):
        hub_deploy.main()
    assert exchanges == ["hf_jwt_fixture"]
    assert not writes and not (tmp_path / "receipt.json").exists()


@pytest.mark.parametrize("lfs", [False, True])
def test_repository_verification_checks_every_remote_byte_without_app_credentials(tmp_path, monkeypatch, lfs):
    remote = DeliveryFixture(tmp_path / "delivery", lfs=lfs)
    remote.install(monkeypatch)
    result = hub_deploy.verify_repository(remote, remote.folder, {"commit": remote.commit})
    assert result == {"remote_files_verified": 4, "private": True}
    assert not remote.reads and not remote.jwt_calls


@pytest.mark.parametrize("patch", [
    {"private": False}, {"protected": True}, {"visibility": "protected"},
    {"id": "other/hessian-recipes"}, {"sdk": "docker"}, {"sha": "d" * 40},
])
def test_repository_verification_rejects_changed_owner_private_sdk_or_commit(tmp_path, monkeypatch, patch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    for key, value in patch.items():
        setattr(remote.info, key, value)
    with pytest.raises(ValueError):
        hub_deploy.verify_repository(remote, remote.folder, {"commit": remote.commit})
    assert not remote.reads and not remote.jwt_calls


@pytest.mark.parametrize("mutation", ["extra", "missing", "git-blob", "lfs-digest"])
def test_repository_verification_refuses_incomplete_or_modified_remote_inventory(tmp_path, monkeypatch, mutation):
    remote = DeliveryFixture(tmp_path / "delivery", lfs=mutation == "lfs-digest")
    remote.install(monkeypatch)
    if mutation == "extra":
        remote.info.siblings.append(SimpleNamespace(rfilename="private-old-source.md", blob_id="old", lfs=None))
    elif mutation == "missing":
        remote.info.siblings.pop(0)
    elif mutation == "git-blob":
        remote.info.siblings[0].blob_id = "0" * 40
    else:
        remote.info.siblings[0].lfs.sha256 = "0" * 64
    with pytest.raises(ValueError):
        hub_deploy.verify_repository(remote, remote.folder, {"commit": remote.commit})
    assert not remote.reads and not remote.jwt_calls


def repository_publication(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.space = SimpleNamespace(**vars(remote.info), host=remote.host)
    output = tmp_path / "receipt.json"
    monkeypatch.setattr(sys, "argv", ["deploy", "publish", "--expected-sha", SHA,
                                     "--bundle", str(remote.folder), "--receipt", str(output),
                                     "--verification", "repository"])
    monkeypatch.setenv("HF_OIDC_RESOURCE", hub_deploy.RESOURCE)
    gates = []

    def gate(sha):
        gates.append(sha)
        return {"source_commit": sha, "run_id": RUN["id"]}

    monkeypatch.setattr(hub_deploy, "_gate", gate)
    monkeypatch.setattr(hub_deploy, "_source", lambda *args: None)
    monkeypatch.setattr(hub_deploy, "verify_bundle", lambda *args: {})
    monkeypatch.setitem(sys.modules, "huggingface_hub", SimpleNamespace(
        HfApi=lambda **kwargs: remote, get_token=lambda: "hf_jwt_fixture"))

    def forbidden_exchange(token):
        raise AssertionError("Repository mode must not exchange an App JWT")

    monkeypatch.setattr(hub_deploy, "_space_jwt", forbidden_exchange)
    writes = []

    def publish(*args, **kwargs):
        assert args == (remote.folder, hub_deploy.SPACE)
        assert kwargs == {"api": remote, "publisher_token": "hf_jwt_fixture", "mirror": True}
        writes.append((args, kwargs))
        return {"commit": remote.commit}

    monkeypatch.setattr(hub_deploy, "publish", publish)
    return remote, output, writes, gates


def test_explicit_repository_mode_reports_the_unverified_app_truthfully(tmp_path, monkeypatch, capsys):
    remote, output, writes, gates = repository_publication(tmp_path, monkeypatch)
    hub_deploy.main()
    receipt = json.loads(output.read_text())
    assert receipt["commit"] == remote.commit and receipt["source_commit"] == SHA
    assert receipt["remote_files_verified"] == 4 and receipt["private"] is True
    assert receipt["anonymous_access_denied"] is True
    assert receipt["served_manifest_matches"] is None
    assert receipt["verification"] == "repository-and-anonymous-access"
    assert receipt["app_verified_by_ci"] is False
    assert len(writes) == 1 and gates == [SHA, SHA]
    assert len(remote.reads) == 8
    assert not any(read["authenticated"] for read in remote.reads)
    assert all("__sign" not in urlsplit(read["url"]).query for read in remote.reads)
    assert "hf_jwt_fixture" not in output.read_text() + capsys.readouterr().out


@pytest.mark.parametrize("path", ["/", "/assets/site.css", "/sources/docs/validation.md"])
def test_repository_mode_still_rejects_anonymous_leaks_before_writing(tmp_path, monkeypatch, path):
    remote, output, writes, _ = repository_publication(tmp_path, monkeypatch)
    remote.anonymous_leak = path
    with pytest.raises(SystemExit, match="anonymous access"):
        hub_deploy.main()
    assert not writes and not output.exists()


@pytest.mark.parametrize("patch", [
    {"private": False}, {"protected": True}, {"id": "other/hessian-recipes"}, {"sdk": "docker"},
])
def test_repository_mode_preserves_the_private_existing_target_precondition(tmp_path, monkeypatch, patch):
    remote, output, writes, _ = repository_publication(tmp_path, monkeypatch)
    for key, value in patch.items():
        setattr(remote.space, key, value)
    with pytest.raises(SystemExit):
        hub_deploy.main()
    assert not writes and not output.exists()


def test_repository_mode_cannot_bypass_clean_bundle_verification(tmp_path, monkeypatch):
    _, output, writes, _ = repository_publication(tmp_path, monkeypatch)

    def reject(*args):
        raise ValueError("Deployment bundle differs from clean source")

    monkeypatch.setattr(hub_deploy, "verify_bundle", reject)
    with pytest.raises(SystemExit, match="clean source"):
        hub_deploy.main()
    assert not writes and not output.exists()


def test_repository_mode_rechecks_trusted_main_immediately_before_write(tmp_path, monkeypatch):
    _, output, writes, _ = repository_publication(tmp_path, monkeypatch)
    calls = []

    def gate(sha):
        calls.append(sha)
        if len(calls) == 2:
            raise ValueError("Commit superseded on main")
        return {"source_commit": sha}

    monkeypatch.setattr(hub_deploy, "_gate", gate)
    with pytest.raises(SystemExit, match="superseded"):
        hub_deploy.main()
    assert calls == [SHA, SHA] and not writes and not output.exists()


def test_repository_mode_checks_uploaded_hashes_before_issuing_success_receipt(tmp_path, monkeypatch):
    remote, output, writes, _ = repository_publication(tmp_path, monkeypatch)
    original = hub_deploy.publish

    def corrupt_after_upload(*args, **kwargs):
        receipt = original(*args, **kwargs)
        remote.info.siblings[0].blob_id = "0" * 40
        return receipt

    monkeypatch.setattr(hub_deploy, "publish", corrupt_after_upload)
    with pytest.raises(SystemExit, match="differs"):
        hub_deploy.main()
    assert len(writes) == 1 and not output.exists()


def test_repository_mode_rechecks_anonymous_access_after_upload(tmp_path, monkeypatch):
    remote, output, writes, _ = repository_publication(tmp_path, monkeypatch)
    original = hub_deploy.publish

    def leak_after_upload(*args, **kwargs):
        receipt = original(*args, **kwargs)
        remote.anonymous_leak = "/assets/site.css"
        return receipt

    monkeypatch.setattr(hub_deploy, "publish", leak_after_upload)
    with pytest.raises(SystemExit, match="anonymous access"):
        hub_deploy.main()
    assert len(writes) == 1 and not output.exists()


def app_verification(tmp_path, monkeypatch):
    remote = DeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.space = SimpleNamespace(**vars(remote.info), host=remote.host)
    output = tmp_path / "app-receipt.json"
    monkeypatch.setattr(sys, "argv", ["deploy", "verify-app", "--expected-sha", SHA,
                                     "--bundle", str(remote.folder), "--receipt", str(output)])
    monkeypatch.delenv("GITHUB_EVENT_PATH", raising=False)
    monkeypatch.delenv("GITHUB_EVENT_NAME", raising=False)
    monkeypatch.delenv("HF_OIDC_RESOURCE", raising=False)
    monkeypatch.setattr(hub_deploy, "_source", lambda *args: None)
    monkeypatch.setattr(hub_deploy, "verify_bundle", lambda *args: {})
    monkeypatch.setitem(sys.modules, "huggingface_hub", SimpleNamespace(
        HfApi=lambda **kwargs: remote, get_token=lambda: "fixture-publisher-token"))

    def forbidden(*args, **kwargs):
        raise AssertionError("Read-only App verification cannot publish or require GitHub events")

    monkeypatch.setattr(hub_deploy, "_gate", forbidden)
    monkeypatch.setattr(hub_deploy, "publish", forbidden)
    return remote, output


def test_verify_app_is_read_only_and_independent_of_github_events(tmp_path, monkeypatch):
    remote, output = app_verification(tmp_path, monkeypatch)
    hub_deploy.main()
    receipt = json.loads(output.read_text())
    assert receipt["commit"] == remote.commit and receipt["source_commit"] == SHA
    assert receipt["verification"] == "authenticated-app"
    assert receipt["served_manifest_matches"] is True
    assert receipt["anonymous_access_denied"] is True
    assert receipt["remote_files_verified"] == 4
    assert remote.jwt_calls == ["fixture-publisher-token"]


def test_verify_app_rejects_changed_remote_content_before_minting_an_app_token(tmp_path, monkeypatch):
    remote, output = app_verification(tmp_path, monkeypatch)
    remote.info.siblings[0].blob_id = "0" * 40
    with pytest.raises(SystemExit, match="differs"):
        hub_deploy.main()
    assert not output.exists() and not remote.jwt_calls


def test_verify_app_still_requires_the_matching_clean_local_source(tmp_path, monkeypatch):
    remote, output = app_verification(tmp_path, monkeypatch)

    def reject(*args):
        raise ValueError("Checkout does not match expected commit")

    monkeypatch.setattr(hub_deploy, "_source", reject)
    with pytest.raises(SystemExit, match="expected commit"):
        hub_deploy.main()
    assert not output.exists() and not remote.jwt_calls


class ProtectedDeliveryFixture(DeliveryFixture):
    """Public App and hidden Hub endpoints; no real tokens or network calls."""

    def __init__(self, folder, *, lfs=False):
        super().__init__(folder, lfs=lfs)
        self.info.protected = True
        self.info.visibility = "protected"
        self.hub_status = 401
        self.public_overrides = {}
        index = (b'<!doctype html><html><head><title>Post-training and evaluation</title>'
                 b'</head><body class="home-page"><a href="hub-manifest.json">Provenance</a>'
                 b'</body></html>')
        (folder / "index.html").write_bytes(index)
        for entry in self.info.siblings:
            if entry.rfilename == "index.html":
                entry.blob_id = hashlib.sha1(b"blob " + str(len(index)).encode() + b"\0" + index).hexdigest()
                if entry.lfs:
                    entry.lfs.sha256 = hashlib.sha256(index).hexdigest()

    def install(self, monkeypatch):
        super().install(monkeypatch)
        monkeypatch.setattr(hub_deploy, "_read_protected_root", lambda url: self.read(url, {}))

    def read(self, url, headers):
        from urllib.error import HTTPError

        assert headers == {}, "Protected probes must not forward credentials or App capabilities"
        assert "__sign" not in urlsplit(url).query
        self.reads.append({"url": url, "authenticated": False})
        parsed = urlsplit(url)
        if parsed.netloc == "huggingface.co":
            assert parsed.path.startswith("/spaces/" + hub_deploy.SPACE + "/")
            if self.hub_status == 200:
                return b"unexpected public repository source"
            raise HTTPError(url, self.hub_status, "fixture denied", {}, None)
        assert parsed.netloc == urlsplit(self.host).netloc
        if parsed.path in self.public_overrides:
            value = self.public_overrides[parsed.path]
            if isinstance(value, int):
                raise HTTPError(url, value, "fixture unavailable", {}, None)
            return value
        if parsed.path == "/hub-manifest.json" and self.served_manifests:
            return self.served_manifests.pop(0)
        filename = "index.html" if parsed.path == "/" else parsed.path.lstrip("/")
        raw = (self.folder / filename).read_bytes()
        if filename == "index.html":
            # The real HF Static ingress adds this script to the HTML head.
            raw = raw.replace(b"<head>", b"<head><script>window.huggingface={variables:{}};</script>")
        return raw


@pytest.mark.parametrize("lfs", [False, True])
def test_protected_delivery_checks_public_app_and_hidden_repo_without_app_jwt(tmp_path, monkeypatch, lfs):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery", lfs=lfs)
    remote.install(monkeypatch)
    result = remote.verify(attempts=1, visibility="protected")
    assert result == {
        "remote_files_verified": 4, "private": True, "visibility": "protected", "protected": True,
        "served_manifest_matches": True, "anonymous_app_access": True,
        "anonymous_repository_access_denied": True,
    }
    assert not remote.jwt_calls
    assert not any(read["authenticated"] for read in remote.reads)
    assert {urlsplit(read["url"]).path for read in remote.reads if "huggingface.co/" in read["url"]} == {
        "/spaces/ctauchmann/hessian-recipes/raw/main/README.md",
        "/spaces/ctauchmann/hessian-recipes/resolve/main/README.md",
    }


def test_protected_metadata_may_be_absent_only_with_mandatory_effective_access_checks(tmp_path, monkeypatch):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery")
    del remote.info.protected
    del remote.info.visibility
    remote.install(monkeypatch)
    assert remote.verify(attempts=1, visibility="protected")["anonymous_app_access"] is True
    remote.public_overrides["/"] = 401
    with pytest.raises(ValueError, match="unavailable"):
        remote.verify(attempts=1, visibility="protected")


@pytest.mark.parametrize("patch", [
    {"private": False}, {"protected": False}, {"visibility": "private"},
    {"visibility": "public"}, {"id": "other/hessian-recipes"}, {"sdk": "docker"},
])
def test_protected_mode_refuses_contradictory_or_public_repository_metadata(tmp_path, monkeypatch, patch):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    for key, value in patch.items():
        setattr(remote.info, key, value)
    with pytest.raises(ValueError):
        remote.verify(attempts=1, visibility="protected")
    assert not remote.reads and not remote.jwt_calls


@pytest.mark.parametrize("path,raw", [
    ("/", b"<!doctype html><h1>Sign in to Hugging Face</h1>"),
    ("/index.html", b"<!doctype html><h1>Sign in to Hugging Face</h1>"),
    ("/assets/site.css", b"<!doctype html><h1>Login</h1>"),
    ("/sources/docs/validation.md", b"unrelated Markdown source"),
    ("/hub-manifest.json", b'{"source_commit":"different"}'),
])
def test_protected_preflight_refuses_login_pages_or_changed_content(tmp_path, monkeypatch, path, raw):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.public_overrides[path] = raw
    with pytest.raises(ValueError):
        hub_deploy.verify_protected(remote, remote.host)


@pytest.mark.parametrize("status", [200, 500])
def test_protected_mode_requires_actual_anonymous_hub_repository_denial(tmp_path, monkeypatch, status):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.hub_status = status
    with pytest.raises(ValueError):
        remote.verify(attempts=1, visibility="protected")


def test_protected_delivery_waits_for_new_manifest_without_exchanging_private_app_token(tmp_path, monkeypatch):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.served_manifests = [b"old manifest", (remote.folder / "hub-manifest.json").read_bytes()]
    assert remote.verify(attempts=2, visibility="protected")["served_manifest_matches"] is True
    assert remote.sleep_calls == [2] and not remote.jwt_calls


def test_protected_bundle_requires_explicit_matching_rebuild_visibility(repository, tmp_path):
    output = tmp_path / "protected-build"
    build(repository, output, visibility="protected")
    assert verify_bundle(repository, output, git_sha(repository), "protected")["intended_visibility"] == "protected"
    with pytest.raises(ValueError, match="differs"):
        verify_bundle(repository, output, git_sha(repository))


def protected_publication(tmp_path, monkeypatch):
    remote = ProtectedDeliveryFixture(tmp_path / "delivery")
    remote.install(monkeypatch)
    remote.space = SimpleNamespace(**vars(remote.info), host=remote.host)
    output = tmp_path / "receipt.json"
    monkeypatch.setattr(sys, "argv", ["deploy", "publish", "--expected-sha", SHA,
                                     "--bundle", str(remote.folder), "--receipt", str(output),
                                     "--verification", "repository", "--visibility", "protected"])
    monkeypatch.setenv("HF_OIDC_RESOURCE", hub_deploy.RESOURCE)
    gates = []
    monkeypatch.setattr(hub_deploy, "_gate", lambda sha: gates.append(sha) or {"source_commit": sha})
    monkeypatch.setattr(hub_deploy, "_source", lambda *args: None)
    monkeypatch.setattr(hub_deploy, "verify_bundle", lambda *args: {})
    monkeypatch.setitem(sys.modules, "huggingface_hub", SimpleNamespace(
        HfApi=lambda **kwargs: remote, get_token=lambda: "hf_jwt_fixture"))
    writes = []

    def publish(*args, **kwargs):
        assert kwargs == {"api": remote, "publisher_token": "hf_jwt_fixture", "mirror": True,
                          "visibility": "protected"}
        writes.append((args, kwargs))
        return {"commit": remote.commit}

    monkeypatch.setattr(hub_deploy, "publish", publish)
    return remote, output, writes, gates


def test_protected_publication_verifies_the_app_even_in_oidc_repository_mode(tmp_path, monkeypatch):
    remote, output, writes, gates = protected_publication(tmp_path, monkeypatch)
    hub_deploy.main()
    receipt = json.loads(output.read_text())
    assert receipt["served_manifest_matches"] is True
    assert receipt["app_verified_by_ci"] is True
    assert receipt["verification"] == "repository-and-anonymous-protected-app"
    assert receipt["anonymous_repository_access_denied"] is True
    assert len(writes) == 1 and gates == [SHA, SHA] and not remote.jwt_calls


def test_protected_publication_checks_existing_public_content_before_any_write(tmp_path, monkeypatch):
    remote, output, writes, _ = protected_publication(tmp_path, monkeypatch)
    remote.public_overrides["/assets/site.css"] = b"login response"
    with pytest.raises(SystemExit, match="differs"):
        hub_deploy.main()
    assert not writes and not output.exists()


def test_protected_publication_rechecks_hidden_hub_access_after_upload(tmp_path, monkeypatch):
    remote, output, writes, _ = protected_publication(tmp_path, monkeypatch)
    original = hub_deploy.publish

    def expose_after_upload(*args, **kwargs):
        receipt = original(*args, **kwargs)
        remote.hub_status = 200
        return receipt

    monkeypatch.setattr(hub_deploy, "publish", expose_after_upload)
    with pytest.raises(SystemExit, match="anonymous access"):
        hub_deploy.main()
    assert len(writes) == 1 and not output.exists()


@pytest.fixture
def static_redirect_server(monkeypatch):
    """Exercise urllib redirects over loopback, with no HF account or network."""
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from threading import Thread

    state = {"requests": [], "code": 302, "location": "/index.html?protected-check=123",
             "index_redirect": False, "body": b"<!doctype html><title>Fixture</title>"}

    class Handler(BaseHTTPRequestHandler):
        def do_GET(self):
            state["requests"].append((self.path, dict(self.headers)))
            if urlsplit(self.path).path == "/":
                self.send_response(state["code"])
                self.send_header("Location", state["location"])
                self.end_headers()
            elif state["index_redirect"]:
                self.send_response(302)
                self.send_header("Location", "/index.html?protected-check=123")
                self.end_headers()
            else:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(state["body"])

        def log_message(self, format, *args):
            pass

    server = HTTPServer(("127.0.0.1", 0), Handler)
    origin = "http://127.0.0.1:" + str(server.server_port)
    # Production pins the exact HTTPS Static origin. Only this test substitutes
    # loopback so the real urllib handler can run without contacting HF.
    monkeypatch.setattr(hub_deploy, "_STATIC_APP_ORIGIN", origin)
    thread = Thread(target=server.serve_forever, daemon=True)
    thread.start()
    try:
        yield origin + "/?protected-check=123", state
    finally:
        server.shutdown()
        server.server_close()
        thread.join(timeout=2)


def test_static_root_follows_only_the_same_query_canonical_index_once(static_redirect_server):
    url, state = static_redirect_server
    assert hub_deploy._read_protected_root(url) == state["body"]
    assert [path for path, _ in state["requests"]] == [
        "/?protected-check=123", "/index.html?protected-check=123",
    ]
    assert not any(name.lower() in {"authorization", "proxy-authorization", "cookie"}
                   for _, headers in state["requests"] for name in headers)


@pytest.mark.parametrize("location", [
    "/login?protected-check=123", "/index.html?protected-check=999", "/index.html",
    "/index.html?protected-check=123&extra=1", "/index.html?protected-check=123#fragment",
    "https://unapproved.example/index.html?protected-check=123",
])
def test_static_root_refuses_other_paths_queries_or_origins(static_redirect_server, location):
    url, state = static_redirect_server
    state["location"] = location
    with pytest.raises(ValueError, match="Unexpected redirect"):
        hub_deploy._read_protected_root(url)
    assert len(state["requests"]) == 1


@pytest.mark.parametrize("code", [301, 303, 307, 308])
def test_static_root_refuses_any_other_redirect_status(static_redirect_server, code):
    url, state = static_redirect_server
    state["code"] = code
    with pytest.raises(ValueError, match="Unexpected redirect"):
        hub_deploy._read_protected_root(url)
    assert len(state["requests"]) == 1


def test_static_root_refuses_a_second_hop(static_redirect_server):
    url, state = static_redirect_server
    state["index_redirect"] = True
    with pytest.raises(ValueError, match="Unexpected redirect"):
        hub_deploy._read_protected_root(url)
    assert len(state["requests"]) == 2


def test_general_transport_keeps_rejecting_even_the_canonical_root_redirect(static_redirect_server):
    url, state = static_redirect_server
    with pytest.raises(ValueError, match="Unexpected redirect"):
        hub_deploy._read(url, {})
    assert len(state["requests"]) == 1


@pytest.mark.parametrize("header", ["Authorization", "Proxy-Authorization", "Cookie"])
def test_static_root_redirect_handler_refuses_credential_headers(header):
    from urllib.request import Request

    source = "https://ctauchmann-hessian-recipes.static.hf.space/?protected-check=123"
    target = "https://ctauchmann-hessian-recipes.static.hf.space/index.html?protected-check=123"
    handler = hub_deploy._StaticRootRedirect(source)
    request = Request(source, headers={header: "fixture-credential"})
    with pytest.raises(ValueError, match="Unexpected redirect"):
        handler.redirect_request(request, None, 302, "Found", {}, target)


def test_static_root_keeps_the_existing_response_size_bound(static_redirect_server):
    url, state = static_redirect_server
    state["body"] = b"x" * 2_000_001
    with pytest.raises(ValueError, match="size bound"):
        hub_deploy._read_protected_root(url)
