ytm-cli 0.7.0__tar.gz → 0.7.2__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.0 → ytm_cli-0.7.2}/PKG-INFO +29 -40
  2. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/README.md +26 -38
  3. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/pyproject.toml +13 -2
  4. ytm_cli-0.7.2/tests/test_cache.py +111 -0
  5. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_config.py +6 -22
  6. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_dislikes.py +4 -2
  7. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_integration.py +2 -168
  8. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_main.py +13 -7
  9. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_playlists.py +8 -8
  10. ytm_cli-0.7.2/tests/test_spectrum.py +167 -0
  11. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/__init__.py +1 -1
  12. ytm_cli-0.7.2/ytm_cli/config.py +49 -0
  13. ytm_cli-0.7.2/ytm_cli/demo.py +120 -0
  14. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/dislikes.py +11 -2
  15. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/llm_client.py +5 -3
  16. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/main.py +24 -9
  17. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/player.py +27 -7
  18. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/playlists.py +17 -8
  19. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/ui.py +3 -3
  20. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli.egg-info/PKG-INFO +29 -40
  21. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli.egg-info/SOURCES.txt +3 -1
  22. ytm_cli-0.7.0/ytm_cli/auth.py +0 -697
  23. ytm_cli-0.7.0/ytm_cli/config.py +0 -29
  24. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/LICENSE +0 -0
  25. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/setup.cfg +0 -0
  26. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_ffmpeg_player.py +0 -0
  27. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_hybrid_player.py +0 -0
  28. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_lyrics_service.py +0 -0
  29. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_runner.py +0 -0
  30. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_ui.py +0 -0
  31. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/tests/test_utils.py +0 -0
  32. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/__main__.py +0 -0
  33. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/cache.py +0 -0
  34. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/ffmpeg_player.py +0 -0
  35. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/hybrid_player.py +0 -0
  36. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/lyrics_service.py +0 -0
  37. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/spectrum.py +0 -0
  38. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/utils.py +0 -0
  39. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli/verbose_logger.py +0 -0
  40. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli.egg-info/dependency_links.txt +0 -0
  41. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli.egg-info/entry_points.txt +0 -0
  42. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/ytm_cli.egg-info/requires.txt +0 -0
  43. {ytm_cli-0.7.0 → ytm_cli-0.7.2}/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.0
3
+ Version: 0.7.2
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,7 +11,8 @@ 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
16
17
  Classifier: Programming Language :: Python :: 3.14
17
18
  Classifier: Topic :: Multimedia :: Sound/Audio :: Players
@@ -32,9 +33,16 @@ Dynamic: license-file
32
33
 
33
34
  Stream music directly from YouTube Music in your terminal with intuitive controls, playlist management, and smart filtering.
34
35
 
35
- ![YTM CLI Player](image.png)
36
+ <p align="center">
37
+ <a href="https://github.com/thieuluan1618/ytm/raw/main/screenshots/player.webm">
38
+ <img src="screenshots/player.gif" alt="ytm-cli player demo" width="800">
39
+ </a>
40
+ </p>
36
41
 
37
- ![YTM CLI Lyrics](image-lyric.png)
42
+ <p align="center">
43
+ <em>Player UI with the real-time FFT spectrum visualizer (synthetic demo capture).
44
+ Click for the higher-quality .webm.</em>
45
+ </p>
38
46
 
39
47
  ## ✨ Features
40
48
 
@@ -45,25 +53,24 @@ Stream music directly from YouTube Music in your terminal with intuitive control
45
53
  - **👎 Smart Filtering**: Dislike songs to filter them from future results
46
54
  - **📜 Synced Lyrics**: Live-highlighted lyrics with auto-scroll (press `l`)
47
55
  - **🎯 Radio Mode**: Automatic playlist generation based on your selection
56
+ - **📊 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
57
  - **🤖 AI-Powered**: Natural language music requests and AI-generated playlists
49
58
 
50
59
  ## 🚀 Quick Start
51
60
 
52
61
  ### Requirements
53
62
 
