import base64 import json import os import time from unittest.mock import MagicMock, patch import pytest from fastapi import HTTPException def _access_jwt(sub: str = "u1") -> str: secret = os.environ.get("JWT_SECRET", "") if secret: import jwt as pyjwt return pyjwt.encode( {"exp": int(time.time()) + 3600, "sub": sub}, secret, algorithm="HS256", ) raw = json.dumps( {"exp": int(time.time()) + 3600, "sub": sub}, separators=(",", ":") ).encode() payload = base64.urlsafe_b64encode(raw).decode().rstrip("=") return f"h.{payload}.s" def test_token_manager_decode_user_id_sub(): # Arrange from main import TokenManager token = _access_jwt("user-abc") # Act uid = TokenManager.decode_user_id(token) # Assert assert uid == "user-abc" def test_token_manager_decode_user_id_invalid(): # Arrange from main import TokenManager # Act uid = TokenManager.decode_user_id("not-a-jwt") # Assert assert uid is None def test_merged_annotation_settings_pascal_case(): # Arrange from main import _merged_annotation_settings_payload raw = { "FramePeriodRecognition": 5, "ProbabilityThreshold": 0.4, "Altitude": 300, "FocalLength": 35, "SensorWidth": 36, } # Act out = _merged_annotation_settings_payload(raw) # Assert assert out["frame_period_recognition"] == 5 assert out["probability_threshold"] == 0.4 assert out["camera_config"]["current_height"] == 300 assert out["camera_config"]["focal_length"] == 35 assert out["camera_config"]["sensor_width"] == 36 def test_merged_annotation_nested_sections(): # Arrange from main import _merged_annotation_settings_payload raw = { "aiRecognitionSettings": {"modelBatchSize": 4}, "cameraSettings": {"altitude": 100}, } # Act out = _merged_annotation_settings_payload(raw) # Assert assert out["model_batch_size"] == 4 assert out["camera_config"]["current_height"] == 100 def test_resolve_media_for_detect_uses_api_path_and_defaults_when_api_empty(): # Arrange import main tm = main.TokenManager(_access_jwt(), "") mock_ann = MagicMock() mock_ann.fetch_user_ai_settings.return_value = None mock_ann.fetch_media_path.return_value = "/m/file.jpg" with patch("main.annotations_client", mock_ann): # Act cfg, path = main._resolve_media_for_detect("mid-1", tm, None) # Assert assert path == "/m/file.jpg" assert "paths" not in cfg assert "probability_threshold" not in cfg def test_resolve_media_for_detect_override_wins(): # Arrange import main tm = main.TokenManager(_access_jwt(), "") override = main.AIConfigDto(probability_threshold=0.99) mock_ann = MagicMock() mock_ann.fetch_user_ai_settings.return_value = { "probabilityThreshold": 0.2, "camera_config": {"current_height": 500}, } mock_ann.fetch_media_path.return_value = "/m/v.mp4" with patch("main.annotations_client", mock_ann): # Act cfg, path = main._resolve_media_for_detect("vid-1", tm, override) # Assert assert cfg["probability_threshold"] == 0.99 assert cfg["camera_config"]["current_height"] == 500 assert path == "/m/v.mp4" assert "paths" not in cfg def test_resolve_media_for_detect_merges_camera_config_override(): # Arrange import main tm = main.TokenManager(_access_jwt(), "") override = main.AIConfigDto( camera_config=main.CameraConfigDto(current_height=500) ) mock_ann = MagicMock() mock_ann.fetch_user_ai_settings.return_value = { "camera_config": { "focal_length": 35, "sensor_width": 36, "current_zoom": 2, "current_angle": 80, "current_height": 300, } } mock_ann.fetch_media_path.return_value = "/m/v.mp4" with patch("main.annotations_client", mock_ann): # Act cfg, path = main._resolve_media_for_detect("vid-1", tm, override) # Assert assert cfg["camera_config"]["current_height"] == 500 assert cfg["camera_config"]["focal_length"] == 35 assert cfg["camera_config"]["sensor_width"] == 36 assert cfg["camera_config"]["current_zoom"] == 2 assert cfg["camera_config"]["current_angle"] == 80 assert path == "/m/v.mp4" def test_resolve_media_for_detect_omits_altitude_when_not_provided(): # Arrange import main tm = main.TokenManager(_access_jwt(), "") mock_ann = MagicMock() mock_ann.fetch_user_ai_settings.return_value = {"probabilityThreshold": 0.2} mock_ann.fetch_media_path.return_value = "/m/v.mp4" with patch("main.annotations_client", mock_ann): # Act cfg, path = main._resolve_media_for_detect("vid-2", tm, None) # Assert assert "camera_config" not in cfg assert cfg["probability_threshold"] == 0.2 assert path == "/m/v.mp4" def test_resolve_media_for_detect_raises_when_no_media_path(): # Arrange import main tm = main.TokenManager(_access_jwt(), "") mock_ann = MagicMock() mock_ann.fetch_user_ai_settings.return_value = {} mock_ann.fetch_media_path.return_value = None with patch("main.annotations_client", mock_ann): # Act / Assert with pytest.raises(HTTPException) as exc: main._resolve_media_for_detect("missing", tm, None) assert exc.value.status_code == 503