From 692939cd0ec477ed1e045a1e1bcacf2684717452 Mon Sep 17 00:00:00 2001 From: xerexoded Date: Sun, 9 Jun 2024 22:49:24 +0530 Subject: [PATCH] added circle CI config with basic unit tests --- .circleci/config.yml | 38 +++++++++++++++++++ pyproject.toml | 6 ++- pytest.ini | 6 +++ tests/conftest.py | 5 +++ tests/unit/__init__.py | 0 tests/unit/test_auth.py | 73 ++++++++++++++++++++++++++++++++++++ tests/unit/test_cache.py | 80 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 pytest.ini create mode 100644 tests/conftest.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_auth.py create mode 100644 tests/unit/test_cache.py diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..3a8dd96 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ +version: 2.1 + +jobs: + setup: + docker: + - image: circleci/python:3.10 + steps: + - checkout + - run: + name: Install dependencies + command: | + python -m pip install --upgrade pip + pip install -e . + pip install pytest pytest-mock + + test: + docker: + - image: circleci/python:3.10 + steps: + - checkout + - run: + name: Install dependencies + command: | + python -m pip install --upgrade pip + pip install -e . + pip install pytest pytest-mock + - run: + name: Run tests + command: pytest + +workflows: + version: 2 + test_workflow: + jobs: + - setup + - test: + requires: + - setup \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 21eb793..8e2cc2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,9 @@ dependencies = [ "tidalapi==0.7.6", "pyyaml~=6.0", "tqdm~=4.64", - "sqlalchemy~=2.0" + "sqlalchemy~=2.0", + "pytest~=7.0", + "pytest-mock~=3.8" ] [tools.setuptools.packages."spotify_to_tidal"] @@ -20,4 +22,4 @@ where = "src" include = "spotify_to_tidal*" [project.scripts] -spotify_to_tidal = "spotify_to_tidal.__main__:main" +spotify_to_tidal = "spotify_to_tidal.__main__:main" \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..f462757 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +addopts = --maxfail=1 --disable-warnings +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9464070 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,5 @@ +import sys +import os + +# Add the src directory to the Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) \ No newline at end of file diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py new file mode 100644 index 0000000..c3b956f --- /dev/null +++ b/tests/unit/test_auth.py @@ -0,0 +1,73 @@ +# tests/unit/test_auth.py + +import pytest +import spotipy +import tidalapi +import yaml +import sys +from unittest import mock +from spotify_to_tidal.auth import open_spotify_session, open_tidal_session + + +def test_open_spotify_session(mocker): + # Mock the SpotifyOAuth class + mock_spotify_oauth = mocker.patch( + "spotify_to_tidal.auth.spotipy.SpotifyOAuth", autospec=True + ) + mock_spotify_instance = mocker.patch( + "spotify_to_tidal.auth.spotipy.Spotify", autospec=True + ) + + # Define a mock configuration + mock_config = { + "username": "test_user", + "client_id": "test_client_id", + "client_secret": "test_client_secret", + "redirect_uri": "http://localhost/", + } + + # Create a mock SpotifyOAuth instance + mock_oauth_instance = mock_spotify_oauth.return_value + mock_oauth_instance.get_access_token.return_value = "mock_access_token" + + # Call the function under test + spotify_instance = open_spotify_session(mock_config) + + # Assert that the SpotifyOAuth was called with correct parameters + mock_spotify_oauth.assert_called_once_with( + username="test_user", + scope="playlist-read-private", + client_id="test_client_id", + client_secret="test_client_secret", + redirect_uri="http://localhost/", + requests_timeout=2, + ) + + # Assert that the Spotify instance was created + mock_spotify_instance.assert_called_once_with(oauth_manager=mock_oauth_instance) + assert spotify_instance == mock_spotify_instance.return_value + + +def test_open_spotify_session_oauth_error(mocker): + # Mock the SpotifyOAuth class and simulate an OAuth error + mock_spotify_oauth = mocker.patch( + "spotify_to_tidal.auth.spotipy.SpotifyOAuth", autospec=True + ) + mock_spotify_oauth.return_value.get_access_token.side_effect = ( + spotipy.SpotifyOauthError("mock error") + ) + + # Define a mock configuration + mock_config = { + "username": "test_user", + "client_id": "test_client_id", + "client_secret": "test_client_secret", + "redirect_uri": "http://localhost/", + } + + # Mock sys.exit to prevent the test from exiting + mock_sys_exit = mocker.patch("sys.exit") + + # Call the function under test and assert sys.exit is called + open_spotify_session(mock_config) + mock_sys_exit.assert_called_once() \ No newline at end of file diff --git a/tests/unit/test_cache.py b/tests/unit/test_cache.py new file mode 100644 index 0000000..7aa0e15 --- /dev/null +++ b/tests/unit/test_cache.py @@ -0,0 +1,80 @@ +# tests/unit/test_cache.py + +import pytest +import datetime +import sqlalchemy +from sqlalchemy import create_engine, select +from unittest import mock +from spotify_to_tidal.cache import MatchFailureDatabase, TrackMatchCache + + +# Setup an in-memory SQLite database for testing +@pytest.fixture +def in_memory_db(): + engine = create_engine("sqlite:///:memory:") + return engine + + +# Test MatchFailureDatabase +def test_cache_match_failure(in_memory_db, mocker): + mocker.patch( + "spotify_to_tidal.cache.sqlalchemy.create_engine", return_value=in_memory_db + ) + failure_db = MatchFailureDatabase() + + track_id = "test_track" + failure_db.cache_match_failure(track_id) + + with failure_db.engine.connect() as connection: + result = connection.execute( + select(failure_db.match_failures).where( + failure_db.match_failures.c.track_id == track_id + ) + ).fetchone() + assert result is not None + assert result.track_id == track_id + + +def test_has_match_failure(in_memory_db, mocker): + mocker.patch( + "spotify_to_tidal.cache.sqlalchemy.create_engine", return_value=in_memory_db + ) + failure_db = MatchFailureDatabase() + + track_id = "test_track" + failure_db.cache_match_failure(track_id) + + assert failure_db.has_match_failure(track_id) is True + + +def test_remove_match_failure(in_memory_db, mocker): + mocker.patch( + "spotify_to_tidal.cache.sqlalchemy.create_engine", return_value=in_memory_db + ) + failure_db = MatchFailureDatabase() + + track_id = "test_track" + failure_db.cache_match_failure(track_id) + failure_db.remove_match_failure(track_id) + + with failure_db.engine.connect() as connection: + result = connection.execute( + select(failure_db.match_failures).where( + failure_db.match_failures.c.track_id == track_id + ) + ).fetchone() + assert result is None + + +# Test TrackMatchCache +def test_track_match_cache_insert(): + track_cache = TrackMatchCache() + track_cache.insert(("spotify_id", 123)) + assert track_cache.get("spotify_id") == 123 + + +def test_track_match_cache_get(): + track_cache = TrackMatchCache() + track_cache.insert(("spotify_id", 123)) + assert track_cache.get("spotify_id") == 123 + assert track_cache.get("nonexistent_id") is None \ No newline at end of file