54
- - Python 3.8+
63
+ - Python **3.14+** (set in `pyproject.toml`)
55
64
  - [mpv media player](https://mpv.io/installation/) (must be installed system-wide)
65
+ - [`ffmpeg`](https://ffmpeg.org/) on `PATH` for the real-time spectrum visualizer (optional — falls back to a stereo oscilloscope if missing)
56
66
 
57
67
  ### Installation
58
68
 
59
69
  **One command with [uv](https://docs.astral.sh/uv/) (recommended):**
60
70
 
61
71
  ```bash
62
- # Install from PyPI
72
+ # Install from PyPI — gets latest version automatically
63
73
  uvx ytm-cli "song name"
64
-
65
- # Or from source (no clone needed)
66
- uvx --from git+https://github.com/thieuluan1618/ytm.git ytm-cli "song name"
67
74
  ```
68
75
 
69
76
  <details>
@@ -78,36 +85,16 @@ uv run ytm-cli "song name"
78
85
  </details>
79
86
 
80
87
  <details>
81
- <summary>Alternative: one-shot setup scripts</summary>
88
+ <summary>Alternative: one-shot setup script (Linux / macOS)</summary>
82
89
 
83
90
  ```bash
84
91
  git clone https://github.com/thieuluan1618/ytm.git
85
92
  cd ytm
86
- ```
87
-
88
- These create the virtual environment, install dependencies, and configure the `ytm` command globally.
89
-
90
- **Linux/macOS:**
91
-
92
- ```bash
93
93
  ./setup.sh
94
- source ~/.zshrc # or ~/.bashrc for bash
94
+ source ~/.zshrc # or ~/.bashrc for bash
95
95
  ```
96
96
 
97
- **Windows PowerShell:**
98
-
99
- ```powershell
100
- .\setup.ps1
101
- . $PROFILE
102
- ```
103
-
104
- **Windows Command Prompt:**
105
-
106
- ```cmd
107
- setup.bat
108
- ```
109
-
110
- After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
97
+ This creates the venv, installs runtime deps from `pyproject.toml`, and adds a `ytm` shell alias.
111
98
 
112
99
  </details>
113
100
 
@@ -117,14 +104,15 @@ After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
117
104
  ```bash
118
105
  git clone https://github.com/thieuluan1618/ytm.git
119
106
  cd ytm
120
- python -m venv venv
121
- source venv/bin/activate # Windows: venv\Scripts\activate
122
- pip install -r requirements.txt
123
- ./setup_alias.sh # or setup_alias.ps1 / setup_alias.bat
107
+ uv sync # installs runtime deps from pyproject.toml + uv.lock
108
+ ./setup_alias.sh # optional: registers the `ytm` alias
109
+ # Run with: uv run ytm-cli "song"
124
110
  ```
125
111
 
126
112
  </details>
127
113
 
114
+ > **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.
115
+
128
116
  ### Basic Usage
129
117
 
130
118
  **Interactive search:**
@@ -143,9 +131,10 @@ ytm-cli "your favorite song"
143
131
  **Non-interactive mode (automation/scripting):**
144
132
 
145
133
  ```bash
146
- ytm-cli search "song name" --select 1 # Auto-select first result
147
- ytm-cli search "song" -s 1 -v # With verbose output
148
- ytm-cli search "song" -s 1 -v --log-file debug.log # Save debug logs
134
+ ytm-cli search "song name" --select 1 # Auto-select first result
135
+ ytm-cli search "song" -s 1 --verbose # With verbose output
136
+ ytm-cli search "song" -s 1 --verbose --log-file debug.log # Save debug logs
137
+ ytm-cli -v # Print version
149
138
  ```
150
139
 
151
140
  > **Tip:** Set up the shell alias to use `ytm` as a shortcut for `ytm-cli`. See the setup scripts above.
@@ -252,7 +241,7 @@ Having issues? Check out the [**Troubleshooting Guide**](TROUBLESHOOTING.md) for
252
241
 
253
242
  ```bash
254
243
  # Enable verbose logging to see what's happening
255
- ytm-cli search "test" -s 1 -v --log-file debug.log
244
+ ytm-cli search "test" -s 1 --verbose --log-file debug.log
256
245
 
257
246
  # Check versions
258
247
  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,25 +24,24 @@ 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.14+** (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
-
37
- # Or from source (no clone needed)
38
- uvx --from git+https://github.com/thieuluan1618/ytm.git ytm-cli "song name"
39
45
  ```
40
46
 
41
47
  <details>
@@ -50,36 +56,16 @@ uv run ytm-cli "song name"
50
56
  </details>
51
57
 
52
58
  <details>
53
- <summary>Alternative: one-shot setup scripts</summary>
59
+ <summary>Alternative: one-shot setup script (Linux / macOS)</summary>
54
60
 
55
61
  ```bash
56
62
  git clone https://github.com/thieuluan1618/ytm.git
57
63
  cd ytm
58
- ```
59
-
60
- These create the virtual environment, install dependencies, and configure the `ytm` command globally.
61
-
62
- **Linux/macOS:**
63
-
64
- ```bash
65
64
  ./setup.sh
66
- source ~/.zshrc # or ~/.bashrc for bash
65
+ source ~/.zshrc # or ~/.bashrc for bash
67
66
  ```
68
67
 
69
- **Windows PowerShell:**
70
-
71
- ```powershell
72
- .\setup.ps1
73
- . $PROFILE
74
- ```
75
-
76
- **Windows Command Prompt:**
77
-
78
- ```cmd
79
- setup.bat
80
- ```
81
-
82
- 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.
83
69
 
84
70
  </details>
85
71
 
@@ -89,14 +75,15 @@ After setup, use `ytm` (alias for `ytm-cli`) from anywhere.
89
75
  ```bash
90
76
  git clone https://github.com/thieuluan1618/ytm.git
91
77
  cd ytm
92
- python -m venv venv
93
- source venv/bin/activate # Windows: venv\Scripts\activate
94
- pip install -r requirements.txt
95
- ./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"
96
81
  ```
97
82
 
98
83
  </details>
99
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
+
100
87
  ### Basic Usage
101
88
 
102
89
  **Interactive search:**
@@ -115,9 +102,10 @@ ytm-cli "your favorite song"
115
102
  **Non-interactive mode (automation/scripting):**
116
103
 
117
104
  ```bash
118
- ytm-cli search "song name" --select 1 # Auto-select first result
119
- ytm-cli search "song" -s 1 -v # With verbose output
120
- 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
121
109
  ```
122
110
 
123
111
  > **Tip:** Set up the shell alias to use `ytm` as a shortcut for `ytm-cli`. See the setup scripts above.
@@ -224,7 +212,7 @@ Having issues? Check out the [**Troubleshooting Guide**](TROUBLESHOOTING.md) for
224
212
 
225
213
  ```bash
226
214
  # Enable verbose logging to see what's happening
227
- ytm-cli search "test" -s 1 -v --log-file debug.log
215
+ ytm-cli search "test" -s 1 --verbose --log-file debug.log
228
216
 
229
217
  # Check versions
230
218
  mpv --version
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ytm-cli"
7
- version = "0.7.0"
7
+ version = "0.7.2"
8
8
  description = "YouTube Music CLI tool with interactive terminal interface"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -17,7 +17,8 @@ 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",
22
23
  "Programming Language :: Python :: 3.14",
23
24
  "Topic :: Multimedia :: Sound/Audio :: Players",
@@ -31,6 +32,16 @@ dependencies = [
31
32
  "numpy",
32
33
  ]
33
34
 
35
+ [dependency-groups]
36
+ dev = [
37
+ "ruff>=0.14.0",
38
+ "pytest>=9.0.3",
39
+ "pytest-cov>=6.0.0",
40
+ "pytest-mock>=3.15.0",
41
+ "coverage>=7.0.0",
42
+ "yt-dlp>=2024.0.0",
43
+ ]
44
+
34
45
  [project.urls]
35
46
  Homepage = "https://github.com/thieuluan1618/ytm"
36
47
  Repository = "https://github.com/thieuluan1618/ytm"
@@ -0,0 +1,111 @@
1
+ """Tests for ytm_cli.cache."""
2
+
3
+ import json
4
+ import time
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+ from ytm_cli import cache as cache_module
10
+
11
+
12
+ @pytest.fixture
13
+ def temp_cache_file(tmp_path, monkeypatch):
14
+ """Redirect CACHE_FILE to an isolated temp path for each test."""
15
+ path = tmp_path / "ytm_cache.json"
16
+ monkeypatch.setattr(cache_module, "CACHE_FILE", str(path))
17
+ return path
18
+
19
+
20
+ class TestLoadSaveCache:
21
+ def test_load_cache_returns_empty_dict_when_missing(self, temp_cache_file):
22
+ assert cache_module.load_cache() == {}
23
+
24
+ def test_load_cache_returns_empty_dict_on_invalid_json(self, temp_cache_file):
25
+ temp_cache_file.write_text("not json {{{")
26
+ assert cache_module.load_cache() == {}
27
+
28
+ def test_save_cache_then_load_round_trip(self, temp_cache_file):
29
+ payload = {"k": {"data": "v", "timestamp": 1234.0}}
30
+ cache_module.save_cache(payload)
31
+ assert json.loads(temp_cache_file.read_text()) == payload
32
+ assert cache_module.load_cache() == payload
33
+
34
+
35
+ class TestGetSetCached:
36
+ def test_get_cached_missing_key_returns_none(self, temp_cache_file):
37
+ assert cache_module.get_cached("nope") is None
38
+
39
+ def test_set_then_get_returns_value(self, temp_cache_file):
40
+ cache_module.set_cached("query1", "result1")
41
+ assert cache_module.get_cached("query1") == "result1"
42
+
43
+ def test_get_cached_expired_entry_removed_and_returns_none(self, temp_cache_file):
44
+ # Write an entry with timestamp older than the TTL.
45
+ old_ts = time.time() - cache_module.CACHE_TTL - 100
46
+ cache_module.save_cache({"old": {"data": "stale", "timestamp": old_ts}})
47
+
48
+ assert cache_module.get_cached("old") is None
49
+ # Expired entry should be evicted from disk on read.
50
+ assert "old" not in cache_module.load_cache()
51
+
52
+ def test_set_cached_overwrites_existing(self, temp_cache_file):
53
+ cache_module.set_cached("k", "v1")
54
+ cache_module.set_cached("k", "v2")
55
+ assert cache_module.get_cached("k") == "v2"
56
+
57
+
58
+ class TestClearExpired:
59
+ def test_clear_expired_removes_only_old_entries(self, temp_cache_file):
60
+ now = time.time()
61
+ cache_module.save_cache(
62
+ {
63
+ "fresh": {"data": "a", "timestamp": now},
64
+ "stale": {"data": "b", "timestamp": now - cache_module.CACHE_TTL - 10},
65
+ }
66
+ )
67
+
68
+ cache_module.clear_expired()
69
+
70
+ remaining = cache_module.load_cache()
71
+ assert "fresh" in remaining
72
+ assert "stale" not in remaining
73
+
74
+ def test_clear_expired_on_empty_cache_is_noop(self, temp_cache_file):
75
+ cache_module.clear_expired()
76
+ assert cache_module.load_cache() == {}
77
+
78
+
79
+ class TestSearchWithCache:
80
+ def test_search_with_cache_calls_search_func_on_miss(self, temp_cache_file):
81
+ search_func = lambda q: [{"title": q}] # noqa: E731
82
+
83
+ result = cache_module.search_with_cache("song", search_func)
84
+
85
+ assert result == [{"title": "song"}]
86
+ # Underlying entry is JSON-serialized.
87
+ stored = cache_module.get_cached("song")
88
+ assert json.loads(stored) == [{"title": "song"}]
89
+
90
+ def test_search_with_cache_uses_cached_value_on_hit(self, temp_cache_file):
91
+ calls = {"n": 0}
92
+
93
+ def search_func(q):
94
+ calls["n"] += 1
95
+ return [{"title": q}]
96
+
97
+ cache_module.search_with_cache("song", search_func)
98
+ cache_module.search_with_cache("song", search_func)
99
+
100
+ assert calls["n"] == 1 # second call served from cache
101
+
102
+ def test_search_with_cache_handles_non_string_cached_value(self, temp_cache_file):
103
+ # Pre-populate cache with a non-string (already-deserialized) value.
104
+ cache_module.set_cached("song", [{"title": "song"}])
105
+
106
+ # search_func should NOT be called.
107
+ with patch.object(cache_module, "save_cache") as mock_save:
108
+ result = cache_module.search_with_cache("song", lambda q: pytest.fail("must not call"))
109
+
110
+ assert result == [{"title": "song"}]
111
+ mock_save.assert_not_called()
@@ -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):