Skip to content

Commit b917be6

Browse files
committed
Merge branch 'feature/channel-info' into feature/video-info
2 parents 7d4b82d + 8c4644b commit b917be6

File tree

5 files changed

+98
-13
lines changed

5 files changed

+98
-13
lines changed

tests/commands/test_base.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ def execute(cls, **kwargs):
1919

2020
@pytest.fixture
2121
def subparsers():
22+
"""Fixture to create subparsers for argument parsing."""
2223
parser = argparse.ArgumentParser()
2324
return parser.add_subparsers()
2425

2526

2627
def test_generate_parser(subparsers):
28+
"""Test to verify the parser generation.
29+
30+
This test checks if the `generate_parser` method correctly generates a parser
31+
for the command and sets the appropriate properties
32+
"""
2733
parser = TestCommand.generate_parser(subparsers)
2834

2935
assert parser is not None, "Parser should not be None"
@@ -32,6 +38,11 @@ def test_generate_parser(subparsers):
3238

3339

3440
def test_parse_arguments(subparsers):
41+
"""Test to verify argument parsing.
42+
43+
This test checks if the `parse_arguments` method correctly adds the command's
44+
arguments to the parser and sets the default function to the command's execute method.
45+
"""
3546
subparsers_mock = MagicMock(spec=subparsers)
3647

3748
TestCommand.parse_arguments(subparsers_mock)
@@ -43,6 +54,11 @@ def test_parse_arguments(subparsers):
4354

4455

4556
def test_command():
57+
"""Test to verify that the `execute` method is implemented.
58+
59+
This test ensures that if a command does not implement the `execute` method,
60+
a `NotImplementedError` is raised.
61+
"""
4662
class MyCommand(Command):
4763
pass
4864

@@ -52,6 +68,7 @@ class MyCommand(Command):
5268

5369
@pytest.fixture
5470
def mock_csv_file():
71+
"""Fixture to provide mock CSV content for tests."""
5572

