Skip to content

Commit 82c7b9b

Browse files
committed
add tests
1 parent 23e01ec commit 82c7b9b

File tree

5 files changed

+166
-1
lines changed

5 files changed

+166
-1
lines changed

python/src/cairo_coder/server/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def hash_user_id(user_id: str | None) -> str | None:
5555
Returns:
5656
Hashed user ID (first 32 chars of SHA-256 hex digest) or None if no input
5757
"""
58-
if not user_id:
58+
if user_id is None:
5959
return None
6060
return hashlib.sha256(user_id.encode()).hexdigest()[:32]
6161

python/tests/integration/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ async def test_db_pool(postgres_container):
183183
agent_id VARCHAR(50) NOT NULL,
184184
mcp_mode BOOLEAN NOT NULL DEFAULT FALSE,
185185
conversation_id VARCHAR(100),
186+
user_id VARCHAR(100),
186187
chat_history JSONB,
187188
query TEXT NOT NULL,
188189
generated_answer TEXT,
@@ -199,6 +200,8 @@ async def test_db_pool(postgres_container):
199200
ON user_interactions(agent_id);
200201
CREATE INDEX IF NOT EXISTS idx_interactions_conversation_id
201202
ON user_interactions(conversation_id);
203+
CREATE INDEX IF NOT EXISTS idx_interactions_user_id
204+
ON user_interactions(user_id);
202205
"""
203206
)
204207

python/tests/integration/test_insights_api.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,72 @@ async def seed():
221221
assert len(matching) == 1
222222
assert matching[0]["conversation_id"] == conv_id
223223

224+
def test_get_queries_with_user_id_filter(self, client, db_connection):
225+
"""Test that queries can be filtered by user_id."""
226+
import asyncio
227+
import json as _json
228+
import uuid
229+
from datetime import datetime, timedelta, timezone
230+
231+
now = datetime.now(timezone.utc)
232+
user_id = "test-user-hash-789"
233+
234+
# Seed records with and without user_id
235+
async def seed():
236+
await db_connection.execute(
237+
"""
238+
INSERT INTO user_interactions (id, created_at, agent_id, mcp_mode, user_id, chat_history, query, generated_answer)
239+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8),
240+
($9, $10, $11, $12, $13, $14, $15, $16),
241+
($17, $18, $19, $20, $21, $22, $23, $24)
242+
""",
243+
uuid.uuid4(), now - timedelta(hours=2), "cairo-coder", False, user_id, _json.dumps([]), "First msg", "Response 1",
244+
uuid.uuid4(), now - timedelta(hours=1), "cairo-coder", False, user_id, _json.dumps([{"role": "user", "content": "First msg"}]), "Second msg", "Response 2",
245+
uuid.uuid4(), now - timedelta(minutes=30), "cairo-coder", False, None, _json.dumps([]), "Other msg", "Other response",
246+
)
247+
248+
asyncio.get_event_loop().run_until_complete(seed())
249+
250+
# Filter by user_id
251+
resp = client.get(
252+
"/v1/insights/queries",
253+
params={"user_id": user_id, "limit": 100, "offset": 0},
254+
)
255+
assert resp.status_code == 200
256+
data = resp.json()
257+
assert data["total"] == 2
258+
assert all(item["user_id"] == user_id for item in data["items"])
259+
260+
def test_get_queries_returns_user_id(self, client, db_connection):
261+
"""Test that user_id is included in the response."""
262+
import asyncio
263+
import json as _json
264+
import uuid
265+
from datetime import datetime, timezone
266+
267+
now = datetime.now(timezone.utc)
268+
user_id = "response-test-user-456"
269+
270+
async def seed():
271+
await db_connection.execute(
272+
"""
273+
INSERT INTO user_interactions (id, created_at, agent_id, mcp_mode, user_id, chat_history, query, generated_answer)
274+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
275+
""",
276+
uuid.uuid4(), now, "cairo-coder", False, user_id, _json.dumps([]), "Test query", "Test response",
277+
)
278+
279+
asyncio.get_event_loop().run_until_complete(seed())
280+
281+
resp = client.get("/v1/insights/queries", params={"limit": 100, "offset": 0})
282+
assert resp.status_code == 200
283+
data = resp.json()
284+
285+
# Find our seeded record and verify user_id is present
286+
matching = [item for item in data["items"] if item.get("user_id") == user_id]
287+
assert len(matching) == 1
288+
assert matching[0]["user_id"] == user_id
289+
224290

225291
class TestDataIngestion:
226292
async def test_chat_completion_logs_interaction_to_db(self, client, test_db_pool):

