File size: 7,230 Bytes
e86dfae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"""
Test Fixtures and Configuration
Shared pytest fixtures for unit and integration tests.
"""

import asyncio
import uuid
from typing import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
import pytest_asyncio
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

# ── Test database (SQLite in-memory for unit tests) ────────────────────────────
SQLITE_URL = "sqlite:///./test.db"

# We need to patch settings BEFORE importing app modules
import os

os.environ.setdefault("DATABASE_URL", SQLITE_URL)
os.environ.setdefault("GEMINI_API_KEY", "test-api-key-000000000000000000000000")
os.environ.setdefault("CLERK_JWKS_URL", "https://test.clerk.com/.well-known/jwks.json")
os.environ.setdefault("REDIS_URL", "redis://localhost:6379/0")

from app.models import (  # noqa: F401 — register tables
    audit,
    chat,
    document,
    prompt,
    user,
)
from app.models.base import Base

# ── Database fixtures ─────────────────────────────────────────────────────────


@pytest.fixture(scope="session")
def test_engine():
    """Create SQLite test engine (session-scoped for performance)."""
    engine = create_engine(
        SQLITE_URL,
        connect_args={"check_same_thread": False},
    )
    Base.metadata.create_all(bind=engine)
    yield engine
    Base.metadata.drop_all(bind=engine)


@pytest.fixture(scope="function")
def db_session(test_engine) -> Generator[Session, None, None]:
    """
    Provide a clean database session for each test.
    Rolls back all changes after each test for isolation.
    """
    connection = test_engine.connect()
    transaction = connection.begin()

    TestingSessionLocal = sessionmaker(
        bind=connection, autocommit=False, autoflush=False
    )
    session = TestingSessionLocal()

    yield session

    session.close()
    transaction.rollback()
    connection.close()


# ── Test client fixtures ───────────────────────────────────────────────────────


@pytest.fixture(scope="function")
def client(db_session: Session):
    """
    FastAPI test client with auth mocked and DB injected.
    """
    from app.api.deps import get_current_user_id
    from app.db.session import get_db
    from app.main import app

    def override_get_db():
        yield db_session

    def override_get_user_id():
        return "test_user_001"

    app.dependency_overrides[get_db] = override_get_db
    app.dependency_overrides[get_current_user_id] = override_get_user_id

    with TestClient(app) as c:
        yield c

    app.dependency_overrides.clear()


# ── Model factory fixtures ─────────────────────────────────────────────────────


@pytest.fixture
def sample_user_id() -> str:
    return "test_user_001"


@pytest.fixture
def sample_document(db_session: Session, sample_user_id: str):
    """Create a sample document for tests."""
    from app.models.document import Document, DocumentCategory, DocumentStatus

    doc = Document(
        user_id=sample_user_id,
        title="Test Mining Safety Protocol",
        file_name="mining_safety.pdf",
        file_size=1024 * 100,  # 100KB
        file_type="application/pdf",
        file_url="https://example.com/mining_safety.pdf",
        status=DocumentStatus.COMPLETED,
        category=DocumentCategory.SAFETY_PROTOCOL,
        content="This document covers underground coal mine safety procedures...",
        total_pages=25,
        summary="A comprehensive guide to mining safety.",
        key_points=["Wear PPE", "Check ventilation", "Follow evacuation plan"],
        safety_score=82.5,
        classification_confidence=0.95,
    )
    db_session.add(doc)
    db_session.commit()
    db_session.refresh(doc)
    return doc


@pytest.fixture
def sample_embedding(db_session: Session, sample_document):
    """Create a sample document embedding for RAG tests."""
    from app.models.document import DocumentEmbedding

    embedding = DocumentEmbedding(
        document_id=sample_document.id,
        chunk_index=0,
        chunk_text="Underground coal mines require adequate ventilation to prevent methane buildup.",
        embedding=[0.1] * 768,  # Mock 768-dim vector
        page_numbers=[12, 13],
        section_title="Ventilation Requirements",
        start_page=12,
    )
    db_session.add(embedding)
    db_session.commit()
    db_session.refresh(embedding)
    return embedding


@pytest.fixture
def sample_chat_session(db_session: Session, sample_user_id: str):
    """Create a sample chat session."""
    from app.models.chat import ChatSession

    session = ChatSession(
        user_id=sample_user_id,
        title="Test Chat Session",
        document_context=[],
    )
    db_session.add(session)
    db_session.commit()
    db_session.refresh(session)
    return session


# ── Gemini mock fixtures ───────────────────────────────────────────────────────


@pytest.fixture
def mock_gemini_response():
    """Mock Gemini generate_content response."""
    mock = MagicMock()
    mock.text = '{"category": "safety_protocol", "confidence": 0.92, "reasoning": "Contains PPE requirements", "subcategory": "underground"}'
    return mock


@pytest.fixture
def mock_gemini_embedding():
    """Mock Gemini embedding response."""
    return {"embedding": [0.1] * 768}


@pytest.fixture
def mock_chat_response():
    """Mock chat generation response."""
    mock = MagicMock()
    mock.text = (
        "According to [mining_safety.pdf, Page 12], the ventilation requirements "
        "state that all underground coal mines must maintain methane levels below 1%."
    )
    return mock


# ── Sample text fixtures ───────────────────────────────────────────────────────

SAMPLE_PDF_TEXT = """
UNDERGROUND COAL MINE SAFETY PROTOCOL

1. VENTILATION REQUIREMENTS

All underground coal mines must maintain adequate ventilation to prevent the
accumulation of hazardous gases including methane (CH4), carbon monoxide (CO),
and hydrogen sulfide (H2S).

Minimum air velocity requirements per 30 CFR 75.321:
- Main intake airways: minimum 60 feet per minute
- Working sections: minimum 60,000 cubic feet per minute

2. PERSONAL PROTECTIVE EQUIPMENT

All personnel entering underground areas must wear:
- Approved hard hat with headlamp
- Self-rescuer device
- Safety boots with steel toe and anti-static properties

3. EMERGENCY EVACUATION

Evacuation routes must be clearly marked and tested quarterly.
Emergency drills must be conducted at least twice per year per MSHA regulation.
"""


@pytest.fixture
def sample_mining_text():
    return SAMPLE_PDF_TEXT