Skip to content

Commit dee0ff9

Browse files
committed
test: add Classification API intent classification test
Add e2e test for standalone Classification API service that validates the /api/v1/classify/intent endpoint correctly classifies different types of queries. Test validates: - Math queries are classified as 'math' - Computer science queries are classified as 'computer science' - Business queries are classified as 'business' - History queries are classified as 'history' - Batch classification endpoint processes multiple texts correctly The Classification API (port 8080) is a standalone service separate from the ExtProc router, providing direct classification capabilities for applications that need text classification without LLM routing. Test requirements: - Classification API must be running on port 8080 - Start with: make run-router-e2e Signed-off-by: Yossi Ovadia <[email protected]>
1 parent 6cef4dd commit dee0ff9

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#!/usr/bin/env python3
2+
"""
3+
03-classification-api-test.py - Classification API tests
4+
5+
This test validates the standalone Classification API service,
6+
which provides direct classification capabilities without LLM routing.
7+
The API is separate from the ExtProc router and runs on port 8080.
8+
"""
9+
10+
import json
11+
import sys
12+
import unittest
13+
14+
import requests
15+
16+
# Import test base from same directory
17+
from test_base import SemanticRouterTestBase
18+
19+
# Constants
20+
CLASSIFICATION_API_URL = "http://localhost:8080"
21+
INTENT_ENDPOINT = "/api/v1/classify/intent"
22+
23+
# Test cases with expected categories based on config.e2e.yaml
24+
INTENT_TEST_CASES = [
25+
{
26+
"name": "Math Query",
27+
"text": "Solve the quadratic equation x^2 + 5x + 6 = 0",
28+
"expected_category": "math",
29+
},
30+
{
31+
"name": "Computer Science Query",
32+
"text": "Write a Python function to implement a linked list",
33+
"expected_category": "computer science",
34+
},
35+
{
36+
"name": "Business Query",
37+
"text": "What are the key principles of supply chain management?",
38+
"expected_category": "business",
39+
},
40+
{
41+
"name": "History Query",
42+
"text": "Describe the main causes of World War I",
43+
"expected_category": "history",
44+
},
45+
]
46+
47+
48+
class ClassificationAPITest(SemanticRouterTestBase):
49+
"""Test the standalone Classification API service."""
50+
51+
def setUp(self):
52+
"""Check if the Classification API is running before running tests."""
53+
self.print_test_header(
54+
"Setup Check",
55+
"Verifying that Classification API is running and accepting requests",
56+
)
57+
58+
try:
59+
# Test health endpoint
60+
health_response = requests.get(
61+
f"{CLASSIFICATION_API_URL}/health", timeout=5
62+
)
63+
64+
if health_response.status_code != 200:
65+
self.skipTest(
66+
f"Classification API health check failed: {health_response.status_code}"
67+
)
68+
69+
self.print_response_info(
70+
health_response, {"Service": "Classification API Health"}
71+
)
72+
73+
except requests.exceptions.ConnectionError:
74+
self.skipTest(
75+
"Cannot connect to Classification API on port 8080. Is it running? Start with: make run-router-e2e"
76+
)
77+
except requests.exceptions.Timeout:
78+
self.skipTest("Classification API health check timed out")
79+
80+
def test_intent_classification(self):
81+
"""Test that intent classification returns correct categories for different query types."""
82+
self.print_test_header(
83+
"Intent Classification Test",
84+
"Verifies that Classification API correctly classifies different query types",
85+
)
86+
87+
for test_case in INTENT_TEST_CASES:
88+
self.print_subtest_header(test_case["name"])
89+
90+
payload = {
91+
"text": test_case["text"],
92+
"options": {"return_probabilities": False},
93+
}
94+
95+
self.print_request_info(
96+
payload=payload,
97+
expectations=f"Expect: Correctly classified as '{test_case['expected_category']}'",
98+
)
99+
100+
response = requests.post(
101+
f"{CLASSIFICATION_API_URL}{INTENT_ENDPOINT}",
102+
headers={"Content-Type": "application/json"},
103+
json=payload,
104+
timeout=10,
105+
)
106+
107+
response_json = response.json()
108+
# The response may be nested in "classification" or at top level
109+
if "classification" in response_json:
110+
classification = response_json["classification"]
111+
actual_category = classification.get("category", "unknown")
112+
confidence = classification.get("confidence", 0.0)
113+
else:
114+
actual_category = response_json.get("category", "unknown")
115+
confidence = response_json.get("confidence", 0.0)
116+
117+
# Check if classification is correct
118+
category_correct = actual_category == test_case["expected_category"]
119+
is_placeholder = actual_category == "general"
120+
passed = response.status_code == 200 and category_correct
121+
122+
self.print_response_info(
123+
response,
124+
{
125+
"Expected Category": test_case["expected_category"],
126+
"Actual Category": actual_category,
127+
"Confidence": f"{confidence:.2f}",
128+
"Is Placeholder": "⚠️ Yes" if is_placeholder else "No",
129+
"Category Match": "✅" if category_correct else "❌",
130+
},
131+
)
132+
133+
if not category_correct:
134+
if is_placeholder:
135+
failure_message = (
136+
f"Classification failed: returned placeholder 'general' instead of '{test_case['expected_category']}'"
137+
)
138+
else:
139+
failure_message = (
140+
f"Classification incorrect: expected '{test_case['expected_category']}', "
141+
f"got '{actual_category}'"
142+
)
143+
else:
144+
failure_message = None
145+
146+
self.print_test_result(
147+
passed=passed,
148+
message=(
149+
f"Correctly classified as '{actual_category}'"
150+
if passed
151+
else failure_message
152+
),
153+
)
154+
155+
self.assertEqual(
156+
response.status_code,
157+
200,
158+
f"Request failed with status {response.status_code}",
159+
)
160+
161+
self.assertEqual(
162+
actual_category,
163+
test_case["expected_category"],
164+
f"{test_case['name']}: Expected category '{test_case['expected_category']}', got '{actual_category}'",
165+
)
166+
167+
def test_batch_classification(self):
168+
"""Test batch classification endpoint works correctly."""
169+
self.print_test_header(
170+
"Batch Classification Test",
171+
"Verifies that batch classification endpoint processes multiple texts correctly",
172+
)
173+
174+
texts = [tc["text"] for tc in INTENT_TEST_CASES]
175+
expected_categories = [tc["expected_category"] for tc in INTENT_TEST_CASES]
176+
177+
payload = {"texts": texts, "task_type": "intent"}
178+
179+
self.print_request_info(
180+
payload={"texts": f"{len(texts)} texts", "task_type": "intent"},
181+
expectations=f"Expect: {len(texts)} classifications matching expected categories",
182+
)
183+
184+
response = requests.post(
185+
f"{CLASSIFICATION_API_URL}/api/v1/classify/batch",
186+
headers={"Content-Type": "application/json"},
187+
json=payload,
188+
timeout=30,
189+
)
190+
191+
response_json = response.json()
192+
results = response_json.get("results", [])
193+
194+
self.print_response_info(
195+
response,
196+
{
197+
"Total Texts": len(texts),
198+
"Results Count": len(results),
199+
"Processing Time (ms)": response_json.get("processing_time_ms", 0),
200+
},
201+
)
202+
203+
passed = response.status_code == 200 and len(results) == len(texts)
204+
205+
self.print_test_result(
206+
passed=passed,
207+
message=(
208+
f"Successfully classified {len(results)} texts"
209+
if passed
210+
else f"Batch classification failed or returned wrong count"
211+
),
212+
)
213+
214+
self.assertEqual(response.status_code, 200, "Batch request failed")
215+
self.assertEqual(len(results), len(texts), "Result count mismatch")
216+
217+
218+
if __name__ == "__main__":
219+
unittest.main()
220+

0 commit comments

Comments
 (0)