Skip to content

Commit 937ad3d

Browse files
committed
- Add test for video_search command;
- Add updates to some test files; - created conftest file
1 parent 6ab1762 commit 937ad3d

File tree

5 files changed

+113
-32
lines changed

5 files changed

+113
-32
lines changed

tests/commands/conftest.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def channels_urls():
6+
return [
7+
"https://www.youtube.com/@Turicas/featured",
8+
"https://www.youtube.com/c/PythonicCaf%C3%A9"
9+
]
10+
11+
12+
@pytest.fixture
13+
def videos_ids():
14+
return [
15+
"video_id_1",
16+
"video_id_2"
17+
]
18+
19+
20+
@pytest.fixture
21+
def videos_urls(videos_ids):
22+
return [
23+
f"https://www.youtube.com/?v={video_id}" for video_id in videos_ids
24+
]
25+
26+
27+
@pytest.fixture
28+
def usernames():
29+
return ["Turicas", "PythonicCafe"]

tests/commands/test_channel_info.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ def test_channel_ids_from_urls_and_usernames(mocker):
5252
youtube_mock.return_value.channel_id_from_username = channel_id_from_username_mock
5353
youtube_mock.return_value.channels_infos = channels_infos_mock
5454

55-
ChannelInfo.execute(urls=urls, usernames=usernames)
55+
ChannelInfo.execute(urls=channels_urls, usernames=usernames)
5656

