Spaces:
Sleeping
Sleeping
Download tests/test_feedback_loop.py from juanquy/MorphGuard: direct link, hf CLI and curl.
- Browser
- Download file 3.31 kB
-
https://huggingface.co/spaces/juanquy/MorphGuard/resolve/main/tests/test_feedback_loop.py
- Command line
-
hf download hf://spaces/juanquy/MorphGuard/tests/test_feedback_loop.py
-
curl -L -o test_feedback_loop.py https://huggingface.co/spaces/juanquy/MorphGuard/resolve/main/tests/test_feedback_loop.py
3.31 kB
| 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() | |