200 lines
6.2 KiB
Python
200 lines
6.2 KiB
Python
|
|
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
|