from __future__ import annotations import time import pytest from yuxi.channels.adapters.twitch.pairing import ( PairingStore, check_pairing_policy, format_pairing_notification, strip_pairing_prefix, ) class TestStripPairingPrefix: def test_strips_twitch_prefix(self): assert strip_pairing_prefix("twitch:user123") == "user123" def test_strips_user_prefix(self): assert strip_pairing_prefix("user:user123") == "user123" def test_preserves_without_prefix(self): assert strip_pairing_prefix("user123") == "user123" def test_strips_case_insensitive(self): assert strip_pairing_prefix("TWITCH:user123") == "user123" assert strip_pairing_prefix("User:user123") == "user123" def test_empty_string(self): assert strip_pairing_prefix("") == "" class TestPairingStore: def test_initial_state(self): store = PairingStore() assert len(store) == 0 assert store.pending_count == 0 def test_is_approved_false_initially(self): store = PairingStore() assert store.is_approved("user1") is False def test_add_pending_and_approve(self): store = PairingStore() store.add_pending("user1", "UserOne", "#chan") assert store.pending_count == 1 result = store.approve("user1") assert result is True assert store.is_approved("user1") is True assert store.pending_count == 0 def test_approve_triggers_callback(self): callback_data = {"called": False} def cb(user_name, channel): callback_data["called"] = True callback_data["user_name"] = user_name callback_data["channel"] = channel store = PairingStore() store.set_on_approve(cb, "#main_channel") store.add_pending("user1", "UserOne", "#chan") store.approve("user1", "UserOne") assert callback_data["called"] is True assert callback_data["user_name"] == "UserOne" assert callback_data["channel"] == "#main_channel" def test_approve_callback_exception_suppressed(self): def cb(user_name, channel): raise RuntimeError("test error") store = PairingStore() store.set_on_approve(cb) store.add_pending("user1", "UserOne", "#chan") result = store.approve("user1") assert result is True def test_approve_without_pending(self): store = PairingStore() result = store.approve("user1") assert result is True assert store.is_approved("user1") is True def test_reject_removes_pending(self): store = PairingStore() store.add_pending("user1", "UserOne", "#chan") result = store.reject("user1") assert result is True assert store.pending_count == 0 def test_reject_nonexistent(self): store = PairingStore() result = store.reject("nonexistent") assert result is False def test_remove_approval(self): store = PairingStore() store.approve("user1") assert store.is_approved("user1") is True result = store.remove_approval("user1") assert result is True assert store.is_approved("user1") is False def test_remove_approval_nonexistent(self): store = PairingStore() result = store.remove_approval("nonexistent") assert result is False def test_get_approved_users(self): store = PairingStore() store.approve("user1") store.approve("user2") approved = store.get_approved_users() assert "user1" in approved assert "user2" in approved def test_get_pending_users(self): store = PairingStore() store.add_pending("user1", "U1", "#ch") store.add_pending("user2", "U2", "#ch") pending = store.get_pending_users() assert "user1" in pending assert "user2" in pending def test_is_pending(self): store = PairingStore() store.add_pending("user1", "U1", "#ch") assert store.is_pending("user1") is True assert store.is_pending("user2") is False def test_approved_expiry(self): store = PairingStore() store.APPROVED_TTL = 0.1 store.approve("user1") assert store.is_approved("user1") is True time.sleep(0.15) assert store.is_approved("user1") is False def test_length_after_expiry(self): store = PairingStore() store.APPROVED_TTL = 0.05 store.approve("user1") assert len(store) == 1 time.sleep(0.1) _ = store.is_approved("user1") assert len(store) == 0 def test_normalize_with_prefix(self): store = PairingStore() store.approve("twitch:user1") assert store.is_approved("user1") is True assert store.is_approved("twitch:user1") is True def test_max_approved_eviction(self): store = PairingStore() store.MAX_APPROVED = 3 for i in range(5): store.approve(f"user{i}") assert len(store) <= 3 def test_pending_expiry(self): store = PairingStore() store.PENDING_TTL = 0.05 store.add_pending("user1", "U1", "#ch") assert store.pending_count == 1 time.sleep(0.1) store.get_pending_users() assert store.pending_count == 0 def test_approved_move_to_end(self): store = PairingStore() store.MAX_APPROVED = 2 store.approve("user1") time.sleep(0.01) store.approve("user2") time.sleep(0.01) assert store.is_approved("user1") is True store.approve("user3") approved = store.get_approved_users() assert "user1" in approved assert "user2" not in approved class TestCheckPairingPolicy: def test_approved_user(self): store = PairingStore() store.approve("user1") assert check_pairing_policy(store, "user1") is True def test_unapproved_user(self): store = PairingStore() assert check_pairing_policy(store, "user1") is False class TestFormatPairingNotification: def test_formats_message(self): msg = format_pairing_notification("TestUser", "ABC123") assert "TestUser" in msg assert "ABC123" in msg assert "!approve" in msg