"""Deterministic in-memory teaching fixture, not an MCP security implementation.
Run: python3 revocation-commit-race.py
No network calls. Events control ordering; sleeps do not decide test outcomes.
"""
import threading
import unittest
from concurrent.futures import ThreadPoolExecutor


class Resource:
    def __init__(self):
        self.lock = threading.Lock()
        self.epoch = 1
        self.active = True
        self.effects = []

    def permitted(self, epoch):
        with self.lock:
            return self.active and epoch == self.epoch

    def revoke(self):
        with self.lock:
            self.active = False
            self.epoch += 1

    def commit(self, epoch, fenced):
        with self.lock:
            # Both revocation and the local effect share this ordering point.
            if fenced and (not self.active or epoch != self.epoch):
                return "denied_at_commit"
            self.effects.append("reservation")
            return "committed"


def worker(resource, checked, release, fenced):
    allowed = resource.permitted(1)
    checked.set()
    if not allowed:
        return "denied_at_start"
    if not release.wait(timeout=5):
        raise TimeoutError("test failed to release worker")
    return resource.commit(1, fenced)


class RevocationRaceTests(unittest.TestCase):
    def run_case(self, timing, fenced):
        resource = Resource()
        checked, release = threading.Event(), threading.Event()
        if timing == "before_start":
            resource.revoke()
        with ThreadPoolExecutor(max_workers=1) as pool:
            pending = pool.submit(worker, resource, checked, release, fenced)
            try:
                self.assertTrue(checked.wait(timeout=5), "worker did not check")
                if timing == "between_check_and_commit":
                    resource.revoke()
            finally:
                release.set()
            result = pending.result(timeout=5)
        if timing == "after_commit":
            resource.revoke()
        return result, resource.effects

    def test_revoked_before_start_is_denied(self):
        self.assertEqual(self.run_case("before_start", True),
                         ("denied_at_start", []))

    def test_startup_only_check_allows_stale_effect(self):
        # Positive reproduction of the bug, not a safe outcome.
        self.assertEqual(self.run_case("between_check_and_commit", False),
                         ("committed", ["reservation"]))

    def test_effect_boundary_check_blocks_stale_effect(self):
        self.assertEqual(self.run_case("between_check_and_commit", True),
                         ("denied_at_commit", []))

    def test_revoke_after_commit_does_not_undo_effect(self):
        self.assertEqual(self.run_case("after_commit", True),
                         ("committed", ["reservation"]))

    def test_active_authority_can_commit(self):
        self.assertEqual(self.run_case("no_revocation", True),
                         ("committed", ["reservation"]))


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