""" ML Service Tests Tests for machine learning analytics endpoints. Covers pattern detection, anomaly detection, and behavioral analysis. """ import pytest from typing import List, Dict, Any class TestMLServiceHealth: """Tests for ML service health and basic functionality.""" def test_ml_health_check(self, ml_client): """Test ML service health check endpoint.""" if not ml_client: pytest.skip("ML client not available") response = ml_client.get("/health") assert response.status_code == 200 data = response.json() assert data["status"] == "healthy" assert "ml_available" in data def test_ml_root_endpoint(self, ml_client): """Test ML service root endpoint.""" if not ml_client: pytest.skip("ML client not available") response = ml_client.get("/") assert response.status_code == 200 data = response.json() assert data["name"] == "Tikker ML Service" assert "endpoints" in data class TestPatternDetection: """Tests for keystroke pattern detection.""" @staticmethod def _create_keystroke_events(count: int = 100, wpm: float = 50) -> List[Dict]: """Create mock keystroke events.""" events = [] interval = int((60000 / (wpm * 5))) for i in range(count): events.append({ "timestamp": i * interval, "key_code": 65 + (i % 26), "event_type": "press" }) return events def test_detect_fast_typing_pattern(self, ml_client): """Test detection of fast typing pattern.""" if not ml_client: pytest.skip("ML client not available") fast_events = self._create_keystroke_events(count=150, wpm=80) payload = { "events": fast_events, "user_id": "test_user" } response = ml_client.post("/patterns/detect", json=payload) if response.status_code == 200: data = response.json() assert isinstance(data, list) pattern_names = [p["name"] for p in data] assert any("fast" in name for name in pattern_names) def test_detect_slow_typing_pattern(self, ml_client): """Test detection of slow typing pattern.""" if not ml_client: pytest.skip("ML client not available") slow_events = self._create_keystroke_events(count=50, wpm=20) payload = { "events": slow_events, "user_id": "test_user" } response = ml_client.post("/patterns/detect", json=payload) if response.status_code == 200: data = response.json() pattern_names = [p["name"] for p in data] assert any("slow" in name for name in pattern_names) def test_pattern_detection_empty_events(self, ml_client): """Test pattern detection with empty events.""" if not ml_client: pytest.skip("ML client not available") payload = { "events": [], "user_id": "test_user" } response = ml_client.post("/patterns/detect", json=payload) assert response.status_code == 400 class TestAnomalyDetection: """Tests for keystroke anomaly detection.""" @staticmethod def _create_keystroke_events(count: int = 100, wpm: float = 50) -> List[Dict]: """Create mock keystroke events.""" events = [] interval = int((60000 / (wpm * 5))) for i in range(count): events.append({ "timestamp": i * interval, "key_code": 65 + (i % 26), "event_type": "press" }) return events def test_detect_typing_speed_anomaly(self, ml_client): """Test detection of typing speed anomaly.""" if not ml_client: pytest.skip("ML client not available") normal_events = self._create_keystroke_events(count=100, wpm=50) payload = { "events": normal_events, "user_id": "test_user_anom" } response = ml_client.post("/anomalies/detect", json=payload) if response.status_code == 200: data = response.json() assert isinstance(data, list) def test_anomaly_detection_empty_events(self, ml_client): """Test anomaly detection with empty events.""" if not ml_client: pytest.skip("ML client not available") payload = { "events": [], "user_id": "test_user" } response = ml_client.post("/anomalies/detect", json=payload) assert response.status_code == 400 class TestBehavioralProfile: """Tests for behavioral profile building.""" @staticmethod def _create_keystroke_events(count: int = 200) -> List[Dict]: """Create mock keystroke events.""" events = [] for i in range(count): events.append({ "timestamp": i * 100, "key_code": 65 + (i % 26), "event_type": "press" }) return events def test_build_behavioral_profile(self, ml_client): """Test building behavioral profile from events.""" if not ml_client: pytest.skip("ML client not available") events = self._create_keystroke_events(count=200) payload = { "events": events, "user_id": "profile_test_user" } response = ml_client.post("/profile/build", json=payload) if response.status_code == 200: data = response.json() assert "user_id" in data assert "avg_typing_speed" in data assert "peak_hours" in data assert "common_words" in data assert "consistency_score" in data assert "patterns" in data assert data["user_id"] == "profile_test_user" assert data["consistency_score"] >= 0 assert data["consistency_score"] <= 1 def test_profile_empty_events(self, ml_client): """Test profile building with empty events.""" if not ml_client: pytest.skip("ML client not available") payload = { "events": [], "user_id": "test_user" } response = ml_client.post("/profile/build", json=payload) assert response.status_code == 400 class TestAuthenticityCheck: """Tests for user authenticity verification.""" @staticmethod def _create_keystroke_events(count: int = 100, wpm: float = 50) -> List[Dict]: """Create mock keystroke events.""" events = [] interval = int((60000 / (wpm * 5))) for i in range(count): events.append({ "timestamp": i * interval, "key_code": 65 + (i % 26), "event_type": "press" }) return events def test_authenticity_check_unknown_user(self, ml_client): """Test authenticity check for unknown user.""" if not ml_client: pytest.skip("ML client not available") events = self._create_keystroke_events(count=100) payload = { "events": events, "user_id": "unknown_user_123" } response = ml_client.post("/authenticity/check", json=payload) if response.status_code == 200: data = response.json() assert "authenticity_score" in data assert "verdict" in data assert data["verdict"] == "unknown" def test_authenticity_check_established_user(self, ml_client): """Test authenticity check for user with established profile.""" if not ml_client: pytest.skip("ML client not available") user_id = "established_user_test" events = self._create_keystroke_events(count=100, wpm=50) build_payload = { "events": events, "user_id": user_id } build_response = ml_client.post("/profile/build", json=build_payload) if build_response.status_code == 200: check_payload = { "events": events, "user_id": user_id } check_response = ml_client.post("/authenticity/check", json=check_payload) if check_response.status_code == 200: data = check_response.json() assert "authenticity_score" in data assert "verdict" in data class TestTemporalAnalysis: """Tests for temporal pattern analysis.""" def test_temporal_analysis_default_range(self, ml_client): """Test temporal analysis with default date range.""" if not ml_client: pytest.skip("ML client not available") payload = {"date_range_days": 7} response = ml_client.post("/temporal/analyze", json=payload) if response.status_code == 200: data = response.json() assert "trend" in data assert "date_range_days" in data or "error" in data if "date_range_days" in data: assert data["date_range_days"] == 7 def test_temporal_analysis_custom_range(self, ml_client): """Test temporal analysis with custom date range.""" if not ml_client: pytest.skip("ML client not available") payload = {"date_range_days": 30} response = ml_client.post("/temporal/analyze", json=payload) if response.status_code == 200: data = response.json() assert "date_range_days" in data or "error" in data if "date_range_days" in data: assert data["date_range_days"] == 30 class TestModelTraining: """Tests for ML model training.""" def test_train_model_default(self, ml_client): """Test training ML model with default parameters.""" if not ml_client: pytest.skip("ML client not available") response = ml_client.post("/model/train") if response.status_code == 200: data = response.json() assert data["status"] == "trained" assert "samples" in data assert "features" in data assert "accuracy" in data def test_train_model_custom_size(self, ml_client): """Test training ML model with custom sample size.""" if not ml_client: pytest.skip("ML client not available") response = ml_client.post("/model/train?sample_size=500") if response.status_code == 200: data = response.json() assert data["samples"] == 500 class TestBehaviorPrediction: """Tests for behavior prediction.""" @staticmethod def _create_keystroke_events(count: int = 100) -> List[Dict]: """Create mock keystroke events.""" events = [] for i in range(count): events.append({ "timestamp": i * 100, "key_code": 65 + (i % 26), "event_type": "press" }) return events def test_predict_behavior_untrained_model(self, ml_client): """Test behavior prediction with untrained model.""" if not ml_client: pytest.skip("ML client not available") events = self._create_keystroke_events(count=100) payload = { "events": events, "user_id": "test_user" } response = ml_client.post("/behavior/predict", json=payload) if response.status_code == 200: data = response.json() assert "behavior_category" in data or "status" in data def test_predict_behavior_after_training(self, ml_client): """Test behavior prediction after model training.""" if not ml_client: pytest.skip("ML client not available") train_response = ml_client.post("/model/train?sample_size=100") if train_response.status_code == 200: events = self._create_keystroke_events(count=100) payload = { "events": events, "user_id": "test_user" } predict_response = ml_client.post("/behavior/predict", json=payload) if predict_response.status_code == 200: data = predict_response.json() assert "behavior_category" in data assert "confidence" in data @pytest.fixture def ml_client(): """Create ML service test client.""" from fastapi.testclient import TestClient try: from ml_service import app return TestClient(app) except: return None