1+ """
2+ tests/conftest.py
3+ Shared pytest fixtures. No live server needed — uses Flask test client
4+ with an in-memory SQLite database.
5+ """
6+
7+ import sys
8+ import os
9+ from unittest .mock import MagicMock , patch
10+
11+ import pytest
12+
13+ # Make backend/ importable
14+ ROOT = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
15+ BACKEND = os .path .join (ROOT , "backend" )
16+ for p in (ROOT , BACKEND ):
17+ if p not in sys .path :
18+ sys .path .insert (0 , p )
19+
20+ # Stub out heavy optional packages so tests don't need GPU/model downloads
21+ for _mod in [
22+ "transformers" , "sentence_transformers" ,
23+ "nltk" , "nltk.tokenize" , "nltk.tokenize.api" ,
24+ "huggingface_hub" , "safetensors" , "tokenizers" , "tqdm" , "joblib" ,
25+ "textblob" , "textblob.blob" , "textblob.base" ,
26+ ]:
27+ sys .modules .setdefault (_mod , MagicMock ())
28+
29+ # Set required env vars before importing app so validation passes
30+ os .environ .setdefault ("GOOGLE_BOOKS_API_KEY" , "fake-key-for-testing" )
31+ os .environ .setdefault ("SECRET_KEY" , "test-secret-key" )
32+ os .environ .setdefault ("JWT_SECRET_KEY" , "test-jwt-secret-key" )
33+ os .environ .setdefault ("DATABASE_URL" , "sqlite:///:memory:" )
34+ os .environ .setdefault ("OPENAI_API_KEY" , "fake-openai-key" )
35+ os .environ .setdefault ("GROQ_API_KEY" , "fake-groq-key" )
36+ os .environ .setdefault ("GEMINI_API_KEY" , "fake-gemini-key" )
37+ os .environ .setdefault ("FLASK_ENV" , "testing" )
38+
39+ from backend .app import app as flask_app
40+ from backend .models import db as _db , User
41+
42+
43+ @pytest .fixture (scope = "session" )
44+ def app ():
45+ flask_app .config .update (
46+ TESTING = True ,
47+ SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" ,
48+ SQLALCHEMY_TRACK_MODIFICATIONS = False ,
49+ JWT_SECRET_KEY = "test-secret-key" ,
50+ SECRET_KEY = "test-secret-key" ,
51+ RATELIMIT_ENABLED = False ,
52+ )
53+ with flask_app .app_context ():
54+ _db .create_all ()
55+ yield flask_app
56+ _db .drop_all ()
57+
58+
59+ @pytest .fixture
60+ def client (app ):
61+ with app .test_client () as c :
62+ with app .app_context ():
63+ yield c
64+ _db .session .rollback ()
65+ for tbl in reversed (_db .metadata .sorted_tables ):
66+ _db .session .execute (tbl .delete ())
67+ _db .session .commit ()
68+
69+
70+ @pytest .fixture
71+ def test_user (app ):
72+ with app .app_context ():
73+ user = User (username = "testuser" , email = "test@example.com" )
74+ user .set_password ("Password123!" )
75+ _db .session .add (user )
76+ _db .session .commit ()
77+ _db .session .refresh (user )
78+ return user
79+
80+
81+ @pytest .fixture
82+ def auth_headers (client , test_user ):
83+ resp = client .post (
84+ "/api/auth/login" ,
85+ json = {"username" : "testuser" , "password" : "Password123!" },
86+ )
87+ token = resp .get_json ().get ("access_token" , "" )
88+ return {"Authorization" : f"Bearer { token } " }
89+
90+
91+ # --- Mocks for external services (issue #511 requirement) ---
92+
93+ @pytest .fixture
94+ def mock_openai ():
95+ with patch ("backend.ai_service.openai" ) as m :
96+ m .ChatCompletion .create .return_value = MagicMock (
97+ choices = [MagicMock (message = MagicMock (content = "Mocked AI response" ))]
98+ )
99+ yield m
100+
101+
102+ @pytest .fixture
103+ def mock_groq ():
104+ with patch ("backend.ai_service.groq" ) as m :
105+ m .chat .completions .create .return_value = MagicMock (
106+ choices = [MagicMock (message = MagicMock (content = "Mocked Groq response" ))]
107+ )
108+ yield m
109+
110+
111+ @pytest .fixture
112+ def mock_gemini ():
113+ with patch ("backend.ai_service.genai" ) as m :
114+ m .GenerativeModel .return_value .generate_content .return_value = MagicMock (
115+ text = "Mocked Gemini response"
116+ )
117+ yield m
118+
119+
120+ @pytest .fixture
121+ def mock_price_tracker ():
122+ with patch ("backend.price_tracker.price_tracker.PriceTracker.fetch_price" ) as m :
123+ m .return_value = {"price" : 9.99 , "currency" : "USD" , "source" : "mock" }
124+ yield m
0 commit comments