File size: 4,660 Bytes
c1d887c
 
 
 
 
 
 
 
 
65ba59e
 
c1d887c
 
 
 
 
 
 
65ba59e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c1d887c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65ba59e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""pytest conftest: force backend/ to sys.path[0] and prevent stale services shadow.

Project root has stale services/ dir (with __init__.py) that shadows
backend/services/ and lacks several service modules needed by routes/tests.

This conftest:
1. Ensures backend/ is at sys.path[0]
2. Evicts any stale 'services' package from sys.modules so subsequent
   imports resolve to backend/services/ instead of the project-root copy.
3. Mocks Firebase Admin auth so test tokens (Bearer mock_token_<uid>)
   work without real Firebase credentials.
"""

import sys
import os

import pytest

# โ”€โ”€โ”€ Firebase Auth Mock โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Intercept firebase_admin.auth.verify_id_token so that test tokens like
# "Bearer mock_token_<uid>" work without real Firebase credentials.
# The mock extracts the uid from the token string (e.g. "mock_token_abc" โ†’ uid="abc").
# Non-mock tokens fall through to the real Firebase implementation.

_mock_orig_verify = None  # Lazily resolved


def _get_mock_verify():
    """Return the real verify_id_token, lazily resolved."""
    global _mock_orig_verify
    if _mock_orig_verify is None:
        import firebase_admin.auth as fa_auth

        _mock_orig_verify = getattr(fa_auth, "verify_id_token", None) or (lambda t, **k: {}.get("uid"))
    return _mock_orig_verify


def _mock_verify_id_token(token: str, *, check_revoked: bool = False) -> dict:
    """Return fake Firebase claims dict for tokens with 'mock_token_' prefix."""
    if token and token.startswith("mock_token_"):
        uid = token[len("mock_token_"):]
        return {
            "uid": uid,
            "sub": uid,
            "email": f"{uid}@test.mathpulse.ai",
            "email_verified": True,
            "role": "student",
        }
    # Non-mock tokens: call the real Firebase implementation
    real_verify = _get_mock_verify()
    return real_verify(token, check_revoked=check_revoked)


def _apply_firebase_mock():
    """Apply the mock to firebase_admin.auth.verify_id_token (idempotent)."""
    try:
        import firebase_admin.auth

        current = firebase_admin.auth.verify_id_token
        if not hasattr(current, "_mathpulse_mock"):
            firebase_admin.auth.verify_id_token = _mock_verify_id_token
            firebase_admin.auth.verify_id_token._mathpulse_mock = True
    except Exception:
        pass


# Apply immediately at conftest load time (covers tests that import main.py
# before any test runs, e.g. via module-level TestClient instantiation).
_apply_firebase_mock()


_backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# 1. Force backend/ to sys.path[0]
while _backend_dir in sys.path:
    sys.path.remove(_backend_dir)
sys.path.insert(0, _backend_dir)

# 2. Evict stale 'services' package from import cache.
#    The project root services/ has __init__.py making it a regular package,
#    and it shadows backend/services/ in sys.path resolution.
_keys_to_evict = [k for k in sys.modules if k == "services" or k.startswith("services.")]
for k in _keys_to_evict:
    del sys.modules[k]


def _evict_stale_services():
    """Remove stale 'services' entry from sys.modules if it exists."""
    for k in list(sys.modules.keys()):
        if k == "services" or k.startswith("services."):
            # Only evict if it's NOT the backend/services/* version
            mod = sys.modules[k]
            mod_file = getattr(mod, "__file__", "") or ""
            if _backend_dir in mod_file:
                continue  # keep backend/services/* entries
            del sys.modules[k]


@pytest.fixture(autouse=True)
def _auto_evict_stale_services():
    """Re-evict stale services before every test to prevent cross-file pollution."""
    _evict_stale_services()
    yield


@pytest.fixture(autouse=True)
def _mock_firebase_auth():
    """Re-apply Firebase auth mock before every test.

    Some test files import main.py after conftest.py is loaded, which may
    overwrite the firebase_admin.auth.verify_id_token reference. This fixture
    ensures the mock is always active when tests run.
    """
    try:
        import firebase_admin
        import firebase_admin.auth

        # Only re-apply if not already our mock (avoid infinite recursion)
        current = firebase_admin.auth.verify_id_token
        if not getattr(current, "_is_mocked", False):
            firebase_admin.auth.verify_id_token = _mock_verify_id_token
            firebase_admin.auth.verify_id_token._is_mocked = True
    except Exception:
        pass
    yield