| |
@@ -16,9 +16,11 @@
|
| |
"""This test suite contains tests on fegistry.views."""
|
| |
|
| |
import json
|
| |
+ import mock
|
| |
import unittest
|
| |
|
| |
import flask
|
| |
+ import requests
|
| |
|
| |
from fegistry import views
|
| |
|
| |
@@ -30,6 +32,17 @@
|
| |
self.app = views.app.test_client()
|
| |
|
| |
|
| |
+ class CachedViewTestCase(ViewsTestCase):
|
| |
+ """A superclass for testing cached views. It invalidates the cache before and after the test."""
|
| |
+ def setUp(self):
|
| |
+ super(CachedViewTestCase, self).setUp()
|
| |
+ views.region.invalidate(hard=True)
|
| |
+
|
| |
+ def tearDown(self):
|
| |
+ super(CachedViewTestCase, self).tearDown()
|
| |
+ views.region.invalidate(hard=True)
|
| |
+
|
| |
+
|
| |
class TestAddDockerHeaders(unittest.TestCase):
|
| |
"""This test class contains tests on the add_docker_headers() function."""
|
| |
def test_add_docker_headers(self):
|
| |
@@ -41,6 +54,135 @@
|
| |
self.assertEqual(response.headers['Docker-Distribution-API-Version'], 'registry/2.0')
|
| |
|
| |
|
| |
+ class TestGetFromBackendRegistry(CachedViewTestCase):
|
| |
+ """This test class contains tests for the _get_from_backend_registry() function."""
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com:123/'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_200(self, get):
|
| |
+ """
|
| |
+ Ensure correct operation of the _get_from_backend_registry() function when the backend gives
|
| |
+ a 200 status code.
|
| |
+ """
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = \
|
| |
+ b'{"name":"fedora","tags":["24","latest","25","26","rawhide"]}\n'
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '61',
|
| |
+ 'Content-Type': 'application/json; charset=utf-8',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 200
|
| |
+
|
| |
+ text, status_code, headers = views._get_from_backend_registry('/v2/fedora/tags/list')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com:123/v2/fedora/tags/list')
|
| |
+ self.assertEqual(status_code, 200)
|
| |
+ self.assertEqual(json.loads(text),
|
| |
+ {'name': 'fedora', 'tags': ['24', 'latest', '25', '26', 'rawhide']})
|
| |
+ expected_headers = {'Content-Type': 'application/json; charset=utf-8'}
|
| |
+ self.assertEqual(headers, expected_headers)
|
| |
+
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com/'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_404(self, get):
|
| |
+ """
|
| |
+ Ensure correct operation of the _get_from_backend_registry() function when the backend
|
| |
+ gives a 404 status code.
|
| |
+ """
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = (
|
| |
+ b'{"errors":[{"code":"NAME_UNKNOWN","message":"repository name not known to registry",'
|
| |
+ b'"detail":{"name":"meaning/of/life"}}]}\n')
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '123',
|
| |
+ 'Content-Type': 'application/json; charset=utf-8',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 404
|
| |
+
|
| |
+ text, status_code, headers = views._get_from_backend_registry(
|
| |
+ '/v2/meaning/of/life/manifests/latest')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com/v2/meaning/of/life/manifests/latest')
|
| |
+ self.assertEqual(status_code, 404)
|
| |
+ self.assertEqual(
|
| |
+ json.loads(text),
|
| |
+ {'errors': [{'code': 'NAME_UNKNOWN', 'message': 'repository name not known to registry',
|
| |
+ 'detail': {'name': 'meaning/of/life'}}]})
|
| |
+ expected_headers = {'Content-Type': 'application/json; charset=utf-8'}
|
| |
+ self.assertEqual(headers, expected_headers)
|
| |
+
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com:1234'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_cache(self, get):
|
| |
+ """Ensure that the function caches responses."""
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = \
|
| |
+ b'{"name":"fedora","tags":["24","latest","25","26","rawhide"]}\n'
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '61',
|
| |
+ 'Content-Type': 'application/json; charset=utf-8',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 200
|
| |
+ # Make one call to the function to get the response cached.
|
| |
+ views._get_from_backend_registry('/v2/fedora/tags/list')
|
| |
+ # Now let's alter what the backend would return so we can make sure the cached version is
|
| |
+ # used.
|
| |
+ get.return_value._content = b'This should not get used yet because the cache should be hit'
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({'Not': 'Yet'})
|
| |
+
|
| |
+ text, status_code, headers = views._get_from_backend_registry('/v2/fedora/tags/list')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com:1234/v2/fedora/tags/list')
|
| |
+ self.assertEqual(status_code, 200)
|
| |
+ self.assertEqual(json.loads(text),
|
| |
+ {'name': 'fedora', 'tags': ['24', 'latest', '25', '26', 'rawhide']})
|
| |
+ expected_headers = {'Content-Type': 'application/json; charset=utf-8'}
|
| |
+ self.assertEqual(headers, expected_headers)
|
| |
+
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com:1234'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_content_type_header_not_present(self, get):
|
| |
+ """
|
| |
+ Ensure correct operation of the list_tags view when the backend doesn't give us a
|
| |
+ Content-Type header.
|
| |
+ """
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = \
|
| |
+ b'{"name":"fedora/cockpit","tags":["24","latest","25","26","rawhide"]}\n'
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '69',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 200
|
| |
+
|
| |
+ text, status_code, headers = views._get_from_backend_registry(
|
| |
+ '/v2/fedora/cockpit/manifests/latest')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com:1234/v2/fedora/cockpit/manifests/latest')
|
| |
+ self.assertEqual(status_code, 200)
|
| |
+ self.assertEqual(
|
| |
+ json.loads(text),
|
| |
+ {'name': 'fedora/cockpit', 'tags': ['24', 'latest', '25', '26', 'rawhide']})
|
| |
+ # Even though the backend didn't give us the Content-Type header, the function should have
|
| |
+ # added it for us.
|
| |
+ expected_headers = {'Content-Type': 'application/json; charset=utf-8'}
|
| |
+ self.assertEqual(headers, expected_headers)
|
| |
+
|
| |
+
|
| |
class Testv2(ViewsTestCase):
|
| |
"""This test class tests the v2() function."""
|
| |
def test_v2(self):
|
| |
@@ -51,3 +193,98 @@
|
| |
self.assertEqual(json.loads(response.get_data().decode('utf-8')), {})
|
| |
self.assertEqual(response.headers['Docker-Distribution-API-Version'], 'registry/2.0')
|
| |
self.assertEqual(response.headers['Content-Type'], 'application/json')
|
| |
+
|
| |
+
|
| |
+ class TestListTags(CachedViewTestCase):
|
| |
+ """This test class contains tests for the list_tags() function."""
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_200(self, get):
|
| |
+ """Ensure correct operation of the list_tags view when the backend gives a 200 status code.
|
| |
+ """
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = \
|
| |
+ b'{"name":"fedora","tags":["24","latest","25","26","rawhide"]}\n'
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '61',
|
| |
+ 'Content-Type': 'application/json; charset=utf-8',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 200
|
| |
+
|
| |
+ response = self.app.get('/v2/fedora/tags/list')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com/v2/fedora/tags/list')
|
| |
+ self.assertEqual(response.status_code, 200)
|
| |
+ self.assertEqual(json.loads(response.get_data().decode('utf-8')),
|
| |
+ {'name': 'fedora', 'tags': ['24', 'latest', '25', '26', 'rawhide']})
|
| |
+ expected_headers = {
|
| |
+ 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '61',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0'}
|
| |
+ self.assertEqual(dict(response.headers), expected_headers)
|
| |
+
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com/'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_404(self, get):
|
| |
+ """Ensure correct operation of the list_tags view when the backend gives a 404 status code.
|
| |
+ """
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = (
|
| |
+ b'{"errors":[{"code":"NAME_UNKNOWN","message":"repository name not known to registry",'
|
| |
+ b'"detail":{"name":"meaning/of/life"}}]}\n')
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '123',
|
| |
+ 'Content-Type': 'application/json; charset=utf-8',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 404
|
| |
+
|
| |
+ response = self.app.get('/v2/meaning/of/life/tags/list')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com/v2/meaning/of/life/tags/list')
|
| |
+ self.assertEqual(response.status_code, 404)
|
| |
+ self.assertEqual(
|
| |
+ json.loads(response.get_data().decode('utf-8')),
|
| |
+ {'errors': [{'code': 'NAME_UNKNOWN', 'message': 'repository name not known to registry',
|
| |
+ 'detail': {'name': 'meaning/of/life'}}]})
|
| |
+ expected_headers = {
|
| |
+ 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '123',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0'}
|
| |
+ self.assertEqual(dict(response.headers), expected_headers)
|
| |
+
|
| |
+ @mock.patch.dict('fegistry.views.app.config', {'BACKEND_REGISTRY': 'http://example.com:1234'})
|
| |
+ @mock.patch('fegistry.views.requests.get')
|
| |
+ def test_content_type_header_not_present(self, get):
|
| |
+ """
|
| |
+ Ensure correct operation of the list_tags view when the backend doesn't give us a
|
| |
+ Content-Type header.
|
| |
+ """
|
| |
+ get.return_value = requests.Response()
|
| |
+ get.return_value._content = \
|
| |
+ b'{"name":"fedora/cockpit","tags":["24","latest","25","26","rawhide"]}\n'
|
| |
+ get.return_value.headers = requests.structures.CaseInsensitiveDict({
|
| |
+ 'AppServer': 'proxy12.fedoraproject.org', 'AppTime': 'D=157698',
|
| |
+ 'Connection': 'Keep-Alive', 'Content-Length': '69',
|
| |
+ 'Date': 'Thu, 12 Jan 2017 14:57:47 GMT',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0', 'Keep-Alive': 'timeout=15, max=500',
|
| |
+ 'Server': 'Apache/2.4.6 (Red Hat Enterprise Linux)',
|
| |
+ 'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload'})
|
| |
+ get.return_value.status_code = 200
|
| |
+
|
| |
+ response = self.app.get('/v2/fedora/cockpit/tags/list')
|
| |
+
|
| |
+ get.assert_called_once_with('http://example.com:1234/v2/fedora/cockpit/tags/list')
|
| |
+ self.assertEqual(response.status_code, 200)
|
| |
+ self.assertEqual(
|
| |
+ json.loads(response.get_data().decode('utf-8')),
|
| |
+ {'name': 'fedora/cockpit', 'tags': ['24', 'latest', '25', '26', 'rawhide']})
|
| |
+ expected_headers = {
|
| |
+ 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '69',
|
| |
+ 'Docker-Distribution-API-Version': 'registry/2.0'}
|
| |
+ self.assertEqual(dict(response.headers), expected_headers)
|
| |
To be honest, I'm not sure I really like this default. The problem is that if someone deploys their own, and forgets to configure, they get a real (working) registry that gives us lots of requests.
I would rather have this use http://example.com/ or the like, and have people explicitly configure it.