5757
channel_id_from_url_mock.assert_has_calls(
58-
[call(url) for url in urls]
58+
[call(url) for url in channels_urls]
5959
)
6060
channel_id_from_username_mock.assert_has_calls(
6161
[call(username) for username in usernames]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import csv
2+
import pytest
3+
4+
from io import StringIO
5+
from unittest.mock import Mock, call
6+
from datetime import datetime
7+
8+
from youtool.commands.video_search import VideoSearch
9+
10+
11+
def test_video_search_string_output(mocker, videos_ids, videos_urls):
12+
youtube_mock = mocker.patch("youtool.commands.video_search.YouTube")
13+
expected_videos_infos = [
14+
{
15+
column: f"v_{index}" for column in VideoSearch.INFO_COLUMNS
16+
} for index, _ in enumerate(videos_ids)
17+
]
18+
19+
csv_file = StringIO()
20+
csv_writer = csv.DictWriter(csv_file, fieldnames=VideoSearch.INFO_COLUMNS)
21+
csv_writer.writeheader()
22+
csv_writer.writerows(expected_videos_infos)
23+
24+
videos_infos_mock = Mock(return_value=expected_videos_infos)
25+
youtube_mock.return_value.videos_infos = videos_infos_mock
26+
27+
result = VideoSearch.execute(ids=videos_ids, urls=videos_urls)
28+
29+
videos_infos_mock.assert_called_once_with(list(set(videos_ids)))
30+
assert result == csv_file.getvalue()
31+
32+
33+
def test_video_search_file_output(mocker, videos_ids, videos_urls, tmp_path):
34+
youtube_mock = mocker.patch("youtool.commands.video_search.YouTube")
35+
expected_videos_infos = [
36+
{
37+
column: f"v_{index}" for column in VideoSearch.INFO_COLUMNS
38+
} for index, _ in enumerate(videos_ids)
39+
]
40+
41+
expected_csv_file = StringIO()
42+
csv_writer = csv.DictWriter(expected_csv_file, fieldnames=VideoSearch.INFO_COLUMNS)
43+
csv_writer.writeheader()
44+
csv_writer.writerows(expected_videos_infos)
45+
46+
timestamp = datetime.now().strftime("%f")
47+
output_file_name = f"output_{timestamp}.csv"
48+
output_file_path = tmp_path / output_file_name
49+
50+
videos_infos_mock = Mock(return_value=expected_videos_infos)
51+
youtube_mock.return_value.videos_infos = videos_infos_mock
52+
53+
result_file_path = VideoSearch.execute(
54+
ids=videos_ids, urls=videos_urls, output_file_path=output_file_path
55+
)
56+
57+
with open(result_file_path, "r") as result_csv_file:
58+
result_csv = result_csv_file.read()
59+
60+
videos_infos_mock.assert_called_once_with(list(set(videos_ids)))
61+
assert result_csv.replace("\r", "") == expected_csv_file.getvalue().replace("\r", "")
62+
63+
64+
def test_video_search_no_id_and_url_error():
65+
with pytest.raises(Exception, match="Either 'ids' or 'urls' must be provided"):
66+
VideoSearch.execute(ids=None, urls=None)

youtool/commands/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def data_from_csv(file_path: Path, data_column_name: Optional[str] = None) -> Li
105105

106106
if fieldnames is None:
107107
raise ValueError("Fieldnames is None")
108-
108+
109109
if data_column_name not in fieldnames:
110110
raise Exception(f"Column {data_column_name} not found on {file_path}")
111111
for row in reader:

youtool/commands/video_search.py

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

99

1010
class VideoSearch(Command):
11-
"""Search video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside), generate CSV output (simplified video dict schema or option to get full video info)
11+
"""
12+
Search video info from a list of IDs or URLs (or CSV filename with URLs/IDs inside),
13+
generate CSV output (simplified video dict schema or option to get full video info)
1214
"""
1315
name = "video-search"
1416
arguments = [
1517
{"name": "--ids", "type": str, "help": "Video IDs", "nargs": "*"},
1618
{"name": "--urls", "type": str, "help": "Video URLs", "nargs": "*"},
1719
{"name": "--input-file-path", "type": str, "help": "Input CSV file path with URLs/IDs"},
1820
{"name": "--output-file-path", "type": str, "help": "Output CSV file path"},
19-
{"name": "--full-info", "type": bool, "help": "Option to get full video info", "default": False}
21+
{"name": "--full-info", "type": bool, "help": "Option to get full video info", "default": False},
22+
{"name": "--url-column-name", "type": str, "help": "URL column name on csv input files"},
23+
{"name": "--id-column-name", "type": str, "help": "Channel ID column name on csv output files"}
2024
]
2125

2226
ID_COLUMN_NAME: str = "video_id"
@@ -28,23 +32,6 @@ class VideoSearch(Command):
2832
"id", "title", "description", "published_at", "view_count", "like_count", "comment_count"
2933
]
3034

31-
@staticmethod
32-
def filter_fields(video_info: Dict, info_columns: Optional[List] = None) -> Dict:
33-
"""Filters the fields of a dictionary containing video information based on specified columns.
34-
35-
Args:
36-
video_info (Dict): A dictionary containing video information.
37-
info_columns (Optional[List], optional): A list specifying which fields to include in the filtered output.
38-
If None, returns the entire video_info dictionary. Defaults to None.
39-
40-
Returns:
41-
A dictionary containing only the fields specified in info_columns (if provided)
42-
or the entire video_info dictionary if info_columns is None.
43-
"""
44-
return {
45-
field: value for field, value in video_info.items() if field in info_columns
46-
} if info_columns else video_info
47-
4835
@classmethod
4936
def execute(cls: Self, **kwargs) -> str:
5037
"""
@@ -68,21 +55,20 @@ def execute(cls: Self, **kwargs) -> str:
6855
"""
6956
ids = kwargs.get("ids", [])
7057
urls = kwargs.get("urls", [])
71-
input_file_path = kwargs.get("input_file_path")
7258
output_file_path = kwargs.get("output_file_path")
7359
api_key = kwargs.get("api_key")
7460
full_info = kwargs.get("full_info", False)
7561

62+
url_column_name = kwargs.get("url_column_name", cls.URL_COLUMN_NAME)
63+
id_column_name = kwargs.get("id_column_name", cls.ID_COLUMN_NAME)
64+
7665
info_columns = VideoSearch.FULL_INFO_COLUMNS if full_info else VideoSearch.INFO_COLUMNS
7766

78-
if input_file_path:
79-
with open(input_file_path, mode='r') as infile:
80-
reader = csv.DictReader(infile)
81-
for row in reader:
82-
if cls.ID_COLUMN_NAME in row and row[cls.ID_COLUMN_NAME]:
83-
ids.append(row[cls.ID_COLUMN_NAME])
84-
elif cls.URL_COLUMN_NAME in row and row[cls.URL_COLUMN_NAME]:
85-
urls.append(row[cls.URL_COLUMN_NAME])
67+
if (input_file_path := kwargs.get("input_file_path")):
68+
if (urls_from_csv := cls.data_from_csv(input_file_path, url_column_name)):
69+
ids += [cls.video_id_from_url(url) for url in urls_from_csv]
70+
if (ids_from_csv := cls.data_from_csv(input_file_path, id_column_name)):
71+
ids += ids_from_csv
8672

8773
if not ids and not urls:
8874
raise Exception("Either 'ids' or 'urls' must be provided for the video-search command")
@@ -95,7 +81,7 @@ def execute(cls: Self, **kwargs) -> str:
9581
# Remove duplicated
9682
ids = list(set(ids))
9783
videos_infos = list(youtube.videos_infos([_id for _id in ids if _id]))
98-
84+
9985
return cls.data_to_csv(
10086
data=[
10187
VideoSearch.filter_fields(

0 commit comments

Comments
 (0)