ytm-cli 0.7.1__tar.gz → 0.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {ytm_cli-0.7.1/ytm_cli.egg-info → ytm_cli-0.7.3}/PKG-INFO +34 -38
  2. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/README.md +26 -35
  3. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/pyproject.toml +19 -4
  4. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_config.py +6 -22
  5. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_dislikes.py +4 -2
  6. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_integration.py +2 -168
  7. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_main.py +13 -7
  8. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_playlists.py +8 -8
  9. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/__init__.py +1 -1
  10. ytm_cli-0.7.3/ytm_cli/config.py +49 -0
  11. ytm_cli-0.7.3/ytm_cli/demo.py +120 -0
  12. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/dislikes.py +12 -3
  13. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/ffmpeg_player.py +1 -1
  14. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/hybrid_player.py +1 -1
  15. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/llm_client.py +5 -3
  16. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/main.py +19 -5
  17. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/player.py +31 -11
  18. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/playlists.py +18 -9
  19. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/spectrum.py +1 -1
  20. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/ui.py +5 -5
  21. {ytm_cli-0.7.1 → ytm_cli-0.7.3/ytm_cli.egg-info}/PKG-INFO +34 -38
  22. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli.egg-info/SOURCES.txt +1 -1
  23. ytm_cli-0.7.1/ytm_cli/auth.py +0 -697
  24. ytm_cli-0.7.1/ytm_cli/config.py +0 -29
  25. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/LICENSE +0 -0
  26. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/setup.cfg +0 -0
  27. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_cache.py +0 -0
  28. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_ffmpeg_player.py +0 -0
  29. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_hybrid_player.py +0 -0
  30. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_lyrics_service.py +0 -0
  31. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_runner.py +0 -0
  32. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_spectrum.py +0 -0
  33. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_ui.py +0 -0
  34. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/tests/test_utils.py +0 -0
  35. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/__main__.py +0 -0
  36. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/cache.py +0 -0
  37. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/lyrics_service.py +0 -0
  38. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/utils.py +0 -0
  39. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli/verbose_logger.py +0 -0
  40. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli.egg-info/dependency_links.txt +0 -0
  41. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli.egg-info/entry_points.txt +0 -0
  42. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli.egg-info/requires.txt +0 -0
  43. {ytm_cli-0.7.1 → ytm_cli-0.7.3}/ytm_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytm-cli
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: YouTube Music CLI tool with interactive terminal interface
5
5
  Author-email: thieuluan <thieuluan.dark@gmail.com>
6
6
  License-Expression: MIT
@@ -11,11 +11,16 @@ Keywords: youtube-music,cli,terminal,music-player,mpv
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Intended Audience :: End Users/Desktop
14
- Classifier: Operating System :: OS Independent
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: MacOS
15
16
  Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
16
21
  Classifier: Programming Language :: Python :: 3.14
17
22
  Classifier: Topic :: Multimedia :: Sound/Audio :: Players
18
- Requires-Python: >=3.14
23
+ Requires-Python: >=3.10
19
24
  Description-Content-Type: text/markdown
20
25
  License-File: LICENSE
21
26
  Requires-Dist: ytmusicapi
@@ -32,9 +37,16 @@ Dynamic: license-file
32
37
 
33
38
  Stream music directly from YouTube Music in your terminal with intuitive controls, playlist management, and smart filtering.
34
39
 
35
- ![YTM CLI Player](image.png)
40
+ <p align="center">
41
+ <a href="https://github.com/thieuluan1618/ytm/raw/main/screenshots/player.webm">
42
+ <img src="screenshots/player.gif" alt="ytm-cli player demo" width="800">
43
+ </a>
44
+ </p>
36
45
 
37
- ![YTM CLI Lyrics](image-lyric.png)
46
+ <p align="center">
47
+ <em>Player UI with the real-time FFT spectrum visualizer (synthetic demo capture).
48
+ Click for the higher-quality .webm.</em>
49
+ </p>
38
50
 
39
51
  ## ✨ Features
40
52
 
