from __future__ import annotations import time import pytest from yuxi.channels.adapters.zalo_user.constants import ZaloCredentialStage from yuxi.channels.adapters.zalo_user.credentials import CredentialManager, _compute_signature class TestCredentialStageTransitions: def test_initial_uninitialized(self): mgr = CredentialManager() assert mgr.stage == ZaloCredentialStage.UNINITIALIZED assert mgr.is_authenticated is False def test_mark_qr_pending(self): mgr = CredentialManager() mgr.mark_qr_pending("qr_test_123") assert mgr.stage == ZaloCredentialStage.QR_PENDING def test_mark_qr_scanned(self): mgr = CredentialManager() mgr.mark_qr_pending("qr_1") mgr.mark_qr_scanned() assert mgr.stage == ZaloCredentialStage.QR_SCANNED def test_mark_qr_declined(self): mgr = CredentialManager() mgr.mark_qr_declined() assert mgr.stage == ZaloCredentialStage.QR_DECLINED assert mgr.error_message is not None assert "declined" in mgr.error_message.lower() def test_mark_qr_expired(self): mgr = CredentialManager() mgr.mark_qr_expired() assert mgr.stage == ZaloCredentialStage.QR_EXPIRED assert mgr.error_message is not None assert "expired" in mgr.error_message.lower() def test_mark_connected_no_data(self): mgr = CredentialManager() mgr.mark_connected() assert mgr.stage == ZaloCredentialStage.CONNECTED assert mgr.is_authenticated is True def test_mark_connected_with_data(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123", "display_name": "Test"}) assert mgr.stage == ZaloCredentialStage.CONNECTED assert mgr.credential_signature is not None def test_mark_logged_out(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) mgr.mark_logged_out() assert mgr.stage == ZaloCredentialStage.LOGGED_OUT assert mgr.is_authenticated is False assert mgr.credential_signature is None def test_mark_error(self): mgr = CredentialManager() mgr.mark_error("something went wrong") assert mgr.stage == ZaloCredentialStage.ERROR assert mgr.error_message == "something went wrong" def test_full_lifecycle(self): mgr = CredentialManager() mgr.mark_qr_pending("qr_001") assert mgr.stage == ZaloCredentialStage.QR_PENDING mgr.mark_qr_scanned() assert mgr.stage == ZaloCredentialStage.QR_SCANNED mgr.mark_connected({"user_id": "u1"}) assert mgr.stage == ZaloCredentialStage.CONNECTED mgr.mark_logged_out() assert mgr.stage == ZaloCredentialStage.LOGGED_OUT class TestCredentialSignature: def test_no_signature_initially(self): mgr = CredentialManager() assert mgr.credential_signature is None def test_signature_after_connect(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) assert mgr.credential_signature is not None def test_signature_cleared_on_logout(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) mgr.mark_logged_out() assert mgr.credential_signature is None def test_same_data_same_signature(self): sig1 = _compute_signature({"user_id": "123", "name": "A"}) sig2 = _compute_signature({"name": "A", "user_id": "123"}) assert sig1 == sig2 def test_different_data_different_signature(self): sig1 = _compute_signature({"user_id": "123"}) sig2 = _compute_signature({"user_id": "456"}) assert sig1 != sig2 def test_has_credential_changed_no_signature(self): mgr = CredentialManager() assert mgr.has_credential_changed({"user_id": "123"}) is True def test_has_credential_changed_same_data(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) assert mgr.has_credential_changed({"user_id": "123"}) is False def test_has_credential_changed_different_data(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) assert mgr.has_credential_changed({"user_id": "456"}) is True def test_update_credentials_same_data(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) assert mgr.update_credentials({"user_id": "123"}) is False def test_update_credentials_different_data(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) old_sig = mgr.credential_signature changed = mgr.update_credentials({"user_id": "456"}) assert changed is True assert mgr.credential_signature != old_sig class TestSessionStatus: @pytest.mark.asyncio async def test_session_alive(self): class FakeBridge: async def check_login_status(self): return {"logged_in": True} mgr = CredentialManager() result = await mgr.check_session_status(FakeBridge()) assert result is True assert mgr.stage == ZaloCredentialStage.CONNECTED @pytest.mark.asyncio async def test_session_dead(self): class FakeBridge: async def check_login_status(self): return {"logged_in": False} mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) result = await mgr.check_session_status(FakeBridge()) assert result is False assert mgr.stage == ZaloCredentialStage.LOGGED_OUT @pytest.mark.asyncio async def test_session_check_error(self): class FakeBridge: async def check_login_status(self): raise RuntimeError("network error") mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) result = await mgr.check_session_status(FakeBridge()) assert result is True class TestLogout: @pytest.mark.asyncio async def test_logout_success(self): class FakeBridge: async def post(self, path, json=None, **kwargs): class FakeResp: @staticmethod def json(): return {"success": True} return FakeResp() mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) result = await mgr.logout(FakeBridge()) assert result is True assert mgr.stage == ZaloCredentialStage.LOGGED_OUT @pytest.mark.asyncio async def test_logout_error_graceful(self): class FakeBridge: async def post(self, path, json=None, **kwargs): raise RuntimeError("network down") mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) result = await mgr.logout(FakeBridge()) assert result is False assert mgr.stage == ZaloCredentialStage.LOGGED_OUT class TestReset: def test_reset(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) mgr.reset() assert mgr.stage == ZaloCredentialStage.UNINITIALIZED assert mgr.credential_signature is None class TestToDict: def test_to_dict(self): mgr = CredentialManager() mgr.mark_connected({"user_id": "123"}) d = mgr.to_dict() assert d["stage"] == "connected" assert d["is_authenticated"] is True assert "last_check_time" in d assert "error" in d