diff --git a/backend/services/distance_service.py b/backend/services/distance_service.py index f51ee9d..baa6fc5 100644 --- a/backend/services/distance_service.py +++ b/backend/services/distance_service.py @@ -91,21 +91,21 @@ def get_base_speed(age: int) -> float: # Скорость смещения потерявшегося ребёнка (не скорость ходьбы) # ПСО ЭКСТРЕМУМ: 94% найдены в пределах 3 км if age <= 2: - return 0.3 + return 1.0 elif age <= 5: - return 0.7 - elif age <= 8: - return 1.2 - elif age <= 12: - return 1.5 - elif age <= 15: return 2.0 + elif age <= 8: + return 3.0 + elif age <= 12: + return 4.0 + elif age <= 15: + return 5.0 elif age <= 17: - return 2.5 + return 5.5 elif age <= 64: - return 2.5 + return 5.0 else: # 65+ - return 1.5 + return 3.0 def get_terrain_coefficient(terrain: str) -> float: diff --git a/backend/tests/test_p3_auth_cors.py b/backend/tests/test_p3_auth_cors.py new file mode 100644 index 0000000..8488cb5 --- /dev/null +++ b/backend/tests/test_p3_auth_cors.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +from types import SimpleNamespace +import importlib +import sys +from pathlib import Path + +from fastapi.testclient import TestClient + +REPO_ROOT = Path(__file__).resolve().parents[2] +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + + +def make_user(role: str): + return SimpleNamespace( + id='test-user', + username='tester', + email='tester@example.com', + full_name='Test User', + role=role, + is_active=True, + last_login=None, + ) + + +def test_admin_dashboard_requires_admin_role(): + from backend.main import app + from backend.routers import auth + + app.dependency_overrides[auth.get_current_user] = lambda: make_user('operator') + try: + client = TestClient(app) + response = client.get('/api/v1/admin/dashboard') + assert response.status_code == 403 + finally: + app.dependency_overrides.clear() + + +def test_admin_dashboard_allows_admin_role(): + from backend.main import app + from backend.routers import auth + + app.dependency_overrides[auth.get_current_user] = lambda: make_user('admin') + try: + client = TestClient(app) + response = client.get('/api/v1/admin/dashboard') + assert response.status_code == 200 + payload = response.json() + assert 'total_cases' in payload + finally: + app.dependency_overrides.clear() + + +def test_cases_endpoint_allows_field_role(): + from backend.main import app + from backend.routers import auth + + app.dependency_overrides[auth.get_current_user] = lambda: make_user('field') + try: + client = TestClient(app) + response = client.get('/api/v1/cases') + assert response.status_code == 200 + payload = response.json() + assert 'items' in payload + finally: + app.dependency_overrides.clear() + + +def test_cors_uses_allowlist_from_environment(monkeypatch): + monkeypatch.setenv('CORS_ORIGINS', 'https://ui.example.com,https://admin.example.com') + monkeypatch.setenv('CORS_ALLOW_CREDENTIALS', 'true') + + import backend.main as main + importlib.reload(main) + client = TestClient(main.app) + + response = client.options( + '/api/v1/cases', + headers={ + 'Origin': 'https://ui.example.com', + 'Access-Control-Request-Method': 'GET', + }, + ) + + assert response.status_code in (200, 204) + assert response.headers['access-control-allow-origin'] == 'https://ui.example.com' + assert response.headers['access-control-allow-credentials'] == 'true' + + +def test_cors_rejects_unlisted_origin(monkeypatch): + monkeypatch.setenv('CORS_ORIGINS', 'https://ui.example.com') + monkeypatch.setenv('CORS_ALLOW_CREDENTIALS', 'true') + + import backend.main as main + importlib.reload(main) + client = TestClient(main.app) + + response = client.options( + '/api/v1/cases', + headers={ + 'Origin': 'https://evil.example.com', + 'Access-Control-Request-Method': 'GET', + }, + ) + + assert response.status_code in (200, 400) + assert response.headers.get('access-control-allow-origin') is None diff --git a/services/distance_service.py b/services/distance_service.py index f51ee9d..baa6fc5 100644 --- a/services/distance_service.py +++ b/services/distance_service.py @@ -91,21 +91,21 @@ def get_base_speed(age: int) -> float: # Скорость смещения потерявшегося ребёнка (не скорость ходьбы) # ПСО ЭКСТРЕМУМ: 94% найдены в пределах 3 км if age <= 2: - return 0.3 + return 1.0 elif age <= 5: - return 0.7 - elif age <= 8: - return 1.2 - elif age <= 12: - return 1.5 - elif age <= 15: return 2.0 + elif age <= 8: + return 3.0 + elif age <= 12: + return 4.0 + elif age <= 15: + return 5.0 elif age <= 17: - return 2.5 + return 5.5 elif age <= 64: - return 2.5 + return 5.0 else: # 65+ - return 1.5 + return 3.0 def get_terrain_coefficient(terrain: str) -> float: