"""Namespace policy tests (SPEC §7.2).

We don't just want the happy path — the namespace is a safety
boundary, so we exercise tag refs, remote-tracking refs, and
lookalike prefixes that would slip past a naive `startswith`.
"""

import pytest

from attractor.checkpoint import (
    ATTRACTOR_REF_PREFIX,
    NamespaceError,
    ensure_attractor_ref,
)


# [unit->REQ-STORE-REF-NAMESPACE]
class TestEnsureAttractorRef:
    """`ensure_attractor_ref` must let valid refs pass and reject everything else."""

    def test_valid_state_ref_passes(self) -> None:
        ensure_attractor_ref("refs/heads/attractor/run/abc/state")

    def test_valid_worktree_ref_passes(self) -> None:
        ensure_attractor_ref("refs/heads/attractor/run/abc/worktree")

    def test_valid_agent_ref_passes(self) -> None:
        ensure_attractor_ref("refs/heads/attractor/run/abc/agent/plan")

    def test_valid_meta_ref_passes(self) -> None:
        ensure_attractor_ref("refs/heads/attractor/meta/config")

    def test_empty_ref_rejected(self) -> None:
        with pytest.raises(NamespaceError, match="empty"):
            ensure_attractor_ref("")

    def test_user_branch_rejected(self) -> None:
        # The bug we're guarding against: an engine handler reaches
        # for the active branch and writes a checkpoint to it.
        with pytest.raises(NamespaceError, match="does not live under"):
            ensure_attractor_ref("refs/heads/main")

    def test_tag_ref_rejected(self) -> None:
        # A tag would render in `git tag -l` and could be mistaken for
        # a release marker — even if its name happens to look like our
        # prefix.
        with pytest.raises(NamespaceError, match="tag"):
            ensure_attractor_ref("refs/tags/attractor/run/x")

    def test_remote_tracking_ref_rejected(self) -> None:
        # Refs under refs/remotes/ could become push targets by
        # accident; the checkpoint module must never go near them.
        with pytest.raises(NamespaceError, match="remote"):
            ensure_attractor_ref("refs/remotes/origin/attractor/run/x")

    def test_lookalike_prefix_rejected(self) -> None:
        # User has a branch literally named "attractor" — without the
        # trailing slash check this would pass.
        with pytest.raises(NamespaceError, match="does not live under"):
            ensure_attractor_ref("refs/heads/attractor")

    def test_namespace_only_rejected(self) -> None:
        # The prefix itself with nothing after is not a usable ref.
        with pytest.raises(NamespaceError, match="suffix"):
            ensure_attractor_ref(ATTRACTOR_REF_PREFIX)

    def test_empty_path_segments_rejected(self) -> None:
        with pytest.raises(NamespaceError, match="empty path segments"):
            ensure_attractor_ref("refs/heads/attractor/run//state")

    def test_trailing_slash_rejected(self) -> None:
        with pytest.raises(NamespaceError, match="trailing"):
            ensure_attractor_ref("refs/heads/attractor/run/abc/")

    def test_prefix_constant_matches(self) -> None:
        # If §7.2 ever changes the namespace, this test fails loudly.
        assert ATTRACTOR_REF_PREFIX == "refs/heads/attractor/"
