diff --git a/blenderpack.py b/blenderpack.py index 6d8096e..f70332c 100755 --- a/blenderpack.py +++ b/blenderpack.py @@ -21,7 +21,37 @@ SCHEMA_VERSION = 1 class BadAddon(Exception): pass -def fetch(url, pipe): + +# TODO add repolist class + +def load_repo(url): + """ + Searches local repo.json for repository with a matching 'url' and returns it + """ + pass #TODO + +def write_repo(repo): + """ + Writes repo to local repolist json + """ + pass #TODO + +def fetch_repo(url: str) -> dict: + """ Requests repo.json from URL and embeds etag/last-modification headers""" + local_copy = load_repo(url) + headers = {} + if 'etag' in local_copy: headers['If-None-Match'] = local_copy['etag'] + if 'last-modified' in local_copy: headers['If-Modified-Since'] = local_copy['last-modified'] + + resp = requests.get(url, headers=headers) + + repo = json.loads(resp.json()) + repo['etag'] = resp.headers.get('etag') + repo['last-modified'] = resp.headers.get('last-modified') + + write_repo(repo) + +def refresh(url): # we have to explicitly close the end of the pipe we are NOT using, # otherwise no exception will be generated when the other process closes its end. pipe[0].close() @@ -30,18 +60,8 @@ def fetch(url, pipe): local_repo_path.mkdir(exist_ok=True) try: - # TODO: do conditional request - re = requests.get(url) - + fetch_repojson(url) pipe[1].send(re.status_code) - - repo = re.json() - repo['etag'] = re.headers.get('etag') - repo['last-modified'] = re.headers.get('last-modified') - - # just stick it here for now.. - dump_repo(local_repo_path, repo) - finally: pipe[1].close() diff --git a/tests/test_refresh_repos.py b/tests/test_refresh_repos.py new file mode 100644 index 0000000..98dd42f --- /dev/null +++ b/tests/test_refresh_repos.py @@ -0,0 +1,72 @@ +import requests +import unittest +from unittest import mock +from blenderpack import fetch_repo, load_repo, write_repo +from datetime import datetime +import json + +# based on https://stackoverflow.com/a/28507806/2730823 + +# This method will be used by the mock to replace requests.get +def mocked_requests_get(*args, **kwargs): + cidict = requests.structures.CaseInsensitiveDict + req_headers = cidict(kwargs.get('headers')) + t_fmt = '%a, %m %b %Y %X %Z' + + class MockResponse: + def __init__(self, headers: cidict, status_code: int): + self.headers = headers + self.status_code = status_code + + def json(self): + return json.dumps({'url': 'http://someurl.tld/repo.json'}) + + if args[0] == 'http://someurl.tld/repo.json': + resp_headers = cidict({ + "ETag": '"2a0094b-b74-55326ced274f3"', + "Last-Modified": 'Sun, 13 Mar 2011 13:38:53 GMT', + }) + + if req_headers == {}: + resp_code = 200 + else: + req_headers = cidict(req_headers) + resp_code = 304 if req_headers.get('if-none-match', '') == resp_headers['etag']\ + or datetime.strptime(req_headers.get('if-modified-since', ''), t_fmt) < \ + datetime.strptime(resp_headers['last-modified'], t_fmt) \ + else 200 + return MockResponse(resp_headers, resp_code) + + return MockResponse(None, 404) + +_mocked_repo_storage = {} +def mocked_load_repo(*args, **kwargs): + global _mocked_repo_storage + if args[0] not in _mocked_repo_storage: + _mocked_repo_storage[args[0]] = {'url': args[0]} + + return _mocked_repo_storage[args[0]] + +def mocked_write_repo(*args, **kwargs): + global _mocked_repo_storage + _mocked_repo_storage[args[0]['url']] = args[0] + + +class fetch_url_twice(unittest.TestCase): + + @mock.patch('requests.get', side_effect=mocked_requests_get) + @mock.patch('blenderpack.load_repo', side_effect=mocked_load_repo) + @mock.patch('blenderpack.write_repo', side_effect=mocked_write_repo) + def test_fetch(self, mock_write, mock_load, mock_get): + fetch_repo('http://someurl.tld/repo.json') + mock_get.assert_called_with('http://someurl.tld/repo.json', headers={}) + + fetch_repo('http://someurl.tld/repo.json') + mock_get.assert_called_with('http://someurl.tld/repo.json', headers={ + 'If-None-Match': '"2a0094b-b74-55326ced274f3"', + 'If-Modified-Since': 'Sun, 13 Mar 2011 13:38:53 GMT' + }) + + +if __name__ == '__main__': + unittest.main()