Skip to content

Commit f915733

Browse files
committed
Add DB/memory unit tests and adjust integration skip
1 parent d9600b7 commit f915733

File tree

7 files changed

+149
-279
lines changed

7 files changed

+149
-279
lines changed

feedback_agent/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33

44
def __getattr__(name: str):
5+
if name == "utils":
6+
import importlib
7+
return importlib.import_module("feedback_agent.utils")
58
if name in ("FeedbackSystem", "get_root_agent"):
69
from .agent import FeedbackSystem, get_root_agent
710
return {"FeedbackSystem": FeedbackSystem, "get_root_agent": get_root_agent}[name]

feedback_agent/database.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,23 @@ def log_analysis(self, exam_id: str, weaknesses: Optional[List[str]] = None, top
100100
current_weaknesses, current_topics, current_recommendations = row
101101
new_weaknesses = json.dumps(weaknesses) if weaknesses is not None else current_weaknesses
102102
new_topics = json.dumps(topics) if topics is not None else current_topics
103-
new_recommendations = recommendations if recommendations is not None else current_recommendations
103+
if recommendations is not None:
104+
new_recommendations = (
105+
json.dumps(recommendations)
106+
if not isinstance(recommendations, str)
107+
else recommendations
108+
)
109+
else:
110+
new_recommendations = current_recommendations
104111

105112
cursor.execute('''
106113
UPDATE analysis
107114
SET weaknesses = ?, topics = ?, recommendations = ?
108115
WHERE exam_id = ?
109116
''', (new_weaknesses, new_topics, new_recommendations, exam_id))
110117
else:
118+
if recommendations is not None and not isinstance(recommendations, str):
119+
recommendations = json.dumps(recommendations)
111120
cursor.execute('''
112121
INSERT INTO analysis (exam_id, weaknesses, topics, recommendations)
113122
VALUES (?, ?, ?, ?)
@@ -223,7 +232,7 @@ def get_analysis(self, exam_id: str) -> Optional[Dict]:
223232
conn.row_factory = sqlite3.Row
224233
cursor = conn.cursor()
225234
cursor.execute('''
226-
SELECT weaknesses, recommendations
235+
SELECT weaknesses, topics, recommendations
227236
FROM analysis
228237
WHERE exam_id = ?
229238
''', (exam_id,))
@@ -233,6 +242,7 @@ def get_analysis(self, exam_id: str) -> Optional[Dict]:
233242
if row:
234243
return {
235244
'weaknesses': json.loads(row['weaknesses']) if row['weaknesses'] else [],
236-
'recommendations': row['recommendations']
245+
'topics': json.loads(row['topics']) if row['topics'] else [],
246+
'recommendations': row['recommendations'],
237247
}
238248
return None

tests/test_database.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
from feedback_agent.database import StudentDatabase
44

55

6+
def test_add_student_idempotent(tmp_path):
7+
db_path = tmp_path / "students.db"
8+
db = StudentDatabase(str(db_path))
9+
db.add_student("s1", "Alice")
10+
db.add_student("s1", "Alice")
11+
students = db.get_all_students()
12+
assert len(students) == 1
13+
14+
615
def test_student_lookup_and_history_order(tmp_path):
716
db_path = tmp_path / "students.db"
817
db = StudentDatabase(str(db_path))

tests/test_database_more.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import uuid
2+
3+
from feedback_agent.database import StudentDatabase
4+
5+
6+
def test_get_student_exams_sorted_desc(tmp_path):
7+
db_path = tmp_path / "students.db"
8+
db = StudentDatabase(str(db_path))
9+
student_id = str(uuid.uuid4())
10+
db.add_student(student_id, "Sort Test")
11+
12+
exam_id_1 = str(uuid.uuid4())
13+
exam_id_2 = str(uuid.uuid4())
14+
15+
db.log_exam(exam_id_1, student_id, "Math", 8, 10)
16+
db.log_exam(exam_id_2, student_id, "Science", 9, 10)
17+
18+
exams = db.get_student_exams(student_id)
19+
assert len(exams) == 2
20+
assert exams[0]["exam_id"] == exam_id_2
21+
assert exams[1]["exam_id"] == exam_id_1
22+
23+
24+
def test_get_analysis_topics_and_recommendations(tmp_path):
25+
db_path = tmp_path / "students.db"
26+
db = StudentDatabase(str(db_path))
27+
student_id = str(uuid.uuid4())
28+
db.add_student(student_id, "Analysis Test")
29+
30+
exam_id = str(uuid.uuid4())
31+
db.log_exam(exam_id, student_id, "History", 5, 10)
32+
33+
db.log_analysis(
34+
exam_id,
35+
weaknesses=[{"topic": "Revolutions"}],
36+
topics=["Revolutions"],
37+
recommendations='{"plan": "review"}',
38+
)
39+
40+
analysis = db.get_analysis(exam_id)
41+
assert analysis is not None
42+
assert analysis["weaknesses"][0]["topic"] == "Revolutions"
43+
assert analysis["topics"] == ["Revolutions"]
44+
assert analysis["recommendations"] == '{"plan": "review"}'

tests/test_integration.py

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,18 @@
1-
import pytest
1+
import asyncio
2+
from unittest.mock import patch
23

3-
pytest.importorskip("google.adk")
4+
from feedback_agent.database import StudentDatabase
45

5-
import os
6-
from feedback_agent.agent import FeedbackSystem
76

8-
@pytest.fixture
9-
def system():
10-
# Use a temporary db for testing
11-
db_path = "data/test_students.db"
12-
if os.path.exists(db_path):
13-
os.remove(db_path)
14-
15-
sys = FeedbackSystem(db_path=db_path)
16-
yield sys
17-
18-
if os.path.exists(db_path):
19-
os.remove(db_path)
7+
def test_full_flow_mocked(tmp_path):
8+
import pytest
9+
pytest.importorskip("google.adk")
10+
db_path = tmp_path / "students.db"
11+
_ = StudentDatabase(str(db_path))
2012

13+
def mock_run_agent(agent, prompt, image_path=None, image_bytes=None, session_state=None):
14+
import json
2115

22-
23-
def test_full_flow_mocked(system):
24-
from unittest.mock import patch
25-
import json
26-
27-
# Mock responses for different agents
28-
def mock_run_agent(agent, prompt):
2916
if "grading_agent" in agent.name:
3017
return json.dumps({
3118
"total_score": 8,
@@ -52,43 +39,73 @@ def mock_run_agent(agent, prompt):
5239
})
5340
elif "analysis_agent" in agent.name:
5441
return json.dumps({
55-
"weaknesses": ["Arithmetic"],
42+
"topics": ["Arithmetic"],
43+
"weaknesses": [
44+
{"topic": "Arithmetic", "description": "Struggled with subtraction", "severity": "medium"}
45+
],
5646
"summary": "Weak in math."
5747
})
58-
elif "recommendation_agent" in agent.name:
48+
elif "study_materials_agent" in agent.name:
49+
return json.dumps({
50+
"study_materials": [
51+
{"topic": "Arithmetic", "resources": [{"type": "textbook", "title": "Math 101", "description": "Basics", "difficulty": "beginner"}]}
52+
]
53+
})
54+
elif "practice_problems_agent" in agent.name:
55+
return json.dumps({
56+
"practice_problems": [
57+
{"topic": "Arithmetic", "problems": [{"difficulty": "easy", "problem": "2+2", "hint": "Add", "learning_goal": "Addition"}]}
58+
]
59+
})
60+
elif "learning_strategy_agent" in agent.name:
61+
return json.dumps({
62+
"learning_strategy": {
63+
"study_schedule": {"weekly_hours": 2, "sessions_per_week": 2, "session_duration": "30m"},
64+
"learning_techniques": [{"technique": "Flashcards", "when_to_use": "Daily", "expected_benefit": "Recall"}],
65+
"milestones": [{"timeline": "1 week", "goal": "Basics", "success_criteria": "80% correct"}]
66+
}
67+
})
68+
elif "recommendation_synthesizer" in agent.name:
5969
return json.dumps({
6070
"learning_objectives": [
6171
{
62-
"topic": "Arithmetic",
6372
"objective": "Learn addition",
64-
"resources": ["Math book"]
73+
"resources": ["Math 101"],
74+
"practice_activities": ["2+2"],
75+
"estimated_time": "1 week",
76+
"priority": "high"
6577
}
6678
],
67-
"encouragement": "Keep practicing!"
79+
"weekly_plan": {
80+
"total_hours": 2,
81+
"activities": [{"day": "Mon", "activity": "Practice", "duration": "30m", "resources_needed": ["Math 101"]}]
82+
},
83+
"encouragement": "Keep practicing!",
84+
"success_metrics": ["80% on quizzes"]
6885
})
6986
return "{}"
7087

71-
with patch('feedback_agent.utils.run_agent', side_effect=mock_run_agent):
72-
# 1. Register Student
88+
with patch("feedback_agent.agent.run_agent", side_effect=mock_run_agent, create=True):
89+
from feedback_agent.agent import FeedbackSystem
90+
91+
system = FeedbackSystem(
92+
db_path=str(db_path),
93+
use_memory_sessions=True,
94+
enable_metrics=False,
95+
enable_logging_plugin=False,
96+
)
97+
7398
student_id = system.register_student("Alice")
7499
assert student_id is not None
75100

76-
# 2. Process Exam
77-
exam_content = "..."
78-
answer_key = "..."
79-
80-
result = system.process_exam(student_id, "General Knowledge", exam_content, answer_key)
81-
82-
# Verify Grading
83-
assert result['grading']['total_score'] == 8
84-
85-
# Verify Analysis
86-
assert "Arithmetic" in result['analysis']['weaknesses']
87-
88-
# Verify Recommendations
89-
assert len(result['recommendations']['learning_objectives']) > 0
101+
result = asyncio.run(
102+
system.process_exam(student_id, "...", "...", "General Knowledge")
103+
)
104+
105+
assert result["total_score"] == 8
106+
assert "Arithmetic" in result["weaknesses"][0]["topic"]
107+
assert result["recommendations"]
90108

91-
# Verify DB Persistence
92109
history = system.db.get_student_history(student_id)
93110
assert len(history) == 1
94-
assert history[0]['subject'] == "General Knowledge"
111+
assert history[0]["subject"] == "General Knowledge"

0 commit comments

Comments
 (0)