[ie/vk] Improve format extraction (#9885)
[yt-dlp3.git] / test / test_networking_utils.py
blobb7b71430e7919a99906a5430fc5b88da17ba9a37
1 #!/usr/bin/env python3
3 # Allow direct execution
4 import os
5 import sys
7 import pytest
9 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11 import io
12 import random
13 import ssl
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 (
19 InstanceStoreMixin,
20 add_accept_encoding_header,
21 get_redirect_method,
22 make_socks_proxy_opts,
23 select_proxy,
24 ssl_load_certs,
26 from yt_dlp.networking.exceptions import (
27 HTTPError,
28 IncompleteRead,
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):
39 proxies = {
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',
54 'port': 1080,
55 'rdns': True,
56 'username': None,
57 'password': None
58 }),
59 ('socks5://user:@example.com:5555', {
60 'proxytype': ProxyType.SOCKS5,
61 'addr': 'example.com',
62 'port': 5555,
63 'rdns': False,
64 'username': 'user',
65 'password': ''
66 }),
67 ('socks4://u%40ser:pa%20ss@127.0.0.1:1080', {
68 'proxytype': ProxyType.SOCKS4,
69 'addr': '127.0.0.1',
70 'port': 1080,
71 'rdns': False,
72 'username': 'u@ser',
73 'password': 'pa ss'
74 }),
75 ('socks4a://:pa%20ss@127.0.0.1', {
76 'proxytype': ProxyType.SOCKS4A,
77 'addr': '127.0.0.1',
78 'port': 1080,
79 'rdns': True,
80 'username': '',
81 'password': 'pa ss'
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', [
109 ('GET', 303, 'GET'),
110 ('HEAD', 303, 'HEAD'),
111 ('PUT', 303, 'GET'),
112 ('POST', 301, 'GET'),
113 ('HEAD', 301, 'HEAD'),
114 ('POST', 302, 'GET'),
115 ('HEAD', 302, 'HEAD'),
116 ('PUT', 302, 'PUT'),
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):
143 pass
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())
162 # Different order
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:
173 @staticmethod
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'