@@ -45,21 +57,23 @@ Stream music directly from YouTube Music in your terminal with intuitive control
45
57
  - **👎 Smart Filtering**: Dislike songs to filter them from future results
46
58
  - **📜 Synced Lyrics**: Live-highlighted lyrics with auto-scroll (press `l`)
47
59
  - **🎯 Radio Mode**: Automatic playlist generation based on your selection
60
+ - **📊 Real-time Spectrum Visualizer**: 24-band FFT bars driven by an `ffmpeg` sidecar — bass left, treble right (stereo oscilloscope fallback when `ffmpeg` is unavailable)
48
61
  - **🤖 AI-Powered**: Natural language music requests and AI-generated playlists
49
62
 
50
63
  ## 🚀 Quick Start
51
64
 
52
65
  ### Requirements
53
66
 
54
- - Python 3.8+
67
+ - Python **3.10+** (set in `pyproject.toml`)
55
68
  - [mpv media player](https://mpv.io/installation/) (must be installed system-wide)
69
+ - [`ffmpeg`](https://ffmpeg.org/) on `PATH` for the real-time spectrum visualizer (optional — falls back to a stereo oscilloscope if missing)
56
70
 
57
71
  ### Installation
58
72
 
59
73
  **One command with [uv](https://docs.astral.sh/uv/) (recommended):**
60
74
 
61
75
  ```bash
62
- # Install from PyPI
76
+ # Install from PyPI — gets latest version automatically
63
77
  uvx ytm-cli "song name"
64
78
  ```
65
79
 
@@ -75,36 +89,16 @@ uv run ytm-cli "song name"
75
89
  </details>
76
90
 
77
91
  <details>
78
- <summary>Alternative: one-shot setup scripts</summary>
92
+ <summary>Alternative: one-shot setup script (Linux / macOS)</summary>
79
93
 
80
94
  ```bash
81
95
  git clone https://github.com/thieuluan1618/ytm.git
82
96
  cd ytm
83
- ```
84
-
85
- These create the virtual environment, install dependencies, and configure the `ytm` command globally.
86
-
87
- **Linux/macOS:**
88
-
89
- ```bash
90
97
  ./setup.sh
91
- source ~/.zshrc # or ~/.bashrc for bash
92
- ```
93
-
94
- **Windows PowerShell:**
95
-
96
- ```powershell
97
- .\setup.ps1
98
- . $PROFILE
99
- ```
100
-
101
- **Windows Command Prompt:**
102
-
103
- ```cmd
104
- setup.bat
98
+ source ~/.zshrc # or ~/.bashrc for bash
105
99
  ```
106
100
 
107
- After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
101
+ This creates the venv, installs runtime deps from `pyproject.toml`, and adds a `ytm` shell alias.
108
102
 
109
103
  </details>
110
104
 
@@ -114,14 +108,15 @@ After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
114
108
  ```bash
115
109
  git clone https://github.com/thieuluan1618/ytm.git
116
110
  cd ytm
117
- python -m venv venv
118
- source venv/bin/activate # Windows: venv\Scripts\activate
119
- pip install -r requirements.txt
120
- ./setup_alias.sh # or setup_alias.ps1 / setup_alias.bat
111
+ uv sync # installs runtime deps from pyproject.toml + uv.lock
112
+ ./setup_alias.sh # optional: registers the `ytm` alias
113
+ # Run with: uv run ytm-cli "song"
121
114
  ```
122
115
 
123
116
  </details>
124
117
 
118
+ > **Windows users:** native Windows is not supported (the codebase uses POSIX-only `termios`/`tty`/`fcntl`). Use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) and follow the Linux instructions above.
119
+
125
120
  ### Basic Usage
126
121
 
127
122
  **Interactive search:**
@@ -140,9 +135,10 @@ ytm-cli "your favorite song"
140
135
  **Non-interactive mode (automation/scripting):**
141
136
 
142
137
  ```bash
143
- ytm-cli search "song name" --select 1 # Auto-select first result
144
- ytm-cli search "song" -s 1 -v # With verbose output
145
- ytm-cli search "song" -s 1 -v --log-file debug.log # Save debug logs
138
+ ytm-cli search "song name" --select 1 # Auto-select first result
139
+ ytm-cli search "song" -s 1 --verbose # With verbose output
140
+ ytm-cli search "song" -s 1 --verbose --log-file debug.log # Save debug logs
141
+ ytm-cli -v # Print version
146
142
  ```
147
143
 
148
144
  > **Tip:** Set up the shell alias to use `ytm` as a shortcut for `ytm-cli`. See the setup scripts above.
@@ -249,7 +245,7 @@ Having issues? Check out the [**Troubleshooting Guide**](TROUBLESHOOTING.md) for
249
245
 
250
246
  ```bash
251
247
  # Enable verbose logging to see what's happening
252
- ytm-cli search "test" -s 1 -v --log-file debug.log
248
+ ytm-cli search "test" -s 1 --verbose --log-file debug.log
253
249
 
254
250
  # Check versions
255
251
  mpv --version
@@ -4,9 +4,16 @@
4
4
 
5
5
  Stream music directly from YouTube Music in your terminal with intuitive controls, playlist management, and smart filtering.
6
6
 
7
- ![YTM CLI Player](image.png)
7
+ <p align="center">
8
+ <a href="https://github.com/thieuluan1618/ytm/raw/main/screenshots/player.webm">
9
+ <img src="screenshots/player.gif" alt="ytm-cli player demo" width="800">
10
+ </a>
11
+ </p>
8
12
 
9
- ![YTM CLI Lyrics](image-lyric.png)
13
+ <p align="center">
14
+ <em>Player UI with the real-time FFT spectrum visualizer (synthetic demo capture).
15
+ Click for the higher-quality .webm.</em>
16
+ </p>
10
17
 
11
18
  ## ✨ Features
12
19
 
@@ -17,21 +24,23 @@ Stream music directly from YouTube Music in your terminal with intuitive control
17
24
  - **👎 Smart Filtering**: Dislike songs to filter them from future results
18
25
  - **📜 Synced Lyrics**: Live-highlighted lyrics with auto-scroll (press `l`)
19
26
  - **🎯 Radio Mode**: Automatic playlist generation based on your selection
27
+ - **📊 Real-time Spectrum Visualizer**: 24-band FFT bars driven by an `ffmpeg` sidecar — bass left, treble right (stereo oscilloscope fallback when `ffmpeg` is unavailable)
20
28
  - **🤖 AI-Powered**: Natural language music requests and AI-generated playlists
21
29
 
22
30
  ## 🚀 Quick Start
23
31
 
24
32
  ### Requirements
25
33
 
26
- - Python 3.8+
34
+ - Python **3.10+** (set in `pyproject.toml`)
27
35
  - [mpv media player](https://mpv.io/installation/) (must be installed system-wide)
36
+ - [`ffmpeg`](https://ffmpeg.org/) on `PATH` for the real-time spectrum visualizer (optional — falls back to a stereo oscilloscope if missing)
28
37
 
29
38
  ### Installation
30
39
 
31
40
  **One command with [uv](https://docs.astral.sh/uv/) (recommended):**
32
41
 
33
42
  ```bash
34
- # Install from PyPI
43
+ # Install from PyPI — gets latest version automatically
35
44
  uvx ytm-cli "song name"
36
45
  ```
37
46
 
@@ -47,36 +56,16 @@ uv run ytm-cli "song name"
47
56
  </details>
48
57
 
49
58
  <details>
50
- <summary>Alternative: one-shot setup scripts</summary>
59
+ <summary>Alternative: one-shot setup script (Linux / macOS)</summary>
51
60
 
52
61
  ```bash
53
62
  git clone https://github.com/thieuluan1618/ytm.git
54
63
  cd ytm
55
- ```
56
-
57
- These create the virtual environment, install dependencies, and configure the `ytm` command globally.
58
-
59
- **Linux/macOS:**
60
-
61
- ```bash
62
64
  ./setup.sh
63
- source ~/.zshrc # or ~/.bashrc for bash
64
- ```
65
-
66
- **Windows PowerShell:**
67
-
68
- ```powershell
69
- .\setup.ps1
70
- . $PROFILE
71
- ```
72
-
73
- **Windows Command Prompt:**
74
-
75
- ```cmd
76
- setup.bat
65
+ source ~/.zshrc # or ~/.bashrc for bash
77
66
  ```
78
67
 
79
- After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
68
+ This creates the venv, installs runtime deps from `pyproject.toml`, and adds a `ytm` shell alias.
80
69
 
81
70
  </details>
82
71
 
@@ -86,14 +75,15 @@ After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
86
75
  ```bash
87
76
  git clone https://github.com/thieuluan1618/ytm.git
88
77
  cd ytm
89
- python -m venv venv
90
- source venv/bin/activate # Windows: venv\Scripts\activate
91
- pip install -r requirements.txt
92
- ./setup_alias.sh # or setup_alias.ps1 / setup_alias.bat
78
+ uv sync # installs runtime deps from pyproject.toml + uv.lock
79
+ ./setup_alias.sh # optional: registers the `ytm` alias
80
+ # Run with: uv run ytm-cli "song"
93
81
  ```
94
82
 
95
83
  </details>
96
84
 
85
+ > **Windows users:** native Windows is not supported (the codebase uses POSIX-only `termios`/`tty`/`fcntl`). Use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) and follow the Linux instructions above.
86
+
97
87
  ### Basic Usage
98
88
 
99
89
  **Interactive search:**
@@ -112,9 +102,10 @@ ytm-cli "your favorite song"
112
102
  **Non-interactive mode (automation/scripting):**
113
103
 
114
104
  ```bash
115
- ytm-cli search "song name" --select 1 # Auto-select first result
116
- ytm-cli search "song" -s 1 -v # With verbose output
117
- ytm-cli search "song" -s 1 -v --log-file debug.log # Save debug logs
105
+ ytm-cli search "song name" --select 1 # Auto-select first result
106
+ ytm-cli search "song" -s 1 --verbose # With verbose output
107
+ ytm-cli search "song" -s 1 --verbose --log-file debug.log # Save debug logs
108
+ ytm-cli -v # Print version
118
109
  ```
119
110
 
120
111
  > **Tip:** Set up the shell alias to use `ytm` as a shortcut for `ytm-cli`. See the setup scripts above.
@@ -221,7 +212,7 @@ Having issues? Check out the [**Troubleshooting Guide**](TROUBLESHOOTING.md) for
221
212
 
222
213
  ```bash
223
214
  # Enable verbose logging to see what's happening
224
- ytm-cli search "test" -s 1 -v --log-file debug.log
215
+ ytm-cli search "test" -s 1 --verbose --log-file debug.log
225
216
 
226
217
  # Check versions
227
218
  mpv --version
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ytm-cli"
7
- version = "0.7.1"
7
+ version = "0.7.3"
8
8
  description = "YouTube Music CLI tool with interactive terminal interface"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
- requires-python = ">=3.14"
11
+ requires-python = ">=3.10"
12
12
  authors = [
13
13
  { name = "thieuluan", email = "thieuluan.dark@gmail.com" },
14
14
  ]
@@ -17,8 +17,13 @@ classifiers = [
17
17
  "Development Status :: 4 - Beta",
18
18
  "Environment :: Console",
19
19
  "Intended Audience :: End Users/Desktop",
20
- "Operating System :: OS Independent",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Operating System :: MacOS",
21
22
  "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
22
27
  "Programming Language :: Python :: 3.14",
23
28
  "Topic :: Multimedia :: Sound/Audio :: Players",
24
29
  ]
@@ -31,6 +36,16 @@ dependencies = [
31
36
  "numpy",
32
37
  ]
33
38
 
39
+ [dependency-groups]
40
+ dev = [
41
+ "ruff>=0.14.0",
42
+ "pytest>=9.0.3",
43
+ "pytest-cov>=6.0.0",
44
+ "pytest-mock>=3.15.0",
45
+ "coverage>=7.0.0",
46
+ "yt-dlp>=2024.0.0",
47
+ ]
48
+
34
49
  [project.urls]
35
50
  Homepage = "https://github.com/thieuluan1618/ytm"
36
51
  Repository = "https://github.com/thieuluan1618/ytm"
@@ -45,7 +60,7 @@ include = ["ytm_cli*"]
45
60
  [tool.ruff]
46
61
  # Set line length to match pylint (100 characters)
47
62
  line-length = 100
48
- target-version = "py314"
63
+ target-version = "py310"
49
64
 
50
65
  [tool.ruff.lint]
51
66
  # Enable flake8 rules and other important checks
@@ -8,7 +8,7 @@ import pytest
8
8
 
9
9
  # We need to mock the imports before importing the config module
10
10
  # since it initializes global objects on import
11
- with patch("ytm_cli.config.AuthManager"), patch("ytm_cli.config.configparser.ConfigParser"):
11
+ with patch("ytm_cli.config.configparser.ConfigParser"):
12
12
  from ytm_cli.config import get_mpv_flags, get_songs_to_display
13
13
 
14
14
 
@@ -139,10 +139,9 @@ class TestConfigModule:
139
139
  """Tests for config module initialization and behavior"""
140
140
 
141
141
  def test_config_reads_file(self):
142
- """Test that config reads from config.ini file"""
142
+ """Test that config reads from ~/.config/ytm-cli/config.ini"""
143
143
  with (
144
144
  patch("ytm_cli.config.configparser.ConfigParser") as mock_parser_class,
145
- patch("ytm_cli.config.AuthManager"),
146
145
  ):
147
146
  mock_parser = Mock()
148
147
  mock_parser_class.return_value = mock_parser
@@ -154,25 +153,10 @@ class TestConfigModule:
154
153
 
155
154
  importlib.reload(ytm_cli.config)
156
155
 
157
- mock_parser.read.assert_called_with("config.ini")
158
-
159
- def test_auth_manager_initialization(self):
160
- """Test that AuthManager is properly initialized"""
161
- mock_auth_manager = Mock()
162
- mock_auth_manager.get_ytmusic_instance.return_value = Mock()
163
-
164
- with patch(
165
- "ytm_cli.auth.AuthManager", return_value=mock_auth_manager
166
- ) as mock_auth_manager_class:
167
- # Re-import to trigger initialization
168
- import importlib
169
-
170
- import ytm_cli.config
171
-
172
- importlib.reload(ytm_cli.config)
173
-
174
- mock_auth_manager_class.assert_called_once()
175
- mock_auth_manager.get_ytmusic_instance.assert_called_once()
156
+ # Config now lives under ~/.config/ytm-cli/
157
+ mock_parser.read.assert_called_once()
158
+ called_path = mock_parser.read.call_args[0][0]
159
+ assert called_path.endswith(os.path.join(".config", "ytm-cli", "config.ini"))
176
160
 
177
161
 
178
162
  class TestConfigIntegration:
@@ -11,11 +11,13 @@ class TestDislikeManagerInit:
11
11
  """Tests for DislikeManager initialization"""
12
12
 
13
13
  def test_init_default_file(self):
14
- """Test initialization with default file"""
14
+ """Test initialization with default file (~/.config/ytm-cli/dislikes.json)"""
15
15
  with patch("os.path.exists", return_value=False):
16
16
  manager = DislikeManager()
17
17
 
18
- assert manager.dislikes_file == "dislikes.json"
18
+ assert manager.dislikes_file.endswith(
19
+ os.path.join(".config", "ytm-cli", "dislikes.json")
20
+ )
19
21
  assert manager._disliked_ids == set()
20
22
 
21
23
  def test_init_custom_file(self):
@@ -1,10 +1,8 @@
1
1
  """Integration tests for YTM CLI components"""
2
2
 
3
- import json
4
3
  import os
5
- from unittest.mock import Mock, patch
4
+ from unittest.mock import patch
6
5
 
7
- from ytm_cli.auth import AuthManager
8
6
  from ytm_cli.dislikes import DislikeManager
9
7
  from ytm_cli.playlists import PlaylistManager
10
8
 
@@ -48,121 +46,6 @@ class TestPlaylistDislikeIntegration:
48
46
 
49
47
  # Should have 2 songs left (song1 and song3)
50
48
  assert len(filtered_songs) == 2
51
- video_ids = [song["videoId"] for song in filtered_songs]
52
- assert "song1" in video_ids
53
- assert "song3" in video_ids
54
- assert "song2" not in video_ids
55
-
56
- def test_playlist_creation_with_auth_enabled(self, temp_dir):
57
- """Test playlist creation when authentication is enabled"""
58
- # Setup auth manager with enabled auth
59
- config_file = os.path.join(temp_dir, "config.ini")
60
- auth_manager = AuthManager(config_path=config_file)
61
-
62
- auth_manager._update_auth_config("oauth")
63
-
64
- # Setup playlist manager
65
- playlist_manager = PlaylistManager(temp_dir)
66
-
67
- # Create playlist - should work regardless of auth status
68
- result = playlist_manager.create_playlist("Auth Test Playlist")
69
-
70
- assert result is True
71
- assert auth_manager.is_auth_enabled() is True
72
-
73
- # Verify playlist exists
74
- playlists = playlist_manager.list_playlists()
75
- assert len(playlists) == 1
76
- assert playlists[0]["name"] == "Auth Test Playlist"
77
-
78
- def test_dislike_persistence_across_playlist_operations(self, temp_dir, sample_songs):
79
- """Test that dislikes persist across playlist operations"""
80
- dislikes_file = os.path.join(temp_dir, "dislikes.json")
81
- dislike_manager = DislikeManager(dislikes_file)
82
- playlist_manager = PlaylistManager(temp_dir)
83
-
84
- # Dislike a song first
85
- with patch("builtins.print"):
86
- dislike_manager.dislike_song(sample_songs[0])
87
-
88
- # Create playlist and add songs
89
- with patch("builtins.print"):
90
- playlist_manager.create_playlist("Test Playlist")
91
- for song in sample_songs:
92
- playlist_manager.add_song_to_playlist("Test Playlist", song)
93
-
94
- # Create new dislike manager instance to test persistence
95
- dislike_manager2 = DislikeManager(dislikes_file)
96
-
97
- # Should still have the disliked song
98
- assert dislike_manager2.is_disliked("song1")
99
- assert dislike_manager2.get_dislike_count() == 1
100
-
101
-
102
- class TestAuthConfigIntegration:
103
- """Integration tests between auth and config management"""
104
-
105
- def test_auth_config_file_management(self, temp_dir):
106
- """Test auth configuration file management"""
107
- config_file = os.path.join(temp_dir, "config.ini")
108
-
109
- auth_manager = AuthManager(config_path=config_file)
110
-
111
- # Initially no auth
112
- assert auth_manager.is_auth_enabled() is False
113
-
114
- # Enable OAuth
115
- auth_manager._update_auth_config("oauth")
116
-
117
- assert auth_manager.is_auth_enabled() is True
118
- assert auth_manager.get_auth_method() == "oauth"
119
-
120
- # Create new instance to test persistence
121
- auth_manager2 = AuthManager(config_path=config_file)
122
- assert auth_manager2.is_auth_enabled() is True
123
- assert auth_manager2.get_auth_method() == "oauth"
124
-
125
- # Disable auth
126
- auth_manager2._update_auth_config("none", enabled=False)
127
-
128
- # Create third instance to verify disable persisted
129
- auth_manager3 = AuthManager(config_path=config_file)
130
- assert auth_manager3.is_auth_enabled() is False
131
-
132
- def test_auth_with_ytmusic_instance_creation(self, temp_dir):
133
- """Test YTMusic instance creation with different auth states"""
134
- config_file = os.path.join(temp_dir, "config.ini")
135
- oauth_file = os.path.join(temp_dir, "oauth.json")
136
-
137
- auth_manager = AuthManager(config_path=config_file)
138
- auth_manager.oauth_file = oauth_file
139
-
140
- # Test unauthenticated instance
141
- with patch("ytm_cli.auth.YTMusic") as mock_ytmusic, patch("ytm_cli.auth.print"):
142
- mock_instance = Mock()
143
- mock_ytmusic.return_value = mock_instance
144
-
145
- instance = auth_manager.get_ytmusic_instance()
146
-
147
- assert instance == mock_instance
148
- mock_ytmusic.assert_called_once_with()
149
-
150
- # Enable auth and create OAuth file
151
- oauth_data = {"access_token": "test_token"}
152
- with open(oauth_file, "w") as f:
153
- json.dump(oauth_data, f)
154
-
155
- auth_manager._update_auth_config("oauth")
156
-
157
- # Test authenticated instance
158
- with patch("ytm_cli.auth.YTMusic") as mock_ytmusic, patch("ytm_cli.auth.print"):
159
- mock_instance = Mock()
160
- mock_ytmusic.return_value = mock_instance
161
-
162
- instance = auth_manager.get_ytmusic_instance()
163
-
164
- assert instance == mock_instance
165
- mock_ytmusic.assert_called_once_with(oauth_file)
166
49
 
167
50
 
168
51
  class TestFullWorkflowIntegration:
@@ -224,55 +107,6 @@ class TestFullWorkflowIntegration:
224
107
  deleted_playlist = playlist_manager.get_playlist("My Workflow Playlist")
225
108
  assert deleted_playlist is None
226
109
 
227
- def test_auth_setup_to_usage_workflow(self, temp_dir):
228
- """Test complete auth workflow from setup to usage"""
229
- config_file = os.path.join(temp_dir, "config.ini")
230
- oauth_file = os.path.join(temp_dir, "oauth.json")
231
-
232
- auth_manager = AuthManager(config_path=config_file)
233
- auth_manager.oauth_file = oauth_file
234
-
235
- with patch("ytm_cli.auth.print"):
236
- # Setup OAuth
237
- mock_oauth_data = {
238
- "access_token": "test_token",
239
- "refresh_token": "refresh_token",
240
- }
241
-
242
- with patch("ytm_cli.auth.setup_oauth"):
243
- result = auth_manager.setup_oauth_auth("client_id", "client_secret")
244
- assert result is True
245
-
246
- # Verify auth is enabled
247
- assert auth_manager.is_auth_enabled() is True
248
- assert auth_manager.get_auth_method() == "oauth"
249
-
250
- # Verify OAuth file was created by setup_oauth_auth
251
- # (setup_oauth is mocked, so we create the file manually for downstream tests)
252
- with open(oauth_file, "w") as f:
253
- json.dump(mock_oauth_data, f)
254
-
255
- assert os.path.exists(oauth_file)
256
- with open(oauth_file) as f:
257
- saved_data = json.load(f)
258
- assert saved_data == mock_oauth_data
259
-
260
- # Test YTMusic instance creation
261
- with patch("ytm_cli.auth.YTMusic") as mock_ytmusic:
262
- mock_instance = Mock()
263
- mock_ytmusic.return_value = mock_instance
264
-
265
- instance = auth_manager.get_ytmusic_instance()
266
-
267
- assert instance == mock_instance
268
- mock_ytmusic.assert_called_once_with(oauth_file)
269
-
270
- # Disable auth
271
- with patch("builtins.input", return_value="n"):
272
- result = auth_manager.disable_auth()
273
- assert result is True
274
- assert auth_manager.is_auth_enabled() is False
275
-
276
110
  def test_error_recovery_workflow(self, temp_dir, sample_songs):
277
111
  """Test error recovery in integrated workflows"""
278
112
  playlist_manager = PlaylistManager(temp_dir)
@@ -285,7 +119,7 @@ class TestFullWorkflowIntegration:
285
119
 
286
120
  # Try to add song with missing data
287
121
  incomplete_song = {"title": "Incomplete Song"} # Missing videoId
288
- result = playlist_manager.add_song_to_playlist("Error Test Playlist", incomplete_song)
122
+ playlist_manager.add_song_to_playlist("Error Test Playlist", incomplete_song)
289
123
  # Should handle gracefully (behavior depends on implementation)
290
124
 
291
125
  # Add valid songs