Spaces:
Running
Running
| from __future__ import annotations | |
| import pytest | |
| from pydantic import ValidationError | |
| from spotify_mcp_server.tools.common import SpotifyReferenceInput, SpotifyType | |
| from spotify_mcp_server.tools.models import ( | |
| GetItemInput, | |
| LibraryAction, | |
| ListeningActivityInput, | |
| PlayerAction, | |
| PlayerStatusInput, | |
| PlaylistAction, | |
| PlaylistReadRequest, | |
| SearchCatalogInput, | |
| ) | |
| def test_reference_normalization(supplied, expected_type, expected_id) -> None: | |
| result = supplied.normalized() | |
| assert result.type == expected_type | |
| assert result.id == expected_id | |
| assert result.uri == f"spotify:{expected_type}:{expected_id}" | |
| def test_reference_rejects_ambiguous_or_invalid_values(supplied) -> None: | |
| with pytest.raises(ValueError): | |
| supplied.normalized() | |
| def test_reference_rejects_disallowed_type() -> None: | |
| with pytest.raises(ValueError, match="must be one of"): | |
| SpotifyReferenceInput(value="spotify:track:abc").normalized(allowed={SpotifyType.ALBUM}) | |
| def test_strict_search_contract_and_duplicate_types() -> None: | |
| with pytest.raises(ValidationError, match="extra_forbidden"): | |
| SearchCatalogInput(query="ambient", types=["track"], typo=True) | |
| with pytest.raises(ValidationError, match="duplicates"): | |
| SearchCatalogInput(query="ambient", types=["track", "track"]) | |
| with pytest.raises(ValidationError): | |
| SearchCatalogInput(query="ambient", types=["track"], limit=11) | |
| def test_action_specific_validation() -> None: | |
| with pytest.raises(ValidationError, match="position_ms"): | |
| PlayerAction(action="seek") | |
| with pytest.raises(ValidationError, match="not both"): | |
| PlayerAction(action="start_playback", context_uri="spotify:album:a", uris=["x"]) | |
| with pytest.raises(ValidationError, match="one offset"): | |
| PlayerAction(action="start_playback", offset_uri="x", offset_position=1) | |
| assert PlayerAction(action="pause_playback", context_uri="x", uris=["y"]) | |
| with pytest.raises(ValidationError, match="name is required"): | |
| PlaylistAction(action="create_playlist") | |
| with pytest.raises(ValidationError, match="playlist is required"): | |
| PlaylistAction(action="add_items", uris=[]) | |
| with pytest.raises(ValidationError, match="range_start"): | |
| PlaylistAction( | |
| action="reorder_items", | |
| playlist=SpotifyReferenceInput(value="spotify:playlist:p"), | |
| range_start=1, | |
| ) | |
| with pytest.raises(ValidationError, match="at least one detail"): | |
| PlaylistAction( | |
| action="change_playlist_details", | |
| playlist=SpotifyReferenceInput(value="spotify:playlist:p"), | |
| ) | |
| with pytest.raises(ValidationError, match="must not be empty"): | |
| PlaylistAction( | |
| action="remove_items", | |
| playlist=SpotifyReferenceInput(value="spotify:playlist:p"), | |
| uris=[], | |
| ) | |
| def test_library_and_read_validation() -> None: | |
| with pytest.raises(ValidationError, match="does not support"): | |
| LibraryAction(action="follow", items=[SpotifyReferenceInput(value="spotify:track:t")]) | |
| with pytest.raises(ValidationError, match="required for get_playlists"): | |
| PlaylistReadRequest(operation="get_playlists") | |
| with pytest.raises(ValidationError, match="mutually exclusive"): | |
| ListeningActivityInput(before=1, after=2) | |
| def test_duplicate_read_sections_are_rejected() -> None: | |
| with pytest.raises(ValidationError, match="expansions"): | |
| GetItemInput( | |
| items=[SpotifyReferenceInput(value="spotify:album:a")], | |
| expansions=["album_tracks", "album_tracks"], | |
| ) | |
| with pytest.raises(ValidationError, match="include"): | |
| PlayerStatusInput(include=["devices", "devices"]) | |
| with pytest.raises(ValidationError, match="time_ranges"): | |
| ListeningActivityInput(time_ranges=["short_term", "short_term"]) | |