| | """
|
| | End-to-End Integration Tests for ScamShield AI.
|
| |
|
| | Implements Task 8.2: End-to-End Testing
|
| |
|
| | Tests:
|
| | - Full scam detection flow
|
| | - Multi-turn engagement
|
| | - Intelligence extraction
|
| | - Session persistence
|
| |
|
| | Verification:
|
| | - Start server: uvicorn app.main:app --reload
|
| | - Run tests: pytest tests/integration/test_e2e.py -v
|
| | """
|
| |
|
| | import pytest
|
| | import time
|
| | import uuid
|
| | from typing import Dict, Any, List
|
| | from unittest.mock import patch, MagicMock
|
| | from fastapi.testclient import TestClient
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | @pytest.fixture
|
| | def scam_messages() -> Dict[str, str]:
|
| | """Provide various scam message types for testing."""
|
| | return {
|
| | "lottery_en": "Congratulations! You have won 10 lakh rupees in our lucky draw! Share your OTP to claim the prize immediately.",
|
| | "lottery_hi": "आप जीत गए हैं 10 लाख रुपये! अपना OTP शेयर करें।",
|
| | "police_threat_en": "This is police department. You are under investigation. Pay ₹20000 fine immediately to avoid arrest.",
|
| | "police_threat_hi": "यह पुलिस है। आप गिरफ्तार हो जाएंगे। ₹50000 जुर्माना भेजें।",
|
| | "bank_fraud_en": "Your bank account will be blocked in 24 hours. Verify your details by sending ₹500 to our official UPI ID.",
|
| | "bank_fraud_hi": "आपका खाता ब्लॉक हो जाएगा। तुरंत OTP शेयर करें।",
|
| | "with_upi": "Send ₹5000 to scammer@paytm immediately to claim your prize!",
|
| | "with_phone": "Call +919876543210 immediately to verify your account.",
|
| | "with_bank": "Transfer funds to account 1234567890123 (IFSC: SBIN0001234)",
|
| | "with_link": "Visit http://fake-bank.com/verify to update your KYC now!",
|
| | "with_multiple_intel": "Send to scammer@paytm, call +919876543210, or bank 1234567890123 IFSC HDFC0000456",
|
| | }
|
| |
|
| |
|
| | @pytest.fixture
|
| | def legitimate_messages() -> Dict[str, str]:
|
| | """Provide legitimate messages for testing false positive rate."""
|
| | return {
|
| | "greeting": "Hi, how are you doing today? Hope you are well.",
|
| | "appointment": "Reminder: Your dentist appointment is scheduled for tomorrow at 3 PM.",
|
| | "delivery": "Your Amazon order #123456789 has been shipped and will arrive by January 28, 2026.",
|
| | "meeting": "Let's meet for coffee this weekend if you're free.",
|
| | "hindi_greeting": "नमस्ते! आज शाम को मिलते हैं। मैं 6 बजे पहुँच जाऊंगा।",
|
| | "hindi_delivery": "आपकी किताब की डिलीवरी हो गई है। ट्रैकिंग नंबर: TRK123456789",
|
| | }
|
| |
|
| |
|
| | @pytest.fixture
|
| | def multi_turn_scam_conversation() -> List[Dict[str, str]]:
|
| | """Provide a multi-turn scam conversation for testing."""
|
| | return [
|
| | {"sender": "scammer", "message": "Congratulations! You won ₹10 lakh in our lucky draw! Reply to claim."},
|
| | {"sender": "scammer", "message": "Just pay ₹500 processing fee to our UPI: winner@scam"},
|
| | {"sender": "scammer", "message": "Any UPI app works. Send to winner@scam or call +919999888877"},
|
| | {"sender": "scammer", "message": "Hurry up! Your prize expires in 1 hour. Also send to bank 9876543210123 IFSC SBIN0001234"},
|
| | ]
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2EScamDetectionFlow:
|
| | """
|
| | End-to-end tests for the full scam detection flow.
|
| |
|
| | Tests the complete pipeline:
|
| | 1. Message received via API
|
| | 2. Language detection
|
| | 3. Scam classification
|
| | 4. Response generation
|
| | """
|
| |
|
| | def test_e2e_english_scam_detection(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test English scam message detection flow."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| |
|
| | assert data["status"] == "success"
|
| | assert data["scam_detected"] is True
|
| | assert data["confidence"] > 0.5
|
| | assert data["language_detected"] == "en"
|
| | assert "session_id" in data
|
| |
|
| |
|
| | try:
|
| | uuid.UUID(data["session_id"])
|
| | except ValueError:
|
| | pytest.fail("session_id is not a valid UUID")
|
| |
|
| | def test_e2e_hindi_scam_detection(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test Hindi scam message detection flow."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_hi"], "language": "hi"},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | assert data["status"] == "success"
|
| |
|
| | assert data["language_detected"] in ["hi", "hinglish", "en"]
|
| |
|
| |
|
| |
|
| | if data["scam_detected"]:
|
| | assert data["confidence"] > 0.5
|
| | else:
|
| |
|
| |
|
| | print(f"Hindi scam not detected, confidence: {data['confidence']}")
|
| |
|
| | def test_e2e_legitimate_message_not_detected(self, client: TestClient, legitimate_messages: Dict[str, str]):
|
| | """E2E: Test legitimate messages are not flagged as scams."""
|
| | for msg_type, message in legitimate_messages.items():
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": message},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| |
|
| |
|
| | if data["scam_detected"]:
|
| | assert data["confidence"] < 0.7, f"False positive for {msg_type}: confidence={data['confidence']}"
|
| |
|
| | def test_e2e_scam_detection_with_engagement(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test scam detection triggers engagement response."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| |
|
| | data = response.json()
|
| |
|
| | if data["scam_detected"]:
|
| |
|
| | assert "engagement" in data
|
| | if data["engagement"]:
|
| | engagement = data["engagement"]
|
| | assert "agent_response" in engagement
|
| | assert len(engagement["agent_response"]) > 0
|
| | assert "turn_count" in engagement
|
| | assert engagement["turn_count"] >= 1
|
| | assert "strategy" in engagement
|
| | assert engagement["strategy"] in ["build_trust", "express_confusion", "probe_details"]
|
| |
|
| | def test_e2e_response_time_under_threshold(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test response time is under 2 seconds (AC-4.1.5)."""
|
| | start = time.time()
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| | elapsed = time.time() - start
|
| |
|
| | assert response.status_code == 200
|
| |
|
| |
|
| | assert elapsed < 10.0, f"Response time {elapsed:.2f}s exceeds threshold"
|
| |
|
| | def test_e2e_multiple_scam_types(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test detection of various scam types."""
|
| | scam_types = ["lottery_en", "police_threat_en", "bank_fraud_en"]
|
| |
|
| | for scam_type in scam_types:
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages[scam_type]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| | assert data["status"] == "success", f"Failed for scam type: {scam_type}"
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2EMultiTurnEngagement:
|
| | """
|
| | End-to-end tests for multi-turn engagement.
|
| |
|
| | Tests:
|
| | - Session continuity across turns
|
| | - Strategy progression
|
| | - Turn count tracking
|
| | - Conversation history accumulation
|
| | """
|
| |
|
| | def test_e2e_multi_turn_session_continuity(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test session continuity across multiple turns."""
|
| |
|
| | response1 = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| |
|
| | assert response1.status_code == 200
|
| | data1 = response1.json()
|
| | session_id = data1["session_id"]
|
| |
|
| |
|
| | response2 = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={
|
| | "message": "Yes, I want to claim my prize! How do I proceed?",
|
| | "session_id": session_id,
|
| | },
|
| | )
|
| |
|
| | assert response2.status_code == 200
|
| | data2 = response2.json()
|
| |
|
| |
|
| | assert data2["session_id"] == session_id
|
| |
|
| |
|
| | if data2.get("engagement") and data1.get("engagement"):
|
| |
|
| | assert data2["engagement"]["turn_count"] >= data1["engagement"]["turn_count"]
|
| |
|
| | def test_e2e_multi_turn_conversation_history(
|
| | self,
|
| | client: TestClient,
|
| | multi_turn_scam_conversation: List[Dict[str, str]],
|
| | ):
|
| | """E2E: Test conversation history accumulation across turns."""
|
| | session_id = None
|
| |
|
| | for i, turn in enumerate(multi_turn_scam_conversation[:2]):
|
| | request = {"message": turn["message"]}
|
| | if session_id:
|
| | request["session_id"] = session_id
|
| |
|
| | response = client.post("/api/v1/honeypot/engage", json=request)
|
| | assert response.status_code == 200
|
| |
|
| | data = response.json()
|
| | if not session_id:
|
| | session_id = data["session_id"]
|
| |
|
| |
|
| | if "conversation_history" in data and data["conversation_history"]:
|
| | history = data["conversation_history"]
|
| |
|
| | assert len(history) >= i + 1, f"History too short at turn {i+1}"
|
| |
|
| | def test_e2e_strategy_progression(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test strategy progresses with turn count."""
|
| | session_id = None
|
| | strategies_seen = set()
|
| |
|
| |
|
| | for i in range(3):
|
| | request = {"message": scam_messages["lottery_en"]}
|
| | if session_id:
|
| | request["session_id"] = session_id
|
| |
|
| | response = client.post("/api/v1/honeypot/engage", json=request)
|
| | assert response.status_code == 200
|
| |
|
| | data = response.json()
|
| | if not session_id:
|
| | session_id = data["session_id"]
|
| |
|
| | if data.get("engagement") and "strategy" in data["engagement"]:
|
| | strategies_seen.add(data["engagement"]["strategy"])
|
| |
|
| |
|
| | assert len(strategies_seen) >= 1
|
| |
|
| | def test_e2e_max_turns_handling(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test max turns handling (AC-2.2.4: No infinite loops)."""
|
| | session_id = None
|
| | max_test_turns = 5
|
| |
|
| | for i in range(max_test_turns):
|
| | request = {"message": scam_messages["lottery_en"]}
|
| | if session_id:
|
| | request["session_id"] = session_id
|
| |
|
| | response = client.post("/api/v1/honeypot/engage", json=request)
|
| | assert response.status_code == 200
|
| |
|
| | data = response.json()
|
| | if not session_id:
|
| | session_id = data["session_id"]
|
| |
|
| |
|
| | assert data["status"] == "success"
|
| |
|
| | def test_e2e_persona_consistency(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test persona consistency across turns (AC-2.1.3)."""
|
| | session_id = None
|
| | persona = None
|
| |
|
| | for i in range(3):
|
| | request = {"message": scam_messages["lottery_en"]}
|
| | if session_id:
|
| | request["session_id"] = session_id
|
| |
|
| | response = client.post("/api/v1/honeypot/engage", json=request)
|
| | assert response.status_code == 200
|
| |
|
| | data = response.json()
|
| | if not session_id:
|
| | session_id = data["session_id"]
|
| |
|
| | if data.get("engagement") and "persona" in data["engagement"]:
|
| | current_persona = data["engagement"]["persona"]
|
| | if persona is None:
|
| | persona = current_persona
|
| | else:
|
| |
|
| | assert current_persona == persona, "Persona changed mid-conversation"
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2EIntelligenceExtraction:
|
| | """
|
| | End-to-end tests for intelligence extraction.
|
| |
|
| | Tests extraction of:
|
| | - UPI IDs (AC-3.1.1: >90% precision)
|
| | - Bank accounts (AC-3.1.2: >85% precision)
|
| | - IFSC codes (AC-3.1.3: >95% precision)
|
| | - Phone numbers (AC-3.1.4: >90% precision)
|
| | - Phishing links (AC-3.1.5: >95% precision)
|
| | """
|
| |
|
| | def test_e2e_upi_id_extraction(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test UPI ID extraction from scam messages."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["with_upi"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | if data.get("scam_detected") and data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| | assert "upi_ids" in intel
|
| |
|
| | if intel["upi_ids"]:
|
| | assert any("scammer@paytm" in upi.lower() for upi in intel["upi_ids"])
|
| |
|
| | def test_e2e_phone_number_extraction(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test phone number extraction from scam messages."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["with_phone"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | if data.get("scam_detected") and data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| | assert "phone_numbers" in intel
|
| |
|
| | if intel["phone_numbers"]:
|
| | phone_normalized = [p.replace("+91", "").replace("-", "") for p in intel["phone_numbers"]]
|
| | assert any("9876543210" in p for p in phone_normalized)
|
| |
|
| | def test_e2e_bank_account_extraction(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test bank account and IFSC extraction from scam messages."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["with_bank"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | if data.get("scam_detected") and data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| | assert "bank_accounts" in intel
|
| | assert "ifsc_codes" in intel
|
| |
|
| |
|
| | if intel["bank_accounts"]:
|
| | assert "1234567890123" in intel["bank_accounts"]
|
| | if intel["ifsc_codes"]:
|
| | assert any("SBIN0001234" in code for code in intel["ifsc_codes"])
|
| |
|
| | def test_e2e_phishing_link_extraction(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test phishing link extraction from scam messages."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["with_link"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | if data.get("scam_detected") and data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| | assert "phishing_links" in intel
|
| |
|
| | if intel["phishing_links"]:
|
| | assert any("fake-bank.com" in link for link in intel["phishing_links"])
|
| |
|
| | def test_e2e_multiple_intelligence_extraction(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test extraction of multiple intelligence types."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["with_multiple_intel"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | if data.get("scam_detected") and data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| |
|
| |
|
| | extraction_count = sum([
|
| | len(intel.get("upi_ids", [])) > 0,
|
| | len(intel.get("phone_numbers", [])) > 0,
|
| | len(intel.get("bank_accounts", [])) > 0,
|
| | len(intel.get("ifsc_codes", [])) > 0,
|
| | ])
|
| |
|
| |
|
| | assert extraction_count >= 2, f"Only extracted {extraction_count} types"
|
| |
|
| | def test_e2e_extraction_confidence_scoring(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test extraction confidence scoring."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["with_multiple_intel"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | if data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| | assert "extraction_confidence" in intel
|
| |
|
| |
|
| | assert 0.0 <= intel["extraction_confidence"] <= 1.0
|
| |
|
| |
|
| | if len(intel.get("upi_ids", [])) > 0 or len(intel.get("phone_numbers", [])) > 0:
|
| | assert intel["extraction_confidence"] > 0.0
|
| |
|
| | def test_e2e_hindi_devanagari_extraction(self, client: TestClient):
|
| | """E2E: Test Devanagari digit conversion in extraction (AC-3.3.1)."""
|
| |
|
| | hindi_message = "अपना पैसा ९८७६५४३२१०१२३ खाते में भेजें। IFSC कोड SBIN0001234 है।"
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": hindi_message, "language": "hi"},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| |
|
| | assert data["status"] == "success"
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2ESessionPersistence:
|
| | """
|
| | End-to-end tests for session persistence.
|
| |
|
| | Tests:
|
| | - Session state saved to Redis (AC-2.3.1)
|
| | - Session retrieval via API
|
| | - Session expiration handling
|
| | - Fallback when Redis unavailable
|
| | """
|
| |
|
| | def test_e2e_session_state_persistence(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test session state persists across API calls (AC-2.3.1)."""
|
| |
|
| | response1 = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| |
|
| | assert response1.status_code == 200
|
| | data1 = response1.json()
|
| | session_id = data1["session_id"]
|
| |
|
| |
|
| | response2 = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={
|
| | "message": "Tell me more about the prize!",
|
| | "session_id": session_id,
|
| | },
|
| | )
|
| |
|
| | assert response2.status_code == 200
|
| | data2 = response2.json()
|
| |
|
| |
|
| | assert data2["session_id"] == session_id
|
| |
|
| | def test_e2e_session_retrieval_via_api(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test session can be retrieved via GET endpoint."""
|
| |
|
| | engage_response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| |
|
| | assert engage_response.status_code == 200
|
| | session_id = engage_response.json()["session_id"]
|
| |
|
| |
|
| | get_response = client.get(f"/api/v1/honeypot/session/{session_id}")
|
| |
|
| |
|
| | assert get_response.status_code in [200, 404]
|
| |
|
| | if get_response.status_code == 200:
|
| | data = get_response.json()
|
| | assert data["session_id"] == session_id
|
| | assert "conversation_history" in data
|
| | assert "extracted_intelligence" in data
|
| |
|
| | def test_e2e_nonexistent_session_returns_404(self, client: TestClient):
|
| | """E2E: Test retrieving non-existent session returns 404."""
|
| | fake_session_id = str(uuid.uuid4())
|
| |
|
| | response = client.get(f"/api/v1/honeypot/session/{fake_session_id}")
|
| |
|
| | assert response.status_code == 404
|
| |
|
| | def test_e2e_invalid_session_id_format(self, client: TestClient):
|
| | """E2E: Test invalid session ID format returns 400."""
|
| | response = client.get("/api/v1/honeypot/session/invalid-not-uuid")
|
| |
|
| | assert response.status_code == 400
|
| | data = response.json()
|
| | assert "detail" in data
|
| | assert data["detail"]["code"] == "INVALID_SESSION_ID"
|
| |
|
| | def test_e2e_session_conversation_history_integrity(
|
| | self,
|
| | client: TestClient,
|
| | scam_messages: Dict[str, str],
|
| | ):
|
| | """E2E: Test conversation history maintains integrity."""
|
| | session_id = None
|
| | messages_sent = []
|
| |
|
| |
|
| | for i in range(2):
|
| | message = scam_messages["lottery_en"] if i == 0 else "How do I claim?"
|
| | messages_sent.append(message)
|
| |
|
| | request = {"message": message}
|
| | if session_id:
|
| | request["session_id"] = session_id
|
| |
|
| | response = client.post("/api/v1/honeypot/engage", json=request)
|
| | assert response.status_code == 200
|
| |
|
| | data = response.json()
|
| | if not session_id:
|
| | session_id = data["session_id"]
|
| |
|
| |
|
| | if "conversation_history" in data and data["conversation_history"]:
|
| | history = data["conversation_history"]
|
| |
|
| |
|
| | for j, entry in enumerate(history):
|
| | assert "turn" in entry
|
| | assert "sender" in entry
|
| | assert "message" in entry
|
| | assert entry["sender"] in ["scammer", "agent"]
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2ECompletePipeline:
|
| | """
|
| | End-to-end tests for the complete pipeline.
|
| |
|
| | Tests the full flow from message receipt to response.
|
| | """
|
| |
|
| | def test_e2e_complete_scam_pipeline(self, client: TestClient):
|
| | """E2E: Test complete scam detection → engagement → extraction pipeline."""
|
| |
|
| | scam_message = (
|
| | "Congratulations! You won 10 lakh rupees! "
|
| | "Send ₹500 processing fee to scammer@paytm or bank account 1234567890123 "
|
| | "(IFSC: HDFC0000456). Call +919876543210 for verification. "
|
| | "Visit http://fake-bank.com/claim for more details!"
|
| | )
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_message},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| |
|
| | assert data["status"] == "success"
|
| | assert data["scam_detected"] is True
|
| | assert data["confidence"] > 0.5
|
| |
|
| |
|
| | if data.get("engagement"):
|
| | assert data["engagement"]["agent_response"] is not None
|
| | assert len(data["engagement"]["agent_response"]) > 0
|
| |
|
| |
|
| | if data.get("extracted_intelligence"):
|
| | intel = data["extracted_intelligence"]
|
| |
|
| |
|
| | total_extracted = (
|
| | len(intel.get("upi_ids", [])) +
|
| | len(intel.get("bank_accounts", [])) +
|
| | len(intel.get("phone_numbers", [])) +
|
| | len(intel.get("phishing_links", []))
|
| | )
|
| |
|
| |
|
| |
|
| | assert total_extracted >= 0
|
| |
|
| |
|
| | if data.get("metadata"):
|
| | assert data["metadata"]["model_version"] == "1.0.0"
|
| |
|
| | def test_e2e_hindi_complete_pipeline(self, client: TestClient):
|
| | """E2E: Test complete pipeline with Hindi message."""
|
| | hindi_scam = (
|
| | "आप जीत गए हैं 10 लाख रुपये! "
|
| | "₹500 प्रोसेसिंग फीस भेजें scammer@paytm पर या कॉल करें +919876543210"
|
| | )
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": hindi_scam, "language": "hi"},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | assert data["status"] == "success"
|
| | assert data["language_detected"] in ["hi", "hinglish"]
|
| |
|
| | def test_e2e_batch_processing(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test batch processing endpoint."""
|
| | messages = [
|
| | {"id": "scam1", "message": scam_messages["lottery_en"]},
|
| | {"id": "legit1", "message": "Hello, how are you?"},
|
| | {"id": "scam2", "message": scam_messages["bank_fraud_en"]},
|
| | ]
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/batch",
|
| | json={"messages": messages},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| | assert data["status"] in ["success", "partial"]
|
| | assert data["processed"] + data["failed"] == 3
|
| | assert len(data["results"]) == 3
|
| |
|
| |
|
| | for result in data["results"]:
|
| | assert "id" in result
|
| | assert "status" in result
|
| | if result["status"] == "success":
|
| | assert "scam_detected" in result
|
| | assert "confidence" in result
|
| |
|
| | def test_e2e_health_check_integration(self, client: TestClient):
|
| | """E2E: Test health check with dependencies."""
|
| | response = client.get("/api/v1/health")
|
| |
|
| | assert response.status_code in [200, 503]
|
| | data = response.json()
|
| |
|
| | assert "status" in data
|
| | assert data["status"] in ["healthy", "degraded", "unhealthy"]
|
| | assert "version" in data
|
| | assert data["version"] == "1.0.0"
|
| |
|
| | if "dependencies" in data and data["dependencies"]:
|
| | deps = data["dependencies"]
|
| | assert "groq_api" in deps
|
| | assert "postgres" in deps
|
| | assert "redis" in deps
|
| | assert "models_loaded" in deps
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2EEdgeCases:
|
| | """
|
| | End-to-end tests for edge cases from FRD.md.
|
| | """
|
| |
|
| | def test_e2e_empty_message_rejected(self, client: TestClient):
|
| | """E2E: Test empty message is rejected."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": ""},
|
| | )
|
| |
|
| | assert response.status_code == 422
|
| |
|
| | def test_e2e_whitespace_only_message(self, client: TestClient):
|
| | """E2E: Test whitespace-only message handling."""
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": " "},
|
| | )
|
| |
|
| |
|
| |
|
| | assert response.status_code in [200, 422]
|
| |
|
| | if response.status_code == 200:
|
| | data = response.json()
|
| |
|
| | assert data["scam_detected"] is False
|
| |
|
| | def test_e2e_very_long_message(self, client: TestClient):
|
| | """E2E: Test message at maximum length (5000 chars)."""
|
| | max_length_message = "You won 10 lakh! " * 300
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": max_length_message[:5000]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| |
|
| | def test_e2e_message_exceeds_max_length(self, client: TestClient):
|
| | """E2E: Test message exceeding maximum length is rejected."""
|
| | too_long_message = "x" * 5001
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": too_long_message},
|
| | )
|
| |
|
| | assert response.status_code == 422
|
| |
|
| | def test_e2e_special_characters_handling(self, client: TestClient):
|
| | """E2E: Test handling of special characters."""
|
| | special_message = "You won ₹10 lakh! 🎉 Send OTP: <script>alert('xss')</script>"
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": special_message},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| |
|
| | assert data["status"] == "success"
|
| |
|
| |
|
| | if data.get("engagement") and data["engagement"].get("agent_response"):
|
| | assert "<script>" not in data["engagement"]["agent_response"]
|
| |
|
| | def test_e2e_sql_injection_safe(self, client: TestClient):
|
| | """E2E: Test SQL injection is handled safely."""
|
| | injection_message = "Hello'; DROP TABLE conversations;--"
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": injection_message},
|
| | )
|
| |
|
| |
|
| | assert response.status_code in [200, 400]
|
| |
|
| | def test_e2e_unicode_handling(self, client: TestClient):
|
| | """E2E: Test Unicode characters are handled correctly."""
|
| | unicode_message = "आप जीत गए हैं ₹10,00,000! 恭喜发财 🎊"
|
| |
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": unicode_message},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| | assert data["status"] == "success"
|
| |
|
| | def test_e2e_concurrent_sessions(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test handling of concurrent sessions."""
|
| | sessions = []
|
| |
|
| |
|
| | for i in range(3):
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| |
|
| | assert response.status_code == 200
|
| | sessions.append(response.json()["session_id"])
|
| |
|
| |
|
| | assert len(set(sessions)) == 3
|
| |
|
| |
|
| | for session_id in sessions:
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={
|
| | "message": "How do I claim?",
|
| | "session_id": session_id,
|
| | },
|
| | )
|
| | assert response.status_code == 200
|
| | assert response.json()["session_id"] == session_id
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class TestE2EPerformance:
|
| | """
|
| | End-to-end performance tests.
|
| | """
|
| |
|
| | def test_e2e_response_time_p95(self, client: TestClient, scam_messages: Dict[str, str]):
|
| | """E2E: Test p95 response time (AC-4.1.5: <2s)."""
|
| | response_times = []
|
| | num_requests = 5
|
| |
|
| | for _ in range(num_requests):
|
| | start = time.time()
|
| | response = client.post(
|
| | "/api/v1/honeypot/engage",
|
| | json={"message": scam_messages["lottery_en"]},
|
| | )
|
| | elapsed = time.time() - start
|
| | response_times.append(elapsed)
|
| | assert response.status_code == 200
|
| |
|
| |
|
| | sorted_times = sorted(response_times)
|
| | p95_index = int(len(sorted_times) * 0.95)
|
| | p95_time = sorted_times[min(p95_index, len(sorted_times) - 1)]
|
| |
|
| |
|
| | print(f"Response times: {response_times}")
|
| | print(f"P95 response time: {p95_time:.2f}s")
|
| |
|
| |
|
| | assert p95_time < 30.0, f"P95 response time {p95_time:.2f}s exceeds threshold"
|
| |
|
| | def test_e2e_batch_performance(self, client: TestClient):
|
| | """E2E: Test batch processing performance."""
|
| | messages = [
|
| | {"id": f"msg{i}", "message": f"Test message {i}"}
|
| | for i in range(10)
|
| | ]
|
| |
|
| | start = time.time()
|
| | response = client.post(
|
| | "/api/v1/honeypot/batch",
|
| | json={"messages": messages},
|
| | )
|
| | elapsed = time.time() - start
|
| |
|
| | assert response.status_code == 200
|
| | data = response.json()
|
| |
|
| |
|
| | assert data["processed"] + data["failed"] == 10
|
| |
|
| |
|
| | print(f"Batch of 10 processed in {elapsed:.2f}s")
|
| | print(f"Processing time from API: {data['processing_time_ms']}ms")
|
| |
|