3 # Allow direct execution
9 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
))))
15 from yt_dlp
.cookies
import YoutubeDLCookieJar
16 from yt_dlp
.dependencies
import certifi
17 from yt_dlp
.networking
import Response
18 from yt_dlp
.networking
._helper
import (
20 add_accept_encoding_header
,
22 make_socks_proxy_opts
,
26 from yt_dlp
.networking
.exceptions
import (
30 from yt_dlp
.socks
import ProxyType
31 from yt_dlp
.utils
.networking
import HTTPHeaderDict
33 TEST_DIR
= os
.path
.dirname(os
.path
.abspath(__file__
))
36 class TestNetworkingUtils
:
38 def test_select_proxy(self
):
40 'all': 'socks5://example.com',
41 'http': 'http://example.com:1080',
42 'no': 'bypass.example.com,yt-dl.org'
45 assert select_proxy('https://example.com', proxies
) == proxies
['all']
46 assert select_proxy('http://example.com', proxies
) == proxies
['http']
47 assert select_proxy('http://bypass.example.com', proxies
) is None
48 assert select_proxy('https://yt-dl.org', proxies
) is None
50 @pytest.mark
.parametrize('socks_proxy,expected', [
51 ('socks5h://example.com', {
52 'proxytype': ProxyType
.SOCKS5
,
53 'addr': 'example.com',
59 ('socks5://user:@example.com:5555', {
60 'proxytype': ProxyType
.SOCKS5
,
61 'addr': 'example.com',
67 ('socks4://u%40ser:pa%20ss@127.0.0.1:1080', {
68 'proxytype': ProxyType
.SOCKS4
,
75 ('socks4a://:pa%20ss@127.0.0.1', {
76 'proxytype': ProxyType
.SOCKS4A
,
84 def test_make_socks_proxy_opts(self
, socks_proxy
, expected
):
85 assert make_socks_proxy_opts(socks_proxy
) == expected
87 def test_make_socks_proxy_unknown(self
):
88 with pytest
.raises(ValueError, match
='Unknown SOCKS proxy version: socks'):
89 make_socks_proxy_opts('socks://127.0.0.1')
91 @pytest.mark
.skipif(not certifi
, reason
='certifi is not installed')
92 def test_load_certifi(self
):
93 context_certifi
= ssl
.SSLContext(ssl
.PROTOCOL_TLS_CLIENT
)
94 context_certifi
.load_verify_locations(cafile
=certifi
.where())
95 context
= ssl
.SSLContext(ssl
.PROTOCOL_TLS_CLIENT
)
96 ssl_load_certs(context
, use_certifi
=True)
97 assert context
.get_ca_certs() == context_certifi
.get_ca_certs()
99 context_default
= ssl
.SSLContext(ssl
.PROTOCOL_TLS_CLIENT
)
100 context_default
.load_default_certs()
101 context
= ssl
.SSLContext(ssl
.PROTOCOL_TLS_CLIENT
)
102 ssl_load_certs(context
, use_certifi
=False)
103 assert context
.get_ca_certs() == context_default
.get_ca_certs()
105 if context_default
.get_ca_certs() == context_certifi
.get_ca_certs():
106 pytest
.skip('System uses certifi as default. The test is not valid')
108 @pytest.mark
.parametrize('method,status,expected', [
110 ('HEAD', 303, 'HEAD'),
112 ('POST', 301, 'GET'),
113 ('HEAD', 301, 'HEAD'),
114 ('POST', 302, 'GET'),
115 ('HEAD', 302, 'HEAD'),
117 ('POST', 308, 'POST'),
118 ('POST', 307, 'POST'),
119 ('HEAD', 308, 'HEAD'),
120 ('HEAD', 307, 'HEAD'),
122 def test_get_redirect_method(self
, method
, status
, expected
):
123 assert get_redirect_method(method
, status
) == expected
125 @pytest.mark
.parametrize('headers,supported_encodings,expected', [
126 ({'Accept-Encoding': 'br'}, ['gzip', 'br'], {'Accept-Encoding': 'br'}),
127 ({}, ['gzip', 'br'], {'Accept-Encoding': 'gzip, br'}),
128 ({'Content-type': 'application/json'}, [], {'Content-type': 'application/json', 'Accept-Encoding': 'identity'}),
130 def test_add_accept_encoding_header(self
, headers
, supported_encodings
, expected
):
131 headers
= HTTPHeaderDict(headers
)
132 add_accept_encoding_header(headers
, supported_encodings
)
133 assert headers
== HTTPHeaderDict(expected
)
136 class TestInstanceStoreMixin
:
138 class FakeInstanceStoreMixin(InstanceStoreMixin
):
139 def _create_instance(self
, **kwargs
):
140 return random
.randint(0, 1000000)
142 def _close_instance(self
, instance
):
145 def test_mixin(self
):
146 mixin
= self
.FakeInstanceStoreMixin()
147 assert mixin
._get
_instance
(d
={'a': 1, 'b': 2, 'c': {'d', 4}}) == mixin
._get
_instance
(d
={'a': 1, 'b': 2, 'c': {'d', 4}})
149 assert mixin
._get
_instance
(d
={'a': 1, 'b': 2, 'c': {'e', 4}}) != mixin
._get
_instance
(d
={'a': 1, 'b': 2, 'c': {'d', 4}})
151 assert mixin
._get
_instance
(d
={'a': 1, 'b': 2, 'c': {'d', 4}} != mixin
._get
_instance
(d
={'a': 1, 'b': 2, 'g': {'d', 4}}))
153 assert mixin
._get
_instance
(d
={'a': 1}, e
=[1, 2, 3]) == mixin
._get
_instance
(d
={'a': 1}, e
=[1, 2, 3])
155 assert mixin
._get
_instance
(d
={'a': 1}, e
=[1, 2, 3]) != mixin
._get
_instance
(d
={'a': 1}, e
=[1, 2, 3, 4])
157 cookiejar
= YoutubeDLCookieJar()
158 assert mixin
._get
_instance
(b
=[1, 2], c
=cookiejar
) == mixin
._get
_instance
(b
=[1, 2], c
=cookiejar
)
160 assert mixin
._get
_instance
(b
=[1, 2], c
=cookiejar
) != mixin
._get
_instance
(b
=[1, 2], c
=YoutubeDLCookieJar())
163 assert mixin
._get
_instance
(c
=cookiejar
, b
=[1, 2]) == mixin
._get
_instance
(b
=[1, 2], c
=cookiejar
)
165 m
= mixin
._get
_instance
(t
=1234)
166 assert mixin
._get
_instance
(t
=1234) == m
167 mixin
._clear
_instances
()
168 assert mixin
._get
_instance
(t
=1234) != m
171 class TestNetworkingExceptions
:
174 def create_response(status
):
175 return Response(fp
=io
.BytesIO(b
'test'), url
='http://example.com', headers
={'tesT': 'test'}, status
=status
)
177 def test_http_error(self
):
179 response
= self
.create_response(403)
180 error
= HTTPError(response
)
182 assert error
.status
== 403
183 assert str(error
) == error
.msg
== 'HTTP Error 403: Forbidden'
184 assert error
.reason
== response
.reason
185 assert error
.response
is response
187 data
= error
.response
.read()
188 assert data
== b
'test'
189 assert repr(error
) == '<HTTPError 403: Forbidden>'
191 def test_redirect_http_error(self
):
192 response
= self
.create_response(301)
193 error
= HTTPError(response
, redirect_loop
=True)
194 assert str(error
) == error
.msg
== 'HTTP Error 301: Moved Permanently (redirect loop detected)'
195 assert error
.reason
== 'Moved Permanently'
197 def test_incomplete_read_error(self
):
198 error
= IncompleteRead(4, 3, cause
='test')
199 assert isinstance(error
, IncompleteRead
)
200 assert repr(error
) == '<IncompleteRead: 4 bytes read, 3 more expected>'
201 assert str(error
) == error
.msg
== '4 bytes read, 3 more expected'
202 assert error
.partial
== 4
203 assert error
.expected
== 3
204 assert error
.cause
== 'test'
206 error
= IncompleteRead(3)
207 assert repr(error
) == '<IncompleteRead: 3 bytes read>'
208 assert str(error
) == '3 bytes read'