Skip to content

Commit 8c2f8a9

Browse files
committed
test: fix test isolation and mocking issues
- Fix repo_manager mocking to use patch.object on instance - Add early patching in conftest.py to prevent import-time errors - Fix auth module reload cleanup in DevApiKeySecurity tests - Remove module-level sys.modules pollution from test_validate_repo.py - Clean up lint issues (unused imports, line length, whitespace) All 139 tests now pass consistently.
1 parent 0bdafa3 commit 8c2f8a9

3 files changed

Lines changed: 204 additions & 148 deletions

File tree

backend/tests/conftest.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66
import sys
77
from pathlib import Path
8-
from unittest.mock import Mock, MagicMock, patch
8+
from unittest.mock import MagicMock, patch
99
import os
1010

1111
# Set test environment BEFORE imports
@@ -20,6 +20,35 @@
2020
os.environ["SUPABASE_ANON_KEY"] = "test-anon-key"
2121
os.environ["SUPABASE_JWT_SECRET"] = "test-jwt-secret"
2222

23+
# =============================================================================
24+
# EARLY PATCHING - runs during collection, before any imports
25+
# =============================================================================
26+
# These patches prevent external service initialization during test collection
27+
28+
_pinecone_patcher = patch('pinecone.Pinecone')
29+
_mock_pinecone = _pinecone_patcher.start()
30+
_pc_instance = MagicMock()
31+
_pc_instance.list_indexes.return_value.names.return_value = []
32+
_pc_instance.Index.return_value = MagicMock()
33+
_mock_pinecone.return_value = _pc_instance
34+
35+
_openai_patcher = patch('openai.AsyncOpenAI')
36+
_mock_openai = _openai_patcher.start()
37+
_openai_client = MagicMock()
38+
_mock_openai.return_value = _openai_client
39+
40+
_supabase_patcher = patch('supabase.create_client')
41+
_mock_supabase = _supabase_patcher.start()
42+
_supabase_client = MagicMock()
43+
_supabase_client.table.return_value.select.return_value.execute.return_value.data = []
44+
# Auth should reject by default - set user to None
45+
_auth_response = MagicMock()
46+
_auth_response.user = None
47+
_supabase_client.auth.get_user.return_value = _auth_response
48+
_mock_supabase.return_value = _supabase_client
49+
50+
# =============================================================================
51+
2352
# Add backend to path
2453
backend_dir = Path(__file__).parent.parent
2554
sys.path.insert(0, str(backend_dir))
@@ -50,29 +79,29 @@ def mock_pinecone():
5079
yield mock
5180

5281

53-
@pytest.fixture(scope="session", autouse=True)
82+
@pytest.fixture(scope="session", autouse=True)
5483
def mock_redis():
5584
"""
5685
Mock Redis client globally.
57-
86+
5887
Includes hash operations for session management (#127).
5988
"""
6089
with patch('redis.Redis') as mock:
6190
redis_instance = MagicMock()
62-
91+
6392
# Connection
6493
redis_instance.ping.return_value = True
65-
94+
6695
# String operations (legacy + IP/global limits)
6796
redis_instance.get.return_value = None
6897
redis_instance.set.return_value = True
6998
redis_instance.incr.return_value = 1
7099
redis_instance.delete.return_value = 1
71-
100+
72101
# TTL operations
73102
redis_instance.expire.return_value = True
74103
redis_instance.ttl.return_value = 86400
75-
104+
76105
# Hash operations (session management #127)
77106
redis_instance.type.return_value = b'hash'
78107
redis_instance.hset.return_value = 1
@@ -84,7 +113,7 @@ def mock_redis():
84113
redis_instance.hincrby.return_value = 1
85114
redis_instance.hexists.return_value = False
86115
redis_instance.hdel.return_value = 1
87-
116+
88117
mock.return_value = redis_instance
89118
yield mock
90119

@@ -95,30 +124,30 @@ def mock_supabase():
95124
with patch('supabase.create_client') as mock:
96125
client = MagicMock()
97126
table = MagicMock()
98-
127+
99128
# Mock the fluent interface for tables
100129
execute_result = MagicMock()
101130
execute_result.data = []
102131
execute_result.count = 0
103-
132+
104133
table.select.return_value = table
105134
table.insert.return_value = table
106135
table.update.return_value = table
107-
table.delete.return_value = table
136+
table.delete.return_value = table
108137
table.eq.return_value = table
109138
table.order.return_value = table
110139
table.limit.return_value = table
111140
table.upsert.return_value = table
112141
table.execute.return_value = execute_result
113-
142+
114143
client.table.return_value = table
115-
144+
116145
# Mock auth.get_user to reject invalid tokens
117146
# By default, return response with user=None (invalid token)
118147
auth_response = MagicMock()
119148
auth_response.user = None
120149
client.auth.get_user.return_value = auth_response
121-
150+
122151
mock.return_value = client
123152
yield mock
124153

@@ -141,20 +170,20 @@ def client():
141170
from fastapi.testclient import TestClient
142171
from main import app
143172
from middleware.auth import AuthContext
144-
173+
145174
# Override the require_auth dependency to always return a valid context
146175
async def mock_require_auth():
147176
return AuthContext(
148177
user_id="test-user-123",
149178
email="test@example.com",
150179
tier="enterprise"
151180
)
152-
181+
153182
from middleware.auth import require_auth
154183
app.dependency_overrides[require_auth] = mock_require_auth
155-
184+
156185
yield TestClient(app)
157-
186+
158187
# Cleanup
159188
app.dependency_overrides.clear()
160189

@@ -169,7 +198,7 @@ def client_no_auth():
169198

170199
@pytest.fixture
171200
def valid_headers():
172-
"""Valid authentication headers (not actually used with mocked auth, but kept for compatibility)"""
201+
"""Valid auth headers (kept for compatibility with mocked auth)."""
173202
return {"Authorization": "Bearer test-secret-key"}
174203

175204

@@ -182,6 +211,7 @@ def sample_repo_payload():
182211
"branch": "main"
183212
}
184213

214+
185215
@pytest.fixture
186216
def malicious_payloads():
187217
"""Collection of malicious inputs for security testing"""

0 commit comments

Comments
 (0)