mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-10-21 11:08:44 +00:00
164 lines
6.2 KiB
Python
164 lines
6.2 KiB
Python
import hashlib
|
|
import io
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from utils import update_check
|
|
from utils.update_check import _extract_assets, _parse_version_tuple, _version_gt
|
|
|
|
|
|
class ParseVersionTupleTest(unittest.TestCase):
|
|
def test_plain_and_prefixed_versions(self):
|
|
self.assertEqual(_parse_version_tuple('1.9.1'), (1, 9, 1))
|
|
self.assertEqual(_parse_version_tuple('v1.9.1'), (1, 9, 1))
|
|
self.assertEqual(_parse_version_tuple(' V2.0 '), (2, 0))
|
|
|
|
def test_trailing_suffixes_are_truncated_to_digits(self):
|
|
self.assertEqual(_parse_version_tuple('1.9.1rc2'), (1, 9, 1))
|
|
self.assertEqual(_parse_version_tuple('1.9.1-beta'), (1, 9, 1))
|
|
|
|
def test_empty_and_non_numeric_segments(self):
|
|
self.assertEqual(_parse_version_tuple(''), (0,))
|
|
self.assertEqual(_parse_version_tuple(None), (0,))
|
|
self.assertEqual(_parse_version_tuple('1.x.3'), (1, 0, 3))
|
|
|
|
|
|
class VersionGtTest(unittest.TestCase):
|
|
def test_newer_versions(self):
|
|
self.assertTrue(_version_gt('1.9.2', '1.9.1'))
|
|
self.assertTrue(_version_gt('1.10.0', '1.9.9'))
|
|
self.assertTrue(_version_gt('2.0', '1.9.9'))
|
|
|
|
def test_equal_and_older_versions(self):
|
|
self.assertFalse(_version_gt('1.9.1', '1.9.1'))
|
|
self.assertFalse(_version_gt('1.9.1', '1.9.2'))
|
|
self.assertFalse(_version_gt('1.9', '1.9.0'))
|
|
|
|
def test_shorter_version_is_padded_with_zeros(self):
|
|
self.assertTrue(_version_gt('1.9.1', '1.9'))
|
|
self.assertFalse(_version_gt('1.9', '1.9.1'))
|
|
|
|
|
|
class ExtractAssetsTest(unittest.TestCase):
|
|
def test_keeps_name_url_and_digest(self):
|
|
data = {'assets': [{
|
|
'name': 'TgWsProxy_windows.exe',
|
|
'browser_download_url': 'https://example.invalid/a.exe',
|
|
'digest': 'sha256:abc',
|
|
}]}
|
|
self.assertEqual(_extract_assets(data), [{
|
|
'name': 'TgWsProxy_windows.exe',
|
|
'url': 'https://example.invalid/a.exe',
|
|
'digest': 'sha256:abc',
|
|
}])
|
|
|
|
def test_drops_entries_without_name_or_url(self):
|
|
data = {'assets': [
|
|
{'name': 'a.exe'},
|
|
{'browser_download_url': 'https://example.invalid/b.exe'},
|
|
]}
|
|
self.assertEqual(_extract_assets(data), [])
|
|
|
|
def test_missing_digest_becomes_empty_string(self):
|
|
data = {'assets': [{
|
|
'name': 'a.exe', 'browser_download_url': 'https://example.invalid/a.exe',
|
|
}]}
|
|
self.assertEqual(_extract_assets(data)[0]['digest'], '')
|
|
|
|
def test_empty_input(self):
|
|
self.assertEqual(_extract_assets(None), [])
|
|
self.assertEqual(_extract_assets({}), [])
|
|
|
|
|
|
class _Response(io.BytesIO):
|
|
def __init__(self, body, content_length='auto'):
|
|
super().__init__(body)
|
|
if content_length == 'auto':
|
|
content_length = len(body)
|
|
self.headers = {} if content_length is None else {
|
|
'Content-Length': str(content_length),
|
|
}
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *exc):
|
|
self.close()
|
|
return False
|
|
|
|
|
|
def _opener(response):
|
|
opener = mock.Mock()
|
|
opener.open.return_value = response
|
|
return mock.Mock(return_value=opener)
|
|
|
|
|
|
class DownloadAssetTest(unittest.TestCase):
|
|
def _download(self, response, digest=''):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
with mock.patch.object(update_check, 'build_github_opener', _opener(response)):
|
|
path = update_check.download_asset(
|
|
'https://example.invalid/a', Path(tmp), suffix='.bin', digest=digest,
|
|
)
|
|
try:
|
|
return path.read_bytes(), sorted(p.name for p in Path(tmp).iterdir())
|
|
finally:
|
|
path.unlink(missing_ok=True)
|
|
|
|
def test_complete_download_is_kept(self):
|
|
body, files = self._download(_Response(b'payload'))
|
|
self.assertEqual(body, b'payload')
|
|
self.assertEqual(len(files), 1)
|
|
|
|
def test_matching_digest_is_accepted(self):
|
|
digest = 'sha256:' + hashlib.sha256(b'payload').hexdigest()
|
|
body, _ = self._download(_Response(b'payload'), digest=digest)
|
|
self.assertEqual(body, b'payload')
|
|
|
|
def test_unknown_digest_format_is_ignored(self):
|
|
body, _ = self._download(_Response(b'payload'), digest='md5:whatever')
|
|
self.assertEqual(body, b'payload')
|
|
|
|
def test_truncated_download_raises_and_leaves_no_file(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
resp = _Response(b'half', content_length=99)
|
|
with mock.patch.object(update_check, 'build_github_opener', _opener(resp)):
|
|
with self.assertRaises(OSError):
|
|
update_check.download_asset(
|
|
'https://example.invalid/a', Path(tmp), suffix='.bin',
|
|
)
|
|
self.assertEqual(list(Path(tmp).iterdir()), [])
|
|
|
|
def test_digest_mismatch_raises_and_leaves_no_file(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
resp = _Response(b'payload')
|
|
with mock.patch.object(update_check, 'build_github_opener', _opener(resp)):
|
|
with self.assertRaises(OSError):
|
|
update_check.download_asset(
|
|
'https://example.invalid/a', Path(tmp),
|
|
suffix='.bin', digest='sha256:' + '0' * 64,
|
|
)
|
|
self.assertEqual(list(Path(tmp).iterdir()), [])
|
|
|
|
def test_missing_content_length_is_tolerated(self):
|
|
body, _ = self._download(_Response(b'payload', content_length=None))
|
|
self.assertEqual(body, b'payload')
|
|
|
|
|
|
class FindAssetTest(unittest.TestCase):
|
|
def test_returns_asset_with_digest(self):
|
|
assets = [{'name': 'a.deb', 'url': 'https://example.invalid/a.deb', 'digest': 'sha256:ab'}]
|
|
with mock.patch.dict(update_check._state, {'assets': assets}):
|
|
self.assertEqual(update_check.find_asset('a.deb'), assets[0])
|
|
self.assertIsNone(update_check.find_asset('b.deb'))
|
|
|
|
def test_ignores_asset_without_url(self):
|
|
with mock.patch.dict(update_check._state, {'assets': [{'name': 'a.deb', 'url': ''}]}):
|
|
self.assertIsNone(update_check.find_asset('a.deb'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|