import pytest import os import json from unittest.mock import patch, MagicMock def test_api_feedback_unauthorized(client): """Test feedback endpoint requires session authentication or API key.""" response = client.post('/api/feedback', json={'image_path': 'test.jpg', 'label': 'morph'}) assert response.status_code == 401 def test_api_feedback_missing_payload(client): """Test feedback endpoint rejects missing parameters.""" with client.session_transaction() as sess: sess['logged_in'] = True sess['role'] = 'admin' response = client.post('/api/feedback', json={}) assert response.status_code == 400 assert 'Missing image_path or label' in response.get_json()['error'] def test_api_feedback_persistence_and_threshold_trigger(client, tmp_path): """Test feedback persists entries and triggers background training when reaching threshold.""" with client.session_transaction() as sess: sess['logged_in'] = True sess['role'] = 'admin' test_feedback_file = str(tmp_path / "test_feedback.json") # Mock FEEDBACK_FILE in training route with patch('src.routes.training.log_model_interaction'), \ patch('src.routes.training.Thread') as mock_thread, \ patch('os.path.exists', return_value=False), \ patch('builtins.open', create=True) as mock_open: # Test single feedback submission response = client.post( '/api/feedback', json={ 'image_path': 'static/uploads/sample_morph.jpg', 'label': 'morph', 'prediction': 'real' } ) assert response.status_code == 200 json_data = response.get_json() assert json_data['status'] == 'flagged' assert json_data['feedback_status'] == 'feedback_received' assert json_data['count'] == 1 def test_api_feedback_triggers_retrain_at_threshold(client): """Test background training thread is spawned when count reaches RETRAIN_FLAG_THRESHOLD (10).""" with client.session_transaction() as sess: sess['logged_in'] = True sess['role'] = 'admin' # Pre-populate 9 feedbacks existing_feedbacks = [ {'timestamp': '2026-08-30T00:00:00', 'image_path': f'img_{i}.jpg', 'user_label': 'morph'} for i in range(9) ] with patch('src.routes.training.log_model_interaction'), \ patch('os.path.exists', return_value=True), \ patch('builtins.open') as mock_file, \ patch('json.load', return_value=existing_feedbacks), \ patch('json.dump'), \ patch('src.routes.training.Thread') as mock_thread: mock_instance = MagicMock() mock_thread.return_value = mock_instance # 10th feedback submission reaches threshold response = client.post( '/api/feedback', json={ 'image_path': 'img_9.jpg', 'label': 'morph' } ) assert response.status_code == 200 json_data = response.get_json() assert json_data['count'] == 10 # Verify background daemon thread was started mock_thread.assert_called_once() assert mock_thread.call_args.kwargs['daemon'] is True mock_instance.start.assert_called_once()