test_utils.py•8.53 kB
"""
Tests for the utils module.
"""
import logging
from unittest.mock import MagicMock
import pytest
from smithsonian_mcp.utils import mask_api_key, resolve_museum_code
def test_mask_api_key():
    """Test that the API key is masked."""
    params = {"api_key": "12345"}
    masked_params = mask_api_key(params)
    assert masked_params["api_key"] == "****"
def test_mask_api_key_no_key():
    """Test that the params are not changed if there is no API key."""
    params = {"foo": "bar"}
    masked_params = mask_api_key(params)
    assert masked_params == params
def test_resolve_museum_code_exact_match():
    """Test exact matches from MUSEUM_MAP."""
    assert resolve_museum_code("asian art") == "FSG"
    assert resolve_museum_code("american art") == "SAAM"
    assert resolve_museum_code("natural history") == "NMNH"
def test_resolve_museum_code_case_insensitive():
    """Test case insensitive matching."""
    assert resolve_museum_code("ASIAN ART") == "FSG"
    assert resolve_museum_code("Asian Art") == "FSG"
def test_resolve_museum_code_with_prefixes():
    """Test matching with common prefixes removed."""
    assert resolve_museum_code("Smithsonian Asian Art Museum") == "FSG"
    assert resolve_museum_code("National Museum of Natural History") == "NMNH"
    assert resolve_museum_code("Smithsonian American Art Museum") == "SAAM"
def test_resolve_museum_code_partial_match():
    """Test partial matching when input contains map key."""
    assert resolve_museum_code("Asian Art Museum") == "FSG"
    assert resolve_museum_code("American Art Museum") == "SAAM"
def test_resolve_museum_code_direct_codes():
    """Test direct museum code matching."""
    assert resolve_museum_code("SAAM") == "SAAM"
    assert resolve_museum_code("FSG") == "FSG"
    assert resolve_museum_code("NMNH") == "NMNH"
def test_resolve_museum_code_word_overlap():
    """Test word-based matching for significant overlap."""
    assert resolve_museum_code("Hirshhorn Museum") == "HMSG"
    assert resolve_museum_code("Portrait Gallery") == "NPG"
def test_resolve_museum_code_no_match():
    """Test that invalid museum names return None."""
    assert resolve_museum_code("Invalid Museum") is None
    assert resolve_museum_code("") is None
    assert resolve_museum_code("   ") is None
def test_resolve_museum_code_edge_cases():
    """Test edge cases and variations."""
    # Should handle extra spaces
    assert resolve_museum_code("  asian art  ") == "FSG"
    # Should handle museum name variations
    assert resolve_museum_code("Freer and Sackler") == "FSG"
    assert resolve_museum_code("Cooper Hewitt") == "CHNDM"
def test_resolve_museum_code_expanded_map():
    """Test the expanded MUSEUM_MAP with full museum names."""
    # Full Smithsonian museum names
    assert resolve_museum_code("Smithsonian Asian Art Museum") == "FSG"
    assert resolve_museum_code("Smithsonian American Art Museum") == "SAAM"
    assert resolve_museum_code("Smithsonian Natural History Museum") == "NMNH"
    assert resolve_museum_code("National Museum of Asian Art") == "FSG"
    assert resolve_museum_code("Freer and Sackler Galleries") == "FSG"
    assert resolve_museum_code("Hirshhorn Museum and Sculpture Garden") == "HMSG"
    assert resolve_museum_code("National Museum of African American History and Culture") == "NMAAHC"