python/tests/unit/db/test_repository.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,72 @@ async def test_get_interactions_filter_by_conversation_id(test_db_pool, db_conne
202202
assert None in conv_ids
203203

204204

205+
@pytest.mark.asyncio
206+
async def test_create_user_interaction_with_user_id(test_db_pool, db_connection):
207+
"""Test that user_id is stored correctly."""
208+
from cairo_coder.db.models import UserInteraction
209+
from cairo_coder.db.repository import create_user_interaction
210+
211+
user_id = "hashed_user_abc123"
212+
interaction = UserInteraction(
213+
agent_id="cairo-coder",
214+
mcp_mode=False,
215+
user_id=user_id,
216+
chat_history=[{"role": "user", "content": "Hello"}],
217+
query="Hello",
218+
generated_answer="Hi",
219+
)
220+
221+
await create_user_interaction(interaction)
222+
223+
row = await db_connection.fetchrow("SELECT * FROM user_interactions WHERE id = $1", interaction.id)
224+
assert row is not None
225+
assert row["user_id"] == user_id
226+
227+
228+
@pytest.mark.asyncio
229+
async def test_get_interactions_filter_by_user_id(test_db_pool, db_connection):
230+
"""Test that interactions can be filtered by user_id."""
231+
from cairo_coder.db.repository import get_interactions
232+
233+
now = datetime.now(timezone.utc)
234+
user_id_1 = "hashed_user_aaa"
235+
user_id_2 = "hashed_user_bbb"
236+
237+
# Seed records with different user IDs
238+
await db_connection.execute(
239+
"""
240+
INSERT INTO user_interactions (id, created_at, agent_id, mcp_mode, query, user_id)
241+
VALUES ($1, $2, $3, $4, $5, $6),
242+
($7, $8, $9, $10, $11, $12),
243+
($13, $14, $15, $16, $17, $18),
244+
($19, $20, $21, $22, $23, $24)
245+
""",
246+
uuid.uuid4(), now - timedelta(hours=3), "cairo-coder", False, "First message user 1", user_id_1,
247+
uuid.uuid4(), now - timedelta(hours=2), "cairo-coder", False, "Second message user 1", user_id_1,
248+
uuid.uuid4(), now - timedelta(hours=1), "cairo-coder", False, "First message user 2", user_id_2,
249+
uuid.uuid4(), now - timedelta(minutes=30), "cairo-coder", False, "No user", None,
250+
)
251+
252+
# Filter by user_id 1
253+
items, total = await get_interactions(None, None, None, 100, 0, user_id=user_id_1)
254+
assert total == 2
255+
assert all(it["user_id"] == user_id_1 for it in items)
256+
257+
# Filter by user_id 2
258+
items, total = await get_interactions(None, None, None, 100, 0, user_id=user_id_2)
259+
assert total == 1
260+
assert items[0]["user_id"] == user_id_2
261+
262+
# Verify user_id is returned in results
263+
items, total = await get_interactions(None, None, None, 100, 0)
264+
assert total == 4
265+
user_ids = [it.get("user_id") for it in items]
266+
assert user_id_1 in user_ids
267+
assert user_id_2 in user_ids
268+
assert None in user_ids
269+
270+
205271
@pytest.mark.asyncio
206272
async def test_migrate_user_interaction_upsert(test_db_pool, db_connection):
207273
"""Test that migrate_user_interaction performs upsert (insert or update)."""
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
Unit tests for server utility functions.
3+
"""
4+
class TestHashUserId:
5+
"""Tests for the hash_user_id function."""
6+
7+
def test_hash_user_id_returns_consistent_hash(self):
8+
"""Test that the same input always produces the same hash."""
9+
from cairo_coder.server.app import hash_user_id
10+
11+
user_id = "test-user-123"
12+
hash1 = hash_user_id(user_id)
13+
hash2 = hash_user_id(user_id)
14+
15+
assert hash1 == hash2
16+
assert len(hash1) == 32 # Truncated to 32 chars
17+
18+
def test_hash_user_id_returns_none_for_none_input(self):
19+
"""Test that None input returns None."""
20+
from cairo_coder.server.app import hash_user_id
21+
22+
result = hash_user_id(None)
23+
assert result is None
24+
25+
def test_hash_user_id_returns_hash_for_empty_string(self):
26+
"""Test that empty string input returns a hash."""
27+
from cairo_coder.server.app import hash_user_id
28+
29+
result = hash_user_id("")
30+
assert result == "e3b0c44298fc1c149afbf4c8996fb924"

0 commit comments

Comments
 (0)