#!/usr/bin/env python3
"""
Tests for the persistence-context gate on auto-bump (round-9 lock-in).

``mmv_floor_check.maybe_auto_bump`` only fires when the run can also
commit the bumped value back to the default branch. The action's
commit-back step is gated on ``event=push`` AND
``ref=refs/heads/<default>``; auto-bumping in any other context
(workflow_dispatch / pull_request / push to a feature branch) would
upload an IPA at the bumped version while git stays at the old
value, causing the next run to recompute against stale state.

These tests pin the persistence-context envs explicitly to prove the
gate's exact triggers / refusals. They are intentionally separate
from ``test_manage_marketing_version_autoroll.py`` (which exercises
the auto-roll DECISION matrix at terminal vs in-review) so each test
file stays under the 10-functions-per-file cap.
"""

from __future__ import annotations

import io
import os
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock

sys.path.insert(0, str(Path(__file__).resolve().parent))

import mmv_floor_check  # noqa: E402


class PersistenceContextGateTests(unittest.TestCase):
    """``maybe_auto_bump`` must refuse when the run cannot commit back."""

    def _run(self, env_overrides: dict) -> tuple[object, str]:
        """Drive ``maybe_auto_bump`` with the given env overrides; mocks
        ``write_marketing_version`` so the only thing under test is the
        policy + persistence-context gating. Returns (result, stderr)."""
        env = {"MARKETING_VERSION_AUTO_BUMP": "patch"}
        env.update(env_overrides)
        buf = io.StringIO()
        with mock.patch.dict(os.environ, env, clear=False), \
                mock.patch(
                    "version_utils.write_marketing_version",
                    return_value=True), \
                redirect_stderr(buf):
            result = mmv_floor_check.maybe_auto_bump("1.0.5", "1.0.5")
        return result, buf.getvalue()

    def test_push_to_default_branch_fires_autobump(self):
        result, _ = self._run({
            "GITHUB_EVENT_NAME": "push",
            "GITHUB_REF": "refs/heads/main",
            "GITHUB_DEFAULT_BRANCH": "main",
        })
        self.assertEqual(result, "1.0.6")

    def test_workflow_dispatch_refuses_autobump(self):
        result, stderr = self._run({
            "GITHUB_EVENT_NAME": "workflow_dispatch",
            "GITHUB_REF": "refs/heads/main",
            "GITHUB_DEFAULT_BRANCH": "main",
        })
        self.assertIsNone(result)
        self.assertIn("cannot persist the bump", stderr)
        self.assertIn("workflow_dispatch", stderr)

    def test_push_to_feature_branch_refuses_autobump(self):
        result, stderr = self._run({
            "GITHUB_EVENT_NAME": "push",
            "GITHUB_REF": "refs/heads/feature-x",
            "GITHUB_DEFAULT_BRANCH": "main",
        })
        self.assertIsNone(result)
        self.assertIn("cannot persist the bump", stderr)
        self.assertIn("feature-x", stderr)

    def test_pull_request_refuses_autobump(self):
        result, stderr = self._run({
            "GITHUB_EVENT_NAME": "pull_request",
            "GITHUB_REF": "refs/pull/42/merge",
            "GITHUB_DEFAULT_BRANCH": "main",
        })
        self.assertIsNone(result)
        self.assertIn("cannot persist the bump", stderr)

    def test_missing_default_branch_env_refuses_autobump(self):
        """Defensive: empty default-branch env -> refuse."""
        result, stderr = self._run({
            "GITHUB_EVENT_NAME": "push",
            "GITHUB_REF": "refs/heads/main",
            "GITHUB_DEFAULT_BRANCH": "",
        })
        self.assertIsNone(result)
        self.assertIn("cannot persist the bump", stderr)

    def test_alternative_default_branch_master_works(self):
        """Sanity: a repo whose default branch is `master` still bumps."""
        result, _ = self._run({
            "GITHUB_EVENT_NAME": "push",
            "GITHUB_REF": "refs/heads/master",
            "GITHUB_DEFAULT_BRANCH": "master",
        })
        self.assertEqual(result, "1.0.6")

    def test_policy_none_short_circuits_before_persist_check(self):
        """Order-of-checks lock-in: policy=none returns None silently
        without surfacing the persistence-context warning."""
        env = {
            "MARKETING_VERSION_AUTO_BUMP": "none",
            "GITHUB_EVENT_NAME": "workflow_dispatch",
            "GITHUB_REF": "refs/heads/main",
            "GITHUB_DEFAULT_BRANCH": "main",
        }
        buf = io.StringIO()
        with mock.patch.dict(os.environ, env, clear=False), \
                mock.patch(
                    "version_utils.write_marketing_version",
                    return_value=True), \
                redirect_stderr(buf):
            result = mmv_floor_check.maybe_auto_bump("1.0.5", "1.0.5")
        self.assertIsNone(result)
        # Persistence-context warning must NOT fire when policy=none.
        self.assertNotIn("cannot persist the bump", buf.getvalue())


if __name__ == "__main__":
    unittest.main()