@pytest.mark.asyncio
async def test_construct_url_from_record_id():
    """Test URL construction from record_id."""
    from smithsonian_mcp.utils import construct_url_from_record_id
    from unittest.mock import patch, AsyncMock
    # Test with valid record_id - NMAH
    url = await construct_url_from_record_id("nmah_1448973")
    assert url == "https://americanhistory.si.edu/collections/object/nmah_1448973"
    # Test FSG (uses accession identifier)
    url = await construct_url_from_record_id("fsg_F1900.47")
    assert url == "https://asia.si.edu/object/F1900.47"
    # Test museums that construct URLs directly (record_ID and accession identifiers with plain base_url)
    test_cases = [
        ("nmaahc_2022.91.10ab", "https://nmaahc.si.edu/object/nmaahc_2022.91.10ab"),
        ("nmnhmineralsciences_17183750", "https://naturalhistory.si.edu/object/nmnhmineralsciences_17183750"),
        ("nmnhpaleobiology_17134484", "https://naturalhistory.si.edu/object/nmnhpaleobiology_17134484"),
        ("nmnhanthropology_8352715", "https://naturalhistory.si.edu/object/nmnhanthropology_8352715"),
        ("nmnheducation_10841904", "https://naturalhistory.si.edu/object/nmnheducation_10841904"),
        ("nmnhinvertebratezoology_14688577", "https://naturalhistory.si.edu/object/nmnhinvertebratezoology_14688577"),
        ("npg_NPG.2002.184", "https://npg.si.edu/object/npg_NPG.2002.184"),
        ("npm_0.293996.232", "https://postalmuseum.si.edu/object/npm_0.293996.232"),
        ("siris_arc_403511", "https://siarchives.si.edu/collections/siris_arc_403511"),
    ]
    for record_id, expected_url in test_cases:
        url = await construct_url_from_record_id(record_id)
        assert url == expected_url, f"Failed for {record_id}: expected {expected_url}, got {url}"
    # Test museums that require API data (guid, record_link, url, idsId, or template variables in base_url)
    api_required_cases = [
        "saam_30913",                    # uses {record_link} in base_url
        "nasm_nv913e903df",              # uses {record_link} in base_url
        "hmsg_66.1608",                  # uses url identifier
        "nmafa_ys7a3f230ba",             # uses {guid} in base_url
        "nmai_ws69d7d97b6",              # uses {record_link} in base_url
        "acm_dl8b7ab6959",               # uses {guid} in base_url
        "nzp_20190815_002RP",            # uses idsId identifier
        "chndm_33665",                   # uses {record_link} in base_url
        "nmnhbirds_352f6df2a",           # uses {guid} in base_url
        "nmnhbotany_32cbf4c79",          # uses {guid} in base_url
        "nmnhento_339e344dc",            # uses {guid} in base_url
        "nmnhfishes_3ccbe2c66",          # uses {guid} in base_url
        "nmnhherps_359523727",           # uses {guid} in base_url
        "nmnhmammals_30b523759",         # uses {guid} in base_url
    ]
    # Mock the API fallback to return None
    with patch('smithsonian_mcp.utils._get_url_from_api_record_id', return_value=None):
        for record_id in api_required_cases:
            url = await construct_url_from_record_id(record_id)
            # With mocked API returning None, these should return None
            assert url is None, f"Expected None for API-required museum {record_id}, got {url}"
    # Test with invalid record_id (no underscore)
    url = await construct_url_from_record_id("invalid")
    assert url is None
    # Test with empty record_id
    url = await construct_url_from_record_id("")
    assert url is None
    # Test with None
    url = await construct_url_from_record_id(None)
    assert url is None
    # Test unknown museum (should fall back to API, mocked to return None)
    with patch('smithsonian_mcp.utils._get_url_from_api_record_id', return_value=None):
        url = await construct_url_from_record_id("unknown_123")
        assert url is None
@pytest.mark.asyncio
async def test_bert_puppet_parsing():
    """Test parsing of bert puppet response to validate record_id extraction."""
    import json
    from smithsonian_mcp.api_client import SmithsonianAPIClient
    from smithsonian_mcp.api_client import create_client
    # Load the bert puppet response
    with open("tests/bert_puppet_response.json", "r") as f:
        response_data = json.load(f)
    # Parse the object
    client = SmithsonianAPIClient()
    obj = client._parse_object_data(response_data["response"])
    # Validate the parsed data
    assert obj.id == "ld1-1643398912743-1643398933001-0"
    assert obj.record_id == "nmah_1448973"
    assert obj.title == "Bert Puppet"
    assert obj.unit_code == "NMAH"
    # Test URL construction from record_id
    from smithsonian_mcp.utils import construct_url_from_record_id
    url = await construct_url_from_record_id(obj.record_id)
    assert url == "https://americanhistory.si.edu/collections/object/nmah_1448973"
    # Test museum website lookup
    client = await create_client()
    units = await client.get_units()
    nmah_unit = next((u for u in units if u.code == "NMAH"), None)
    assert nmah_unit is not None
    assert str(nmah_unit.website) == "https://americanhistory.si.edu/"
    await client.disconnect()