5673
csv_content = """URL
5774
http://example.com
@@ -60,6 +77,14 @@ def mock_csv_file():
6077
return csv_content
6178

6279
def test_data_from_csv_valid(mock_csv_file):
80+
"""Test to verify reading data from a valid CSV file.
81+
82+
This test checks if the `data_from_csv` method correctly reads data from a valid CSV file
83+
and returns the expected list of URLs.
84+
85+
Args:
86+
mock_csv_file (str): The mock CSV file content.
87+
"""
6388
with patch('pathlib.Path.is_file', return_value=True):
6489
with patch('builtins.open', mock_open(read_data=mock_csv_file)):
6590
data_column_name = "URL"
@@ -70,6 +95,11 @@ def test_data_from_csv_valid(mock_csv_file):
7095
assert result[1] == "http://example2.com"
7196

7297
def test_data_from_csv_file_not_found():
98+
"""Test to verify behavior when the specified column is not found in the CSV file.
99+
100+
This test checks if the `data_from_csv` method raises an exception when the specified
101+
column does not exist in the CSV file.
102+
"""
73103
with patch('pathlib.Path.is_file', return_value=False):
74104
file_path = Path("/fake/path/not_found.csv")
75105
with pytest.raises(FileNotFoundError):
@@ -86,12 +116,18 @@ def test_data_from_csv_column_not_found(mock_csv_file):
86116

87117
@pytest.fixture
88118
def sample_data():
119+
"""Fixture to provide sample data for tests."""
89120
return [
90121
{"id": "123", "name": "Channel One"},
91122
{"id": "456", "name": "Channel Two"}
92123
]
93124

94125
def test_data_to_csv_with_output_file_path(tmp_path, sample_data):
126+
"""Test to verify writing data to a CSV file with an output file path specified.
127+
128+
This test checks if the `data_to_csv` method correctly writes the sample data to
129+
a CSV file when an output file path is provided.
130+
"""
95131
output_file_path = tmp_path / "output.csv"
96132

97133
result_path = Command.data_to_csv(sample_data, str(output_file_path))
@@ -105,13 +141,24 @@ def test_data_to_csv_with_output_file_path(tmp_path, sample_data):
105141
assert rows[0]["id"] == "123" and rows[1]["id"] == "456"
106142

107143
def test_data_to_csv_without_output_file_path(sample_data):
144+
"""Test to verify writing data to a CSV format without an output file path specified.
145+
146+
This test checks if the `data_to_csv` method correctly returns the CSV content
147+
as a string when no output file path is provided.
148+
"""
108149
csv_content = Command.data_to_csv(sample_data)
109150

110151
assert "id,name" in csv_content
111152
assert "123,Channel One" in csv_content
112153
assert "456,Channel Two" in csv_content
113154

114155
def test_data_to_csv_output(tmp_path):
156+
"""
157+
Test to verify the content of the output CSV file.
158+
159+
This test checks if the `data_to_csv` method writes the expected content
160+
to the output CSV file.
161+
"""
115162
output_file_path = tmp_path / "output.csv"
116163

117164
data = [

tests/commands/test_channel_id.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,30 @@
88

99
@pytest.fixture
1010
def csv_file(tmp_path):
11+
"""Fixture to create a temporary CSV file with a single YouTube channel URL."""
12+
1113
csv_content = "channel_url\nhttps://www.youtube.com/@Turicas/featured\n"
1214
csv_file = tmp_path / "urls.csv"
1315
csv_file.write_text(csv_content)
1416
return csv_file
1517

1618
@pytest.fixture
1719
def youtube_api_mock():
20+
"""Fixture to mock the YouTube API.
21+
22+
This fixture mocks the `YouTube` class and its `channel_id_from_url` method
23+
to return a channel ID based on the URL.
24+
"""
1825
with patch("youtool.commands.channel_id.YouTube") as mock:
1926
mock.return_value.channel_id_from_url.side_effect = lambda url: f"channel-{url}"
2027
yield mock
2128

2229
def test_channels_ids_csv_preparation(youtube_api_mock):
30+
"""Fixture to mock the YouTube API.
31+
32+
This fixture mocks the `YouTube` class and its `channel_id_from_url` method
33+
to return a channel ID based on the URL.
34+
"""
2335
urls = ["https://www.youtube.com/@Turicas/featured", "https://www.youtube.com/c/PythonicCaf%C3%A9"]
2436
api_key = "test_api_key"
2537
id_column_name = "custom_id_column"
@@ -40,16 +52,29 @@ def test_channels_ids_csv_preparation(youtube_api_mock):
4052

4153

4254
def test_resolve_urls_with_direct_urls():
43-
# Tests whether the function returns the directly given list of URLs.
55+
"""Test to verify resolving URLs when provided directly.
56+
57+
This test checks if the `resolve_urls` method of the `ChannelId` class correctly
58+
returns the given list of URLs when provided directly.
59+
"""
4460
urls = ["https://www.youtube.com/@Turicas/featured"]
4561
result = ChannelId.resolve_urls(urls, None, None)
4662
assert result == urls
4763

4864
def test_resolve_urls_with_file_path(csv_file):
65+
"""Test to verify resolving URLs from a CSV file.
66+
67+
This test checks if the `resolve_urls` method of the `ChannelId` class correctly
68+
reads URLs from a given CSV file.
69+
"""
4970
result = ChannelId.resolve_urls(None, csv_file, "channel_url")
5071
assert result == ["https://www.youtube.com/@Turicas/featured"]
5172

5273
def test_resolve_urls_raises_exception():
53-
# Tests whether the function throws an exception when neither urls nor urls_file_path are provided.
74+
"""Test to verify exception raising when no URLs are provided.
75+
76+
This test checks if the `resolve_urls` method of the `ChannelId` class raises an exception
77+
when neither direct URLs nor a file path are provided.
78+
"""
5479
with pytest.raises(Exception, match="Either 'username' or 'url' must be provided for the channel-id command"):
5580
ChannelId.resolve_urls(None, None, None)

tests/commands/test_channel_info.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import pytest
1+
from unittest.mock import Mock, call
22

3-
from unittest.mock import patch, Mock, call
4-
5-
from youtool.commands.channel_info import ChannelInfo, YouTube
3+
from youtool.commands.channel_info import ChannelInfo
64

75

86
def test_filter_fields():
7+
"""Test to verify the filtering of channel information fields.
8+
9+
This test checks if the `filter_fields` method of the `ChannelInfo` class correctly
10+
filters out unwanted fields from the channel information dictionary based on the provided columns.
11+
"""
912
channel_info = {
1013
'channel_id': '123456',
1114
'channel_name': 'Test Channel',
@@ -27,6 +30,11 @@ def test_filter_fields():
2730

2831

2932
def test_channel_ids_from_urls_and_usernames(mocker):
33+
"""Test to verify fetching channel IDs from both URLs and usernames.
34+
35+
This test checks if the `execute` method of the `ChannelInfo` class correctly fetches channel IDs
36+
from a list of URLs and usernames, and then calls the `channels_infos` method with these IDs.
37+
"""
3038
urls = ["https://www.youtube.com/@Turicas/featured", "https://www.youtube.com/c/PythonicCaf%C3%A9"]
3139
usernames = ["Turicas", "PythonicCafe"]
3240

tests/test_cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
"command", COMMANDS
1212
)
1313
def test_missing_api_key(monkeypatch: pytest.MonkeyPatch, command: Command):
14+
"""Test to verify behavior when the YouTube API key is missing.
15+
16+
This test ensures that when the YouTube API key is not set, running any command
17+
from the youtool CLI results in an appropriate error message and exit code.
18+
"""
1419
monkeypatch.delenv('YOUTUBE_API_KEY', raising=False)
1520
cli_path = "youtool/cli.py"
1621
command = ["python", cli_path, command.name]

youtool/commands/channel_info.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99

1010
class ChannelInfo(Command):
11-
"""
12-
Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output
11+
"""Get channel info from a list of IDs (or CSV filename with IDs inside), generate CSV output
1312
(same schema for `channel` dicts)
1413
"""
1514
name = "channel-info"
@@ -35,8 +34,7 @@ class ChannelInfo(Command):
3534

