Skip to content

Commit b55457c

Browse files
Add test cases
1 parent 93f71bf commit b55457c

10 files changed

Lines changed: 1087 additions & 21 deletions

File tree

pypodget/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def main():
3333
action='store_true', default=False,
3434
help='Silent operation')
3535
parser.add_argument('--version', '-v',
36-
action='version', version='%(prog)s 0.1.1')
36+
action='version', version='%(prog)s 0.1.2')
3737
args = parser.parse_args()
3838

3939
verbose = not args.silent
@@ -58,6 +58,7 @@ def main():
5858
except Exception as err:
5959
print("Mandatory key missing")
6060
print(err)
61+
continue
6162

6263
if "filename" in config[pod]:
6364
filename_template = config[pod]["filename"]

pypodget/download.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,29 @@ def pod_download(url, filename):
3636
3737
3838
"""
39-
r = requests.get(url, stream=verbose, allow_redirects=True)
39+
r = requests.get(url, stream=verbose(), allow_redirects=True)
4040
disp_filename = filename.split(os.sep)[-1]
4141

4242
try:
4343
if verbose():
44-
file_size = int(r.headers['Content-Length'])
45-
chunk = 1
46-
chunk_size = 1024
47-
num_bars = int(file_size / chunk_size)
44+
content_length = r.headers.get('Content-Length')
45+
if content_length:
46+
file_size = int(content_length)
47+
chunk_size = 1024
48+
num_bars = int(file_size / chunk_size)
49+
else:
50+
num_bars = None
4851

4952
with open(filename, 'wb') as fp:
50-
for chunk in tqdm.tqdm(r.iter_content(chunk_size=chunk_size),
53+
for chunk in tqdm.tqdm(r.iter_content(chunk_size=1024),
5154
total=num_bars,
5255
unit='KB',
5356
desc=disp_filename,
5457
leave=True):
5558
fp.write(chunk)
5659
else:
57-
open(filename, 'wb').write(r.content)
60+
with open(filename, 'wb') as f:
61+
f.write(r.content)
5862
except KeyboardInterrupt as ki:
5963
if os.path.exists(filename):
6064
os.remove(filename)

pypodget/podcast.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def download(self):
4747

4848
eyed3.log.setLevel("ERROR")
4949
audiofile = eyed3.load(self.__local_filename)
50+
if audiofile.tag is None:
51+
audiofile.initTag()
5052
if not audiofile.tag.artist:
5153
audiofile.tag.artist = self.__parent.title
5254
if not audiofile.tag.title:
@@ -74,24 +76,27 @@ def update(self):
7476
.format(self.__url, r.status_code))
7577

7678
feed = ElementTree.fromstring(r.content)
77-
if feed.findall('channel/title') is not None:
78-
self.__feed_title = feed.findall('channel/title')[0].text
79+
channel_title = feed.findall('channel/title')
80+
if channel_title:
81+
self.__feed_title = channel_title[0].text
7982
else:
8083
self.__feed_title = None
8184

82-
if feed.findall('channel/description') is not None:
83-
txt = feed.findall('channel/description')[0].text
84-
self.__feed_description = txt
85+
channel_desc = feed.findall('channel/description')
86+
if channel_desc:
87+
self.__feed_description = channel_desc[0].text
8588
else:
8689
self.__feed_description = None
8790

88-
if feed.findall('channel/link') is not None:
89-
self.__feed_link = feed.findall('channel/link')[0].text
91+
channel_link = feed.findall('channel/link')
92+
if channel_link:
93+
self.__feed_link = channel_link[0].text
9094
else:
9195
self.__feed_link = None
9296

93-
if feed.findall('channel/image/url') is not None:
94-
self.__feed_image = feed.findall('channel/image/url')[-1].text
97+
channel_image = feed.findall('channel/image/url')
98+
if channel_image:
99+
self.__feed_image = channel_image[-1].text
95100
else:
96101
self.__feed_image = None
97102

@@ -102,8 +107,9 @@ def update(self):
102107

103108
for feed_item in feed.iter("item"):
104109
title = feed_item.find("title").text
105-
if feed_item.find('description'):
106-
description = feed_item.find('description').text
110+
desc_elem = feed_item.find('description')
111+
if desc_elem is not None and desc_elem.text is not None:
112+
description = desc_elem.text
107113
else:
108114
description = ""
109115

@@ -112,17 +118,19 @@ def update(self):
112118

113119
if feed_item.find("enclosure") is None:
114120
epi_url = None
121+
ext = ""
115122
else:
116123
epi_url = feed_item.find("enclosure").attrib["url"]
124+
ext = (epi_url.split('?')[0]).split('.')[-1]
117125

118126
title_unicode = title.replace('\'', '').replace('\"', '')
119127
title_unicode = title_unicode.replace('\\', '')
120128
title_unicode = title_unicode.replace('/', '')
121129
title_unicode = title_unicode.replace(':', '')
122130
title_unicode = title_unicode.replace('!', '')
123131

124-
link = feed_item.find('link').text
125-
ext = (epi_url.split('?')[0]).split('.')[-1]
132+
link_elem = feed_item.find('link')
133+
link = link_elem.text if link_elem is not None else ""
126134

127135
filename = self.__filename_template_instance.substitute(
128136
year="{:04}".format(pubdate.year),
@@ -142,6 +150,9 @@ def update(self):
142150
filename = filename.replace(':', '_')
143151
filename = filename.replace('?', '_')
144152
filename = filename.replace('*', '_')
153+
filename = filename.replace('>', '_')
154+
filename = filename.replace('<', '_')
155+
filename = filename.replace('|', '_')
145156

146157
filename = self.__folder + os.sep + filename
147158

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,9 @@ dependencies = [
2929
[project.scripts]
3030
pypodget = "pypodget.cli:main"
3131

32+
[project.optional-dependencies]
33+
test = ["pytest>=7.0"]
34+
35+
[tool.pytest.ini_options]
36+
testpaths = ["tests"]
37+

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import pytest
2+
from unittest.mock import MagicMock
3+
4+
5+
SAMPLE_RSS_XML = """<?xml version="1.0" encoding="utf-8"?>
6+
<rss version="2.0">
7+
<channel>
8+
<title>Test Podcast</title>
9+
<link>https://example.com/</link>
10+
<description>A test podcast feed</description>
11+
<image>
12+
<url>https://example.com/image.png</url>
13+
<title>Test Image</title>
14+
<link>https://example.com/</link>
15+
</image>
16+
<item>
17+
<title>Episode One</title>
18+
<link>https://example.com/episode-one</link>
19+
<description>Description of episode one</description>
20+
<pubDate>Mon, 31 Jan 2022 18:43:00 +0100</pubDate>
21+
<enclosure url="https://example.com/ep1.mp3"
22+
type="audio/mpeg" length="1000"/>
23+
</item>
24+
<item>
25+
<title>Episode Two</title>
26+
<link>https://example.com/episode-two</link>
27+
<description>Description of episode two</description>
28+
<pubDate>Fri, 28 Jan 2022 10:00:00 +0100</pubDate>
29+
<enclosure url="https://example.com/ep2.ogg"
30+
type="audio/ogg" length="2000"/>
31+
</item>
32+
</channel>
33+
</rss>"""
34+
35+
RSS_NO_DESCRIPTION = """<?xml version="1.0" encoding="utf-8"?>
36+
<rss version="2.0">
37+
<channel>
38+
<title>Test Podcast</title>
39+
<link>https://example.com/</link>
40+
<image>
41+
<url>https://example.com/image.png</url>
42+
<title>Test</title>
43+
<link>https://example.com/</link>
44+
</image>
45+
<item>
46+
<title>Episode One</title>
47+
<link>https://example.com/episode-one</link>
48+
<pubDate>Mon, 31 Jan 2022 18:43:00 +0100</pubDate>
49+
<enclosure url="https://example.com/ep1.mp3"
50+
type="audio/mpeg" length="1000"/>
51+
</item>
52+
</channel>
53+
</rss>"""
54+
55+
RSS_NO_LINK = """<?xml version="1.0" encoding="utf-8"?>
56+
<rss version="2.0">
57+
<channel>
58+
<title>Test Podcast</title>
59+
<description>A test feed</description>
60+
<image>
61+
<url>https://example.com/image.png</url>
62+
<title>Test</title>
63+
<link>https://example.com/</link>
64+
</image>
65+
<item>
66+
<title>Episode One</title>
67+
<link>https://example.com/episode-one</link>
68+
<pubDate>Mon, 31 Jan 2022 18:43:00 +0100</pubDate>
69+
<enclosure url="https://example.com/ep1.mp3"
70+
type="audio/mpeg" length="1000"/>
71+
</item>
72+
</channel>
73+
</rss>"""
74+
75+
RSS_NO_IMAGE = """<?xml version="1.0" encoding="utf-8"?>
76+
<rss version="2.0">
77+
<channel>
78+
<title>Test Podcast</title>
79+
<link>https://example.com/</link>
80+
<description>A test feed</description>
81+
<item>
82+
<title>Episode One</title>
83+
<link>https://example.com/episode-one</link>
84+
<pubDate>Mon, 31 Jan 2022 18:43:00 +0100</pubDate>
85+
<enclosure url="https://example.com/ep1.mp3"
86+
type="audio/mpeg" length="1000"/>
87+
</item>
88+
</channel>
89+
</rss>"""
90+
91+
RSS_NO_ENCLOSURE = """<?xml version="1.0" encoding="utf-8"?>
92+
<rss version="2.0">
93+
<channel>
94+
<title>Test Podcast</title>
95+
<link>https://example.com/</link>
96+
<description>A test feed</description>
97+
<image>
98+
<url>https://example.com/image.png</url>
99+
<title>Test</title>
100+
<link>https://example.com/</link>
101+
</image>
102+
<item>
103+
<title>Episode Without Audio</title>
104+
<link>https://example.com/episode</link>
105+
<description>No audio file</description>
106+
<pubDate>Mon, 31 Jan 2022 18:43:00 +0100</pubDate>
107+
</item>
108+
</channel>
109+
</rss>"""
110+
111+
RSS_ITEM_NO_LINK = """<?xml version="1.0" encoding="utf-8"?>
112+
<rss version="2.0">
113+
<channel>
114+
<title>Test Podcast</title>
115+
<link>https://example.com/</link>
116+
<description>A test feed</description>
117+
<image>
118+
<url>https://example.com/image.png</url>
119+
<title>Test</title>
120+
<link>https://example.com/</link>
121+
</image>
122+
<item>
123+
<title>Episode Without Link</title>
124+
<description>No link element</description>
125+
<pubDate>Mon, 31 Jan 2022 18:43:00 +0100</pubDate>
126+
<enclosure url="https://example.com/ep.mp3"
127+
type="audio/mpeg" length="1000"/>
128+
</item>
129+
</channel>
130+
</rss>"""
131+
132+
RSS_EMPTY_ITEMS = """<?xml version="1.0" encoding="utf-8"?>
133+
<rss version="2.0">
134+
<channel>
135+
<title>Test Podcast</title>
136+
<link>https://example.com/</link>
137+
<description>A test feed</description>
138+
<image>
139+
<url>https://example.com/image.png</url>
140+
<title>Test</title>
141+
<link>https://example.com/</link>
142+
</image>
143+
</channel>
144+
</rss>"""
145+
146+
147+
def _make_mock_response(content, status_code=200):
148+
mock_resp = MagicMock()
149+
mock_resp.status_code = status_code
150+
mock_resp.content = (
151+
content.encode("utf-8") if isinstance(content, str)
152+
else content
153+
)
154+
mock_resp.headers = {"Content-Length": "1024"}
155+
mock_resp.iter_content = (
156+
lambda chunk_size=1024: iter(
157+
[content.encode("utf-8")[:chunk_size]]
158+
)
159+
)
160+
return mock_resp
161+
162+
163+
@pytest.fixture(autouse=True)
164+
def reset_verbose():
165+
import pypodget.globals as g
166+
original = g.__verbose__
167+
g.__verbose__ = True
168+
yield
169+
g.__verbose__ = original
170+
171+
172+
@pytest.fixture
173+
def sample_rss_xml():
174+
return SAMPLE_RSS_XML
175+
176+
177+
@pytest.fixture
178+
def mock_requests_get(sample_rss_xml):
179+
with pytest.importorskip("unittest.mock").patch(
180+
"pypodget.podcast.requests.get"
181+
) as mock_get:
182+
mock_get.return_value = _make_mock_response(sample_rss_xml)
183+
yield mock_get
184+
185+
186+
@pytest.fixture
187+
def tmp_folder(tmp_path):
188+
return str(tmp_path)
189+
190+
191+
@pytest.fixture
192+
def sample_config_file(tmp_path):
193+
config_text = """[TestPodcast]
194+
url = https://example.com/feed.xml
195+
directory = ./test_output/
196+
filename = $year-$month-$day - $title.$ext
197+
"""
198+
p = tmp_path / "test_config.ini"
199+
p.write_text(config_text)
200+
return str(p)
201+
202+
203+
@pytest.fixture
204+
def config_missing_url(tmp_path):
205+
config_text = """[TestPodcast]
206+
directory = ./test_output/
207+
"""
208+
p = tmp_path / "bad_config.ini"
209+
p.write_text(config_text)
210+
return str(p)
211+
212+
213+
@pytest.fixture
214+
def config_missing_directory(tmp_path):
215+
config_text = """[TestPodcast]
216+
url = https://example.com/feed.xml
217+
"""
218+
p = tmp_path / "bad_config2.ini"
219+
p.write_text(config_text)
220+
return str(p)

0 commit comments

Comments
 (0)