"""Tests for exposure profile Pydantic models and endpoint logic.""" import json import uuid from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch import pytest from pydantic import ValidationError from services.symbol_registry.exposure import ( ExposureProfileCreate, ExposureProfileResponse, VALID_MARKET_POSITION_TIERS, VALID_SOURCES, _row_to_profile, ) # --- ExposureProfileCreate validation --- def test_create_defaults(): p = ExposureProfileCreate() assert p.geographic_revenue_mix == {} assert p.supply_chain_regions == [] assert p.key_input_commodities == [] assert p.regulatory_jurisdictions == [] assert p.market_position_tier == "regional" assert p.export_dependency_pct == 0.0 assert p.source == "manual" assert p.confidence == 1.0 def test_create_with_full_data(): p = ExposureProfileCreate( geographic_revenue_mix={"US": 0.6, "EU": 0.3, "CN": 0.1}, supply_chain_regions=["CN", "TW", "KR"], key_input_commodities=["lithium", "cobalt"], regulatory_jurisdictions=["US", "EU"], market_position_tier="global_leader", export_dependency_pct=0.45, source="manual", confidence=0.95, ) assert p.geographic_revenue_mix["US"] == 0.6 assert len(p.supply_chain_regions) == 3 assert p.market_position_tier == "global_leader" def test_create_all_valid_tiers(): for tier in VALID_MARKET_POSITION_TIERS: p = ExposureProfileCreate(market_position_tier=tier) assert p.market_position_tier == tier def test_create_rejects_invalid_tier(): with pytest.raises(ValidationError): ExposureProfileCreate(market_position_tier="mega_corp") def test_create_all_valid_sources(): for src in VALID_SOURCES: p = ExposureProfileCreate(source=src) assert p.source == src def test_create_rejects_invalid_source(): with pytest.raises(ValidationError): ExposureProfileCreate(source="guessed") def test_create_rejects_export_dependency_above_1(): with pytest.raises(ValidationError): ExposureProfileCreate(export_dependency_pct=1.5) def test_create_rejects_export_dependency_below_0(): with pytest.raises(ValidationError): ExposureProfileCreate(export_dependency_pct=-0.1) def test_create_rejects_confidence_above_1(): with pytest.raises(ValidationError): ExposureProfileCreate(confidence=1.1) def test_create_rejects_confidence_below_0(): with pytest.raises(ValidationError): ExposureProfileCreate(confidence=-0.5) # --- _row_to_profile helper --- def test_row_to_profile_converts_uuids(): """UUID fields should be converted to strings.""" uid = uuid.uuid4() now = datetime.now(timezone.utc) class FakeRecord(dict): pass row = FakeRecord( id=uid, company_id=uid, geographic_revenue_mix={"US": 0.5}, supply_chain_regions=["US"], key_input_commodities=[], regulatory_jurisdictions=[], market_position_tier="regional", export_dependency_pct=0.0, source="manual", confidence=1.0, version=1, active=True, created_at=now, updated_at=now, ) result = _row_to_profile(row) assert result["id"] == str(uid) assert result["company_id"] == str(uid) def test_row_to_profile_parses_json_string(): """geographic_revenue_mix stored as JSON string should be parsed.""" uid = uuid.uuid4() now = datetime.now(timezone.utc) class FakeRecord(dict): pass row = FakeRecord( id=uid, company_id=uid, geographic_revenue_mix='{"US": 0.7, "EU": 0.3}', supply_chain_regions=["US"], key_input_commodities=[], regulatory_jurisdictions=[], market_position_tier="regional", export_dependency_pct=0.0, source="manual", confidence=1.0, version=1, active=True, created_at=now, updated_at=now, ) result = _row_to_profile(row) assert result["geographic_revenue_mix"] == {"US": 0.7, "EU": 0.3} # --- ExposureProfileResponse model --- def test_response_model_accepts_valid_data(): now = datetime.now(timezone.utc) resp = ExposureProfileResponse( id=str(uuid.uuid4()), company_id=str(uuid.uuid4()), geographic_revenue_mix={"US": 0.5, "EU": 0.5}, supply_chain_regions=["CN"], key_input_commodities=["oil"], regulatory_jurisdictions=["US"], market_position_tier="multinational", export_dependency_pct=0.3, source="inferred", confidence=0.8, version=3, active=True, created_at=now, updated_at=now, ) assert resp.version == 3 assert resp.source == "inferred"