3635
@staticmethod
3736
def filter_fields(channel_info: Dict, info_columns: Optional[List] = None):
38-
"""
39-
Filters the fields of a dictionary containing channel information based on
37+
"""Filters the fields of a dictionary containing channel information based on
4038
specified columns.
4139
4240
Args:
@@ -55,8 +53,8 @@ def filter_fields(channel_info: Dict, info_columns: Optional[List] = None):
5553

5654
@classmethod
5755
def execute(cls: Self, **kwargs) -> str:
58-
"""
59-
Execute the channel-info command to fetch YouTube channel information from URLs or usernames and save them to a CSV file.
56+
"""Execute the channel-info command to fetch YouTube channel information from URLs or
57+
usernames and save them to a CSV file.
6058
6159
Args:
6260
urls (list[str], optional): A list of YouTube channel URLs. If not provided, `urls_file_path` must be specified.
@@ -69,7 +67,8 @@ def execute(cls: Self, **kwargs) -> str:
6967
Default is "channel_url".
7068
username_column_name (str, optional): The name of the column in the `usernames_file_path` CSV file that contains the usernames.
7169
Default is "channel_username".
72-
info_columns (str, optional): Comma-separated list of columns to include in the output CSV. Default is the class attribute `INFO_COLUMNS`.
70+
info_columns (str, optional): Comma-separated list of columns to include in the output CSV.
71+
Default is the class attribute `INFO_COLUMNS`.
7372
7473
Returns:
7574
str: A message indicating the result of the command. If `output_file_path` is specified, the message will
@@ -78,6 +77,7 @@ def execute(cls: Self, **kwargs) -> str:
7877
Raises:
7978
Exception: If neither `urls`, `usernames`, `urls_file_path` nor `usernames_file_path` is provided.
8079
"""
80+
8181
urls = kwargs.get("urls")
8282
usernames = kwargs.get("usernames")
8383
urls_file_path = kwargs.get("urls_file_path")

0 commit comments

Comments
 (0)