From a7fd6e99d90d2c991aa1cc97b9fb0ba7d153bd8b Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 30 Aug 2019 11:55:19 +1000 Subject: Refactor galaxy collection API for v3 support (#61510) * Refactor galaxy collection API for v3 support * Added unit tests for GalaxyAPI and starting to fix other failures * finalise tests * more unit test fixes --- test/units/galaxy/test_api.py | 749 ++++++++++++++++++++++++++- test/units/galaxy/test_collection.py | 537 +------------------ test/units/galaxy/test_collection_install.py | 305 +++-------- 3 files changed, 841 insertions(+), 750 deletions(-) (limited to 'test/units/galaxy') diff --git a/test/units/galaxy/test_api.py b/test/units/galaxy/test_api.py index 7def7cb0bb..c2439b9b3f 100644 --- a/test/units/galaxy/test_api.py +++ b/test/units/galaxy/test_api.py @@ -6,13 +6,26 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import json +import os +import re import pytest +import tarfile +import tempfile +import time + +from io import BytesIO, StringIO +from units.compat.mock import MagicMock from ansible import context from ansible.errors import AnsibleError -from ansible.galaxy.api import GalaxyAPI +from ansible.galaxy import api as galaxy_api +from ansible.galaxy.api import CollectionVersionMetadata, GalaxyAPI, GalaxyError from ansible.galaxy.token import GalaxyToken +from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.six.moves.urllib import error as urllib_error from ansible.utils import context_objects as co +from ansible.utils.display import Display @pytest.fixture(autouse='function') @@ -24,9 +37,34 @@ def reset_cli_args(): co.GlobalCLIArgs._Singleton__instance = None +@pytest.fixture() +def collection_artifact(tmp_path_factory): + ''' Creates a collection artifact tarball that is ready to be published ''' + output_dir = to_text(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Output')) + + tar_path = os.path.join(output_dir, 'namespace-collection-v1.0.0.tar.gz') + with tarfile.open(tar_path, 'w:gz') as tfile: + b_io = BytesIO(b"\x00\x01\x02\x03") + tar_info = tarfile.TarInfo('test') + tar_info.size = 4 + tar_info.mode = 0o0644 + tfile.addfile(tarinfo=tar_info, fileobj=b_io) + + yield tar_path + + +def get_test_galaxy_api(url, version): + api = GalaxyAPI(None, "test", url) + api._available_api_versions = {version: '/api/%s' % version} + api.token = GalaxyToken(token="my token") + + return api + + def test_api_no_auth(): api = GalaxyAPI(None, "test", "https://galaxy.ansible.com") - actual = api._auth_header(required=False) + actual = {} + api._add_auth_token(actual, "") assert actual == {} @@ -34,23 +72,722 @@ def test_api_no_auth_but_required(): expected = "No access token or username set. A token can be set with --api-key, with 'ansible-galaxy login', " \ "or set in ansible.cfg." with pytest.raises(AnsibleError, match=expected): - GalaxyAPI(None, "test", "https://galaxy.ansible.com")._auth_header() + GalaxyAPI(None, "test", "https://galaxy.ansible.com")._add_auth_token({}, "", required=True) def test_api_token_auth(): token = GalaxyToken(token=u"my_token") api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=token) - actual = api._auth_header() + actual = {} + api._add_auth_token(actual, "") + assert actual == {'Authorization': 'Token my_token'} + + +def test_api_token_auth_with_token_type(): + token = GalaxyToken(token=u"my_token") + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=token) + actual = {} + api._add_auth_token(actual, "", token_type="Bearer") + assert actual == {'Authorization': 'Bearer my_token'} + + +def test_api_token_auth_with_v3_url(): + token = GalaxyToken(token=u"my_token") + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=token) + actual = {} + api._add_auth_token(actual, "https://galaxy.ansible.com/api/v3/resource/name") + assert actual == {'Authorization': 'Bearer my_token'} + + +def test_api_token_auth_with_v2_url(): + token = GalaxyToken(token=u"my_token") + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=token) + actual = {} + # Add v3 to random part of URL but response should only see the v2 as the full URI path segment. + api._add_auth_token(actual, "https://galaxy.ansible.com/api/v2/resourcev3/name") assert actual == {'Authorization': 'Token my_token'} def test_api_basic_auth_password(): api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", username=u"user", password=u"pass") - actual = api._auth_header() + actual = {} + api._add_auth_token(actual, "") assert actual == {'Authorization': 'Basic dXNlcjpwYXNz'} def test_api_basic_auth_no_password(): api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", username=u"user",) - actual = api._auth_header() + actual = {} + api._add_auth_token(actual, "") assert actual == {'Authorization': 'Basic dXNlcjo='} + + +def test_api_dont_override_auth_header(): + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com") + actual = {'Authorization': 'Custom token'} + api._add_auth_token(actual, "") + assert actual == {'Authorization': 'Custom token'} + + +def test_initialise_galaxy(monkeypatch): + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(u'{"available_versions":{"v1":"/api/v1"}}'), + StringIO(u'{"token":"my token"}'), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com") + actual = api.authenticate("github_token") + + assert len(api.available_api_versions) == 2 + assert api.available_api_versions['v1'] == u'/api/v1' + assert api.available_api_versions['v2'] == u'/api/v2' + assert actual == {u'token': u'my token'} + assert mock_open.call_count == 2 + assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api' + assert mock_open.mock_calls[1][1][0] == 'https://galaxy.ansible.com/api/v1/tokens/' + assert mock_open.mock_calls[1][2]['data'] == 'github_token=github_token' + + +def test_initialise_galaxy_with_auth(monkeypatch): + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(u'{"available_versions":{"v1":"/api/v1"}}'), + StringIO(u'{"token":"my token"}'), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=GalaxyToken(token='my_token')) + actual = api.authenticate("github_token") + + assert len(api.available_api_versions) == 2 + assert api.available_api_versions['v1'] == u'/api/v1' + assert api.available_api_versions['v2'] == u'/api/v2' + assert actual == {u'token': u'my token'} + assert mock_open.call_count == 2 + assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api' + assert mock_open.mock_calls[0][2]['headers'] == {'Authorization': 'Token my_token'} + assert mock_open.mock_calls[1][1][0] == 'https://galaxy.ansible.com/api/v1/tokens/' + assert mock_open.mock_calls[1][2]['data'] == 'github_token=github_token' + + +def test_initialise_automation_hub(monkeypatch): + mock_open = MagicMock() + mock_open.side_effect = [ + urllib_error.HTTPError('https://galaxy.ansible.com/api', 401, 'msg', {}, StringIO()), + # AH won't return v1 but we do for authenticate() to work. + StringIO(u'{"available_versions":{"v1":"/api/v1","v3":"/api/v3"}}'), + StringIO(u'{"token":"my token"}'), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=GalaxyToken(token='my_token')) + actual = api.authenticate("github_token") + + assert len(api.available_api_versions) == 2 + assert api.available_api_versions['v1'] == u'/api/v1' + assert api.available_api_versions['v3'] == u'/api/v3' + assert actual == {u'token': u'my token'} + assert mock_open.call_count == 3 + assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api' + assert mock_open.mock_calls[0][2]['headers'] == {'Authorization': 'Token my_token'} + assert mock_open.mock_calls[1][1][0] == 'https://galaxy.ansible.com/api' + assert mock_open.mock_calls[1][2]['headers'] == {'Authorization': 'Bearer my_token'} + assert mock_open.mock_calls[2][1][0] == 'https://galaxy.ansible.com/api/v1/tokens/' + assert mock_open.mock_calls[2][2]['data'] == 'github_token=github_token' + + +def test_initialise_unknown(monkeypatch): + mock_open = MagicMock() + mock_open.side_effect = [ + urllib_error.HTTPError('https://galaxy.ansible.com/api', 500, 'msg', {}, StringIO(u'{"msg":"raw error"}')), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com", token=GalaxyToken(token='my_token')) + + expected = "Error when finding available api versions from test (%s/api) (HTTP Code: 500, Message: Unknown " \ + "error returned by Galaxy server.)" % api.api_server + with pytest.raises(GalaxyError, match=re.escape(expected)): + api.authenticate("github_token") + + +def test_get_available_api_versions(monkeypatch): + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(u'{"available_versions":{"v1":"/api/v1","v2":"/api/v2"}}'), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + api = GalaxyAPI(None, "test", "https://galaxy.ansible.com") + actual = api.available_api_versions + assert len(actual) == 2 + assert actual['v1'] == u'/api/v1' + assert actual['v2'] == u'/api/v2' + + assert mock_open.call_count == 1 + assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api' + + +def test_publish_collection_missing_file(): + fake_path = u'/fake/ÅÑŚÌβŁÈ/path' + expected = to_native("The collection path specified '%s' does not exist." % fake_path) + + api = get_test_galaxy_api("https://galaxy.ansible.com", "v2") + with pytest.raises(AnsibleError, match=expected): + api.publish_collection(fake_path) + + +def test_publish_collection_not_a_tarball(): + expected = "The collection path specified '{0}' is not a tarball, use 'ansible-galaxy collection build' to " \ + "create a proper release artifact." + + api = get_test_galaxy_api("https://galaxy.ansible.com", "v2") + with tempfile.NamedTemporaryFile(prefix=u'ÅÑŚÌβŁÈ') as temp_file: + temp_file.write(b"\x00") + temp_file.flush() + with pytest.raises(AnsibleError, match=expected.format(to_native(temp_file.name))): + api.publish_collection(temp_file.name) + + +def test_publish_collection_unsupported_version(): + expected = "Galaxy action publish_collection requires API versions 'v2, v3' but only 'v1' are available on test " \ + "https://galaxy.ansible.com" + + api = get_test_galaxy_api("https://galaxy.ansible.com", "v1") + with pytest.raises(AnsibleError, match=expected): + api.publish_collection("path") + + +@pytest.mark.parametrize('api_version, collection_url', [ + ('v2', 'collections'), + ('v3', 'artifacts/collections'), +]) +def test_publish_collection(api_version, collection_url, collection_artifact, monkeypatch): + api = get_test_galaxy_api("https://galaxy.ansible.com", api_version) + + mock_call = MagicMock() + mock_call.return_value = {'task': 'http://task.url/'} + monkeypatch.setattr(api, '_call_galaxy', mock_call) + + actual = api.publish_collection(collection_artifact) + assert actual == 'http://task.url/' + assert mock_call.call_count == 1 + assert mock_call.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api/%s/%s/' % (api_version, collection_url) + assert mock_call.mock_calls[0][2]['headers']['Content-length'] == len(mock_call.mock_calls[0][2]['args']) + assert mock_call.mock_calls[0][2]['headers']['Content-type'].startswith( + 'multipart/form-data; boundary=--------------------------') + assert mock_call.mock_calls[0][2]['args'].startswith(b'--------------------------') + assert mock_call.mock_calls[0][2]['method'] == 'POST' + assert mock_call.mock_calls[0][2]['auth_required'] is True + + +@pytest.mark.parametrize('api_version, collection_url, response, expected', [ + ('v2', 'collections', {}, + 'Error when publishing collection to test (%s) (HTTP Code: 500, Message: Unknown error returned by Galaxy ' + 'server. Code: Unknown)'), + ('v2', 'collections', { + 'message': u'Galaxy error messäge', + 'code': 'GWE002', + }, u'Error when publishing collection to test (%s) (HTTP Code: 500, Message: Galaxy error messäge Code: GWE002)'), + ('v3', 'artifact/collections', {}, + 'Error when publishing collection to test (%s) (HTTP Code: 500, Message: Unknown error returned by Galaxy ' + 'server. Code: Unknown)'), + ('v3', 'artifact/collections', { + 'errors': [ + { + 'code': 'conflict.collection_exists', + 'detail': 'Collection "mynamespace-mycollection-4.1.1" already exists.', + 'title': 'Conflict.', + 'status': '400', + }, + { + 'code': 'quantum_improbability', + 'title': u'Rändom(?) quantum improbability.', + 'source': {'parameter': 'the_arrow_of_time'}, + 'meta': {'remediation': 'Try again before'}, + }, + ], + }, u'Error when publishing collection to test (%s) (HTTP Code: 500, Message: Collection ' + u'"mynamespace-mycollection-4.1.1" already exists. Code: conflict.collection_exists), (HTTP Code: 500, ' + u'Message: Rändom(?) quantum improbability. Code: quantum_improbability)') +]) +def test_publish_failure(api_version, collection_url, response, expected, collection_artifact, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + + expected_url = '%s/api/%s/%s' % (api.api_server, api_version, collection_url) + + mock_open = MagicMock() + mock_open.side_effect = urllib_error.HTTPError(expected_url, 500, 'msg', {}, + StringIO(to_text(json.dumps(response)))) + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + with pytest.raises(GalaxyError, match=re.escape(to_native(expected % api.api_server))): + api.publish_collection(collection_artifact) + + +@pytest.mark.parametrize('api_version, token_type', [ + ('v2', 'Token'), + ('v3', 'Bearer'), +]) +def test_wait_import_task(api_version, token_type, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + import_uri = 'https://galaxy.server.com/api/%s/task/1234' % api_version + + mock_open = MagicMock() + mock_open.return_value = StringIO(u'{"state":"success","finished_at":"time"}') + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + mock_display = MagicMock() + monkeypatch.setattr(Display, 'display', mock_display) + + api.wait_import_task(import_uri) + + assert mock_open.call_count == 1 + assert mock_open.mock_calls[0][1][0] == import_uri + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + + assert mock_display.call_count == 1 + assert mock_display.mock_calls[0][1][0] == 'Waiting until Galaxy import task %s has completed' % import_uri + + +@pytest.mark.parametrize('api_version, token_type', [ + ('v2', 'Token'), + ('v3', 'Bearer'), +]) +def test_wait_import_task_multiple_requests(api_version, token_type, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + import_uri = 'https://galaxy.server.com/api/%s/task/1234' % api_version + + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(u'{"state":"test"}'), + StringIO(u'{"state":"success","finished_at":"time"}'), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + mock_display = MagicMock() + monkeypatch.setattr(Display, 'display', mock_display) + + mock_vvv = MagicMock() + monkeypatch.setattr(Display, 'vvv', mock_vvv) + + monkeypatch.setattr(time, 'sleep', MagicMock()) + + api.wait_import_task(import_uri) + + assert mock_open.call_count == 2 + assert mock_open.mock_calls[0][1][0] == import_uri + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + assert mock_open.mock_calls[1][1][0] == import_uri + assert mock_open.mock_calls[1][2]['headers']['Authorization'] == '%s my token' % token_type + + assert mock_display.call_count == 1 + assert mock_display.mock_calls[0][1][0] == 'Waiting until Galaxy import task %s has completed' % import_uri + + assert mock_vvv.call_count == 2 # 1st is opening Galaxy token file. + assert mock_vvv.mock_calls[1][1][0] == \ + 'Galaxy import process has a status of test, wait 2 seconds before trying again' + + +@pytest.mark.parametrize('api_version, token_type', [ + ('v2', 'Token'), + ('v3', 'Bearer'), +]) +def test_wait_import_task_with_failure(api_version, token_type, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + import_uri = 'https://galaxy.server.com/api/%s/task/1234' % api_version + + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(to_text(json.dumps({ + 'finished_at': 'some_time', + 'state': 'failed', + 'error': { + 'code': 'GW001', + 'description': u'Becäuse I said so!', + + }, + 'messages': [ + { + 'level': 'error', + 'message': u'Somé error', + }, + { + 'level': 'warning', + 'message': u'Some wärning', + }, + { + 'level': 'info', + 'message': u'Somé info', + }, + ], + }))), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + mock_display = MagicMock() + monkeypatch.setattr(Display, 'display', mock_display) + + mock_vvv = MagicMock() + monkeypatch.setattr(Display, 'vvv', mock_vvv) + + mock_warn = MagicMock() + monkeypatch.setattr(Display, 'warning', mock_warn) + + mock_err = MagicMock() + monkeypatch.setattr(Display, 'error', mock_err) + + expected = to_native(u'Galaxy import process failed: Becäuse I said so! (Code: GW001)') + with pytest.raises(AnsibleError, match=re.escape(expected)): + api.wait_import_task(import_uri) + + assert mock_open.call_count == 1 + assert mock_open.mock_calls[0][1][0] == import_uri + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + + assert mock_display.call_count == 1 + assert mock_display.mock_calls[0][1][0] == 'Waiting until Galaxy import task %s has completed' % import_uri + + assert mock_vvv.call_count == 2 # 1st is opening Galaxy token file. + assert mock_vvv.mock_calls[1][1][0] == u'Galaxy import message: info - Somé info' + + assert mock_warn.call_count == 1 + assert mock_warn.mock_calls[0][1][0] == u'Galaxy import warning message: Some wärning' + + assert mock_err.call_count == 1 + assert mock_err.mock_calls[0][1][0] == u'Galaxy import error message: Somé error' + + +@pytest.mark.parametrize('api_version, token_type', [ + ('v2', 'Token'), + ('v3', 'Bearer'), +]) +def test_wait_import_task_with_failure_no_error(api_version, token_type, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + import_uri = 'https://galaxy.server.com/api/%s/task/1234' % api_version + + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(to_text(json.dumps({ + 'finished_at': 'some_time', + 'state': 'failed', + 'error': {}, + 'messages': [ + { + 'level': 'error', + 'message': u'Somé error', + }, + { + 'level': 'warning', + 'message': u'Some wärning', + }, + { + 'level': 'info', + 'message': u'Somé info', + }, + ], + }))), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + mock_display = MagicMock() + monkeypatch.setattr(Display, 'display', mock_display) + + mock_vvv = MagicMock() + monkeypatch.setattr(Display, 'vvv', mock_vvv) + + mock_warn = MagicMock() + monkeypatch.setattr(Display, 'warning', mock_warn) + + mock_err = MagicMock() + monkeypatch.setattr(Display, 'error', mock_err) + + expected = 'Galaxy import process failed: Unknown error, see %s for more details (Code: UNKNOWN)' % import_uri + with pytest.raises(AnsibleError, match=re.escape(expected)): + api.wait_import_task(import_uri) + + assert mock_open.call_count == 1 + assert mock_open.mock_calls[0][1][0] == import_uri + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + + assert mock_display.call_count == 1 + assert mock_display.mock_calls[0][1][0] == 'Waiting until Galaxy import task %s has completed' % import_uri + + assert mock_vvv.call_count == 2 # 1st is opening Galaxy token file. + assert mock_vvv.mock_calls[1][1][0] == u'Galaxy import message: info - Somé info' + + assert mock_warn.call_count == 1 + assert mock_warn.mock_calls[0][1][0] == u'Galaxy import warning message: Some wärning' + + assert mock_err.call_count == 1 + assert mock_err.mock_calls[0][1][0] == u'Galaxy import error message: Somé error' + + +@pytest.mark.parametrize('api_version, token_type', [ + ('v2', 'Token'), + ('v3', 'Bearer'), +]) +def test_wait_import_task_timeout(api_version, token_type, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + import_uri = 'https://galaxy.server.com/api/%s/task/1234' % api_version + + def return_response(*args, **kwargs): + return StringIO(u'{"state":"waiting"}') + + mock_open = MagicMock() + mock_open.side_effect = return_response + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + mock_display = MagicMock() + monkeypatch.setattr(Display, 'display', mock_display) + + mock_vvv = MagicMock() + monkeypatch.setattr(Display, 'vvv', mock_vvv) + + monkeypatch.setattr(time, 'sleep', MagicMock()) + + expected = "Timeout while waiting for the Galaxy import process to finish, check progress at '%s'" % import_uri + with pytest.raises(AnsibleError, match=expected): + api.wait_import_task(import_uri, 1) + + assert mock_open.call_count > 1 + assert mock_open.mock_calls[0][1][0] == import_uri + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + assert mock_open.mock_calls[1][1][0] == import_uri + assert mock_open.mock_calls[1][2]['headers']['Authorization'] == '%s my token' % token_type + + assert mock_display.call_count == 1 + assert mock_display.mock_calls[0][1][0] == 'Waiting until Galaxy import task %s has completed' % import_uri + + expected_wait_msg = 'Galaxy import process has a status of waiting, wait {0} seconds before trying again' + assert mock_vvv.call_count > 9 # 1st is opening Galaxy token file. + assert mock_vvv.mock_calls[1][1][0] == expected_wait_msg.format(2) + assert mock_vvv.mock_calls[2][1][0] == expected_wait_msg.format(3) + assert mock_vvv.mock_calls[3][1][0] == expected_wait_msg.format(4) + assert mock_vvv.mock_calls[4][1][0] == expected_wait_msg.format(6) + assert mock_vvv.mock_calls[5][1][0] == expected_wait_msg.format(10) + assert mock_vvv.mock_calls[6][1][0] == expected_wait_msg.format(15) + assert mock_vvv.mock_calls[7][1][0] == expected_wait_msg.format(22) + assert mock_vvv.mock_calls[8][1][0] == expected_wait_msg.format(30) + + +@pytest.mark.parametrize('api_version, token_type, version', [ + ('v2', 'Token', 'v2.1.13'), + ('v3', 'Bearer', 'v1.0.0'), +]) +def test_get_collection_version_metadata_no_version(api_version, token_type, version, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(to_text(json.dumps({ + 'download_url': 'https://downloadme.com', + 'artifact': { + 'sha256': 'ac47b6fac117d7c171812750dacda655b04533cf56b31080b82d1c0db3c9d80f', + }, + 'namespace': { + 'name': 'namespace', + }, + 'collection': { + 'name': 'collection', + }, + 'version': version, + 'metadata': { + 'dependencies': {}, + } + }))), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + actual = api.get_collection_version_metadata('namespace', 'collection', version) + + assert isinstance(actual, CollectionVersionMetadata) + assert actual.namespace == u'namespace' + assert actual.name == u'collection' + assert actual.download_url == u'https://downloadme.com' + assert actual.artifact_sha256 == u'ac47b6fac117d7c171812750dacda655b04533cf56b31080b82d1c0db3c9d80f' + assert actual.version == version + assert actual.dependencies == {} + + assert mock_open.call_count == 1 + assert mock_open.mock_calls[0][1][0] == '%s/api/%s/collections/namespace/collection/versions/%s' \ + % (api.api_server, api_version, version) + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + + +@pytest.mark.parametrize('api_version, token_type, response', [ + ('v2', 'Token', { + 'count': 2, + 'next': None, + 'previous': None, + 'results': [ + { + 'version': '1.0.0', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.0', + }, + { + 'version': '1.0.1', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.1', + }, + ], + }), + # TODO: Verify this once Automation Hub is actually out + ('v3', 'Bearer', { + 'count': 2, + 'next': None, + 'previous': None, + 'data': [ + { + 'version': '1.0.0', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.0', + }, + { + 'version': '1.0.1', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.1', + }, + ], + }), +]) +def test_get_collection_versions(api_version, token_type, response, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + + mock_open = MagicMock() + mock_open.side_effect = [ + StringIO(to_text(json.dumps(response))), + ] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + actual = api.get_collection_versions('namespace', 'collection') + assert actual == [u'1.0.0', u'1.0.1'] + + assert mock_open.call_count == 1 + assert mock_open.mock_calls[0][1][0] == 'https://galaxy.server.com/api/%s/collections/namespace/collection/' \ + 'versions' % api_version + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + + +@pytest.mark.parametrize('api_version, token_type, responses', [ + ('v2', 'Token', [ + { + 'count': 6, + 'next': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/?page=2', + 'previous': None, + 'results': [ + { + 'version': '1.0.0', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.0', + }, + { + 'version': '1.0.1', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.1', + }, + ], + }, + { + 'count': 6, + 'next': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/?page=3', + 'previous': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions', + 'results': [ + { + 'version': '1.0.2', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.2', + }, + { + 'version': '1.0.3', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.3', + }, + ], + }, + { + 'count': 6, + 'next': None, + 'previous': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/?page=2', + 'results': [ + { + 'version': '1.0.4', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.4', + }, + { + 'version': '1.0.5', + 'href': 'https://galaxy.server.com/api/v2/collections/namespace/collection/versions/1.0.5', + }, + ], + }, + ]), + ('v3', 'Bearer', [ + { + 'count': 6, + 'links': { + 'next': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/?page=2', + 'previous': None, + }, + 'data': [ + { + 'version': '1.0.0', + 'href': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/1.0.0', + }, + { + 'version': '1.0.1', + 'href': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/1.0.1', + }, + ], + }, + { + 'count': 6, + 'links': { + 'next': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/?page=3', + 'previous': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions', + }, + 'data': [ + { + 'version': '1.0.2', + 'href': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/1.0.2', + }, + { + 'version': '1.0.3', + 'href': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/1.0.3', + }, + ], + }, + { + 'count': 6, + 'links': { + 'next': None, + 'previous': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/?page=2', + }, + 'data': [ + { + 'version': '1.0.4', + 'href': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/1.0.4', + }, + { + 'version': '1.0.5', + 'href': 'https://galaxy.server.com/api/v3/collections/namespace/collection/versions/1.0.5', + }, + ], + }, + ]), +]) +def test_get_collection_versions_pagination(api_version, token_type, responses, monkeypatch): + api = get_test_galaxy_api('https://galaxy.server.com', api_version) + + mock_open = MagicMock() + mock_open.side_effect = [StringIO(to_text(json.dumps(r))) for r in responses] + monkeypatch.setattr(galaxy_api, 'open_url', mock_open) + + actual = api.get_collection_versions('namespace', 'collection') + a = '' + assert actual == [u'1.0.0', u'1.0.1', u'1.0.2', u'1.0.3', u'1.0.4', u'1.0.5'] + + assert mock_open.call_count == 3 + assert mock_open.mock_calls[0][1][0] == 'https://galaxy.server.com/api/%s/collections/namespace/collection/' \ + 'versions' % api_version + assert mock_open.mock_calls[0][2]['headers']['Authorization'] == '%s my token' % token_type + assert mock_open.mock_calls[1][1][0] == 'https://galaxy.server.com/api/%s/collections/namespace/collection/' \ + 'versions/?page=2' % api_version + assert mock_open.mock_calls[1][2]['headers']['Authorization'] == '%s my token' % token_type + assert mock_open.mock_calls[2][1][0] == 'https://galaxy.server.com/api/%s/collections/namespace/collection/' \ + 'versions/?page=3' % api_version + assert mock_open.mock_calls[2][2]['headers']['Authorization'] == '%s my token' % token_type diff --git a/test/units/galaxy/test_collection.py b/test/units/galaxy/test_collection.py index 6276fb346c..c86a524a09 100644 --- a/test/units/galaxy/test_collection.py +++ b/test/units/galaxy/test_collection.py @@ -9,18 +9,13 @@ __metaclass__ = type import json import os import pytest -import re import tarfile -import tempfile -import time import uuid from hashlib import sha256 -from io import BytesIO, StringIO +from io import BytesIO from units.compat.mock import MagicMock -import ansible.module_utils.six.moves.urllib.error as urllib_error - from ansible import context from ansible.cli.galaxy import GalaxyCLI from ansible.errors import AnsibleError @@ -55,13 +50,6 @@ def collection_input(tmp_path_factory): return collection_dir, output_dir -@pytest.fixture() -def galaxy_api_version(monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - @pytest.fixture() def collection_artifact(monkeypatch, tmp_path_factory): ''' Creates a temp collection artifact and mocked open_url instance for publishing tests ''' @@ -408,440 +396,53 @@ def test_build_with_symlink_inside_collection(collection_input): assert actual_file == '63444bfc766154e1bc7557ef6280de20d03fcd81' -def test_publish_missing_file(): - fake_path = u'/fake/ÅÑŚÌβŁÈ/path' - expected = to_native("The collection path specified '%s' does not exist." % fake_path) - - with pytest.raises(AnsibleError, match=expected): - collection.publish_collection(fake_path, None, True, 0) - - -def test_publish_not_a_tarball(): - expected = "The collection path specified '{0}' is not a tarball, use 'ansible-galaxy collection build' to " \ - "create a proper release artifact." - - with tempfile.NamedTemporaryFile(prefix=u'ÅÑŚÌβŁÈ') as temp_file: - temp_file.write(b"\x00") - temp_file.flush() - with pytest.raises(AnsibleError, match=expected.format(to_native(temp_file.name))): - collection.publish_collection(temp_file.name, None, True, 0) - - def test_publish_no_wait(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) artifact_path, mock_open = collection_artifact fake_import_uri = 'https://galaxy.server.com/api/v2/import/1234' - mock_open.return_value = StringIO(u'{"task":"%s"}' % fake_import_uri) - expected_form, expected_content_type = collection._get_mime_data(to_bytes(artifact_path)) + mock_publish = MagicMock() + mock_publish.return_value = fake_import_uri + monkeypatch.setattr(galaxy_server, 'publish_collection', mock_publish) collection.publish_collection(artifact_path, galaxy_server, False, 0) - assert mock_open.call_count == 1 - assert mock_open.mock_calls[0][1][0] == '%s/api/v2/collections/' % galaxy_server.api_server - assert mock_open.mock_calls[0][2]['data'] == expected_form - assert mock_open.mock_calls[0][2]['method'] == 'POST' - assert mock_open.mock_calls[0][2]['validate_certs'] is True - assert mock_open.mock_calls[0][2]['headers']['Authorization'] == 'Token key' - assert mock_open.mock_calls[0][2]['headers']['Content-length'] == len(expected_form) - assert mock_open.mock_calls[0][2]['headers']['Content-type'] == expected_content_type - - assert mock_display.call_count == 2 - assert mock_display.mock_calls[0][1][0] == "Publishing collection artifact '%s' to %s %s" \ - % (artifact_path, galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[1][1][0] == \ - "Collection has been pushed to the Galaxy server %s %s, not waiting until import has completed due to --no-wait " \ - "being set. Import task results can be found at %s" % (galaxy_server.name, galaxy_server.api_server, fake_import_uri) - - -def test_publish_dont_validate_cert(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - galaxy_server.validate_certs = False - artifact_path, mock_open = collection_artifact - - mock_open.return_value = StringIO(u'{"task":"https://galaxy.server.com/api/v2/import/1234"}') - - collection.publish_collection(artifact_path, galaxy_server, False, 0) - - assert mock_open.call_count == 1 - assert mock_open.mock_calls[0][2]['validate_certs'] is False - - -def test_publish_failure(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - artifact_path, mock_open = collection_artifact - - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 500, 'msg', {}, StringIO()) - - expected = 'Error when publishing collection to test_server (https://galaxy.ansible.com) ' \ - '(HTTP Code: 500, Message: Unknown error returned by Galaxy ' \ - 'server. Code: Unknown)' - with pytest.raises(AnsibleError, match=re.escape(expected)): - collection.publish_collection(artifact_path, galaxy_server, True, 0) - - -def test_publish_failure_with_json_info(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - artifact_path, mock_open = collection_artifact - - return_content = StringIO(u'{"message":"Galaxy error message","code":"GWE002"}') - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 503, 'msg', {}, return_content) - - expected = 'Error when publishing collection to test_server (https://galaxy.ansible.com) ' \ - '(HTTP Code: 503, Message: Galaxy error message Code: GWE002)' - with pytest.raises(AnsibleError, match=re.escape(expected)): - collection.publish_collection(artifact_path, galaxy_server, True, 0) - - -@pytest.mark.parametrize("api_version,token_type", [ - ('v2', 'Token'), - ('v3', 'Bearer') -]) -def test_publish_with_wait(api_version, token_type, galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {api_version: '/api/%s' % api_version} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - mock_display = MagicMock() - monkeypatch.setattr(Display, 'display', mock_display) - - fake_import_uri = 'https://galaxy-server/api/v2/import/1234' - - artifact_path, mock_open = collection_artifact - - mock_open.side_effect = ( - StringIO(u'{"task":"%s"}' % fake_import_uri), - StringIO(u'{"finished_at":"some_time","state":"success"}') - ) - - collection.publish_collection(artifact_path, galaxy_server, True, 0) - - assert mock_open.call_count == 2 - assert mock_open.mock_calls[1][1][0] == fake_import_uri - assert mock_open.mock_calls[1][2]['headers']['Authorization'] == '%s key' % token_type - assert mock_open.mock_calls[1][2]['validate_certs'] is True - assert mock_open.mock_calls[1][2]['method'] == 'GET' - - assert mock_display.call_count == 5 - assert mock_display.mock_calls[0][1][0] == "Publishing collection artifact '%s' to %s %s" \ - % (artifact_path, galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[1][1][0] == 'Collection has been published to the Galaxy server %s %s'\ - % (galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[2][1][0] == 'Waiting until Galaxy import task %s has completed' % fake_import_uri - assert mock_display.mock_calls[4][1][0] == 'Collection has been successfully published and imported to the ' \ - 'Galaxy server %s %s' % (galaxy_server.name, galaxy_server.api_server) - - -@pytest.mark.parametrize("api_version,exp_api_url,token_type", [ - ('v2', '/api/v2/collections/', 'Token'), - ('v3', '/api/v3/artifacts/collections/', 'Bearer') -]) -def test_publish_with_wait_timeout(api_version, exp_api_url, token_type, galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {api_version: '/api/%s' % api_version} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - monkeypatch.setattr(time, 'sleep', MagicMock()) - - mock_vvv = MagicMock() - monkeypatch.setattr(Display, 'vvv', mock_vvv) - - fake_import_uri = 'https://galaxy-server/api/v2/import/1234' - - artifact_path, mock_open = collection_artifact - - mock_open.side_effect = ( - StringIO(u'{"task":"%s"}' % fake_import_uri), - StringIO(u'{"finished_at":null}'), - StringIO(u'{"finished_at":"some_time","state":"success"}') - ) - - collection.publish_collection(artifact_path, galaxy_server, True, 60) - - assert mock_open.call_count == 3 - assert mock_open.mock_calls[1][1][0] == fake_import_uri - assert mock_open.mock_calls[1][2]['headers']['Authorization'] == '%s key' % token_type - assert mock_open.mock_calls[1][2]['validate_certs'] is True - assert mock_open.mock_calls[1][2]['method'] == 'GET' - assert mock_open.mock_calls[2][1][0] == fake_import_uri - assert mock_open.mock_calls[2][2]['headers']['Authorization'] == '%s key' % token_type - assert mock_open.mock_calls[2][2]['validate_certs'] is True - assert mock_open.mock_calls[2][2]['method'] == 'GET' - - assert mock_vvv.call_count == 2 - assert mock_vvv.mock_calls[1][1][0] == \ - 'Galaxy import process has a status of waiting, wait 2 seconds before trying again' - - -def test_publish_with_wait_timeout_failure(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - monkeypatch.setattr(time, 'sleep', MagicMock()) - - mock_vvv = MagicMock() - monkeypatch.setattr(Display, 'vvv', mock_vvv) + assert mock_publish.call_count == 1 + assert mock_publish.mock_calls[0][1][0] == artifact_path - fake_import_uri = 'https://galaxy-server/api/v2/import/1234' - - artifact_path, mock_open = collection_artifact - - first_call = True - - def open_value(*args, **kwargs): - if first_call: - return StringIO(u'{"task":"%s"}' % fake_import_uri) - else: - return StringIO(u'{"finished_at":null}') + assert mock_display.call_count == 1 + assert mock_display.mock_calls[0][1][0] == \ + "Collection has been pushed to the Galaxy server %s %s, not waiting until import has completed due to " \ + "--no-wait being set. Import task results can be found at %s" % (galaxy_server.name, galaxy_server.api_server, + fake_import_uri) - mock_open.side_effect = open_value - - expected = "Timeout while waiting for the Galaxy import process to finish, check progress at '%s'" \ - % fake_import_uri - with pytest.raises(AnsibleError, match=expected): - collection.publish_collection(artifact_path, galaxy_server, True, 2) - - # While the seconds exceed the time we are testing that the exponential backoff gets to 30 and then sits there - # Because we mock time.sleep() there should be thousands of calls here - expected_wait_msg = 'Galaxy import process has a status of waiting, wait {0} seconds before trying again' - assert mock_vvv.call_count > 9 - assert mock_vvv.mock_calls[1][1][0] == expected_wait_msg.format(2) - assert mock_vvv.mock_calls[2][1][0] == expected_wait_msg.format(3) - assert mock_vvv.mock_calls[3][1][0] == expected_wait_msg.format(4) - assert mock_vvv.mock_calls[4][1][0] == expected_wait_msg.format(6) - assert mock_vvv.mock_calls[5][1][0] == expected_wait_msg.format(10) - assert mock_vvv.mock_calls[6][1][0] == expected_wait_msg.format(15) - assert mock_vvv.mock_calls[7][1][0] == expected_wait_msg.format(22) - assert mock_vvv.mock_calls[8][1][0] == expected_wait_msg.format(30) - - -def test_publish_with_wait_and_failure(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) +def test_publish_with_wait(galaxy_server, collection_artifact, monkeypatch): mock_display = MagicMock() monkeypatch.setattr(Display, 'display', mock_display) - mock_vvv = MagicMock() - monkeypatch.setattr(Display, 'vvv', mock_vvv) - - mock_warn = MagicMock() - monkeypatch.setattr(Display, 'warning', mock_warn) - - mock_err = MagicMock() - monkeypatch.setattr(Display, 'error', mock_err) - - fake_import_uri = 'https://galaxy-server/api/v2/import/1234' - artifact_path, mock_open = collection_artifact + fake_import_uri = 'https://galaxy.server.com/api/v2/import/1234' - import_stat = { - 'finished_at': 'some_time', - 'state': 'failed', - 'error': { - 'code': 'GW001', - 'description': 'Because I said so!', - - }, - 'messages': [ - { - 'level': 'error', - 'message': 'Some error', - }, - { - 'level': 'warning', - 'message': 'Some warning', - }, - { - 'level': 'info', - 'message': 'Some info', - }, - ], - } - - mock_open.side_effect = ( - StringIO(u'{"task":"%s"}' % fake_import_uri), - StringIO(to_text(json.dumps(import_stat))) - ) - - expected = 'Galaxy import process failed: Because I said so! (Code: GW001)' - with pytest.raises(AnsibleError, match=re.escape(expected)): - collection.publish_collection(artifact_path, galaxy_server, True, 0) - - assert mock_open.call_count == 2 - assert mock_open.mock_calls[1][1][0] == fake_import_uri - assert mock_open.mock_calls[1][2]['headers']['Authorization'] == 'Token key' - assert mock_open.mock_calls[1][2]['validate_certs'] is True - assert mock_open.mock_calls[1][2]['method'] == 'GET' - - assert mock_display.call_count == 4 - assert mock_display.mock_calls[0][1][0] == "Publishing collection artifact '%s' to %s %s"\ - % (artifact_path, galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[1][1][0] == 'Collection has been published to the Galaxy server %s %s'\ - % (galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[2][1][0] == 'Waiting until Galaxy import task %s has completed' % fake_import_uri - - assert mock_vvv.call_count == 2 - assert mock_vvv.mock_calls[1][1][0] == 'Galaxy import message: info - Some info' - - assert mock_warn.call_count == 1 - assert mock_warn.mock_calls[0][1][0] == 'Galaxy import warning message: Some warning' - - assert mock_err.call_count == 1 - assert mock_err.mock_calls[0][1][0] == 'Galaxy import error message: Some error' - - -def test_publish_with_wait_and_failure_and_no_error(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - mock_display = MagicMock() - monkeypatch.setattr(Display, 'display', mock_display) - - mock_vvv = MagicMock() - monkeypatch.setattr(Display, 'vvv', mock_vvv) - - mock_warn = MagicMock() - monkeypatch.setattr(Display, 'warning', mock_warn) - - mock_err = MagicMock() - monkeypatch.setattr(Display, 'error', mock_err) - - fake_import_uri = 'https://galaxy-server/api/v2/import/1234' - - artifact_path, mock_open = collection_artifact - - import_stat = { - 'finished_at': 'some_time', - 'state': 'failed', - 'error': {}, - 'messages': [ - { - 'level': 'error', - 'message': 'Some error', - }, - { - 'level': 'warning', - 'message': 'Some warning', - }, - { - 'level': 'info', - 'message': 'Some info', - }, - ], - } - - mock_open.side_effect = ( - StringIO(u'{"task":"%s"}' % fake_import_uri), - StringIO(to_text(json.dumps(import_stat))) - ) - - expected = 'Galaxy import process failed: Unknown error, see %s for more details (Code: UNKNOWN)' % fake_import_uri - with pytest.raises(AnsibleError, match=re.escape(expected)): - collection.publish_collection(artifact_path, galaxy_server, True, 0) - - assert mock_open.call_count == 2 - assert mock_open.mock_calls[1][1][0] == fake_import_uri - assert mock_open.mock_calls[1][2]['headers']['Authorization'] == 'Token key' - assert mock_open.mock_calls[1][2]['validate_certs'] is True - assert mock_open.mock_calls[1][2]['method'] == 'GET' - - assert mock_display.call_count == 4 - assert mock_display.mock_calls[0][1][0] == "Publishing collection artifact '%s' to %s %s"\ - % (artifact_path, galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[1][1][0] == 'Collection has been published to the Galaxy server %s %s'\ - % (galaxy_server.name, galaxy_server.api_server) - assert mock_display.mock_calls[2][1][0] == 'Waiting until Galaxy import task %s has completed' % fake_import_uri - - assert mock_vvv.call_count == 2 - assert mock_vvv.mock_calls[1][1][0] == 'Galaxy import message: info - Some info' - - assert mock_warn.call_count == 1 - assert mock_warn.mock_calls[0][1][0] == 'Galaxy import warning message: Some warning' - - assert mock_err.call_count == 1 - assert mock_err.mock_calls[0][1][0] == 'Galaxy import error message: Some error' - - -def test_publish_failure_v3_with_json_info_409_conflict(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v3': '/api/v3'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - artifact_path, mock_open = collection_artifact - - error_response = { - "errors": [ - { - "code": "conflict.collection_exists", - "detail": 'Collection "testing-ansible_testing_content-4.0.4" already exists.', - "title": "Conflict.", - "status": "409", - }, - ] - } - - return_content = StringIO(to_text(json.dumps(error_response))) - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 409, 'msg', {}, return_content) + mock_publish = MagicMock() + mock_publish.return_value = fake_import_uri + monkeypatch.setattr(galaxy_server, 'publish_collection', mock_publish) - expected = 'Error when publishing collection to test_server (https://galaxy.ansible.com) ' \ - '(HTTP Code: 409, Message: Collection "testing-ansible_testing_content-4.0.4"' \ - ' already exists. Code: conflict.collection_exists)' - with pytest.raises(AnsibleError, match=re.escape(expected)): - collection.publish_collection(artifact_path, galaxy_server, True, 0) + mock_wait = MagicMock() + monkeypatch.setattr(galaxy_server, 'wait_import_task', mock_wait) + collection.publish_collection(artifact_path, galaxy_server, True, 0) -def test_publish_failure_v3_with_json_info_multiple_errors(galaxy_server, collection_artifact, monkeypatch): - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v3': '/api/v3'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) + assert mock_publish.call_count == 1 + assert mock_publish.mock_calls[0][1][0] == artifact_path - artifact_path, mock_open = collection_artifact + assert mock_wait.call_count == 1 + assert mock_wait.mock_calls[0][1][0] == fake_import_uri - error_response = { - "errors": [ - { - "code": "conflict.collection_exists", - "detail": 'Collection "mynamespace-mycollection-4.1.1" already exists.', - "title": "Conflict.", - "status": "400", - }, - { - "code": "quantum_improbability", - "title": "Random(?) quantum improbability.", - "source": {"parameter": "the_arrow_of_time"}, - "meta": {"remediation": "Try again before"} - }, - ] - } - - return_content = StringIO(to_text(json.dumps(error_response))) - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 400, 'msg', {}, return_content) - - expected = 'Error when publishing collection to test_server (https://galaxy.ansible.com) ' \ - '(HTTP Code: 400, Message: Collection "mynamespace-mycollection-4.1.1"' \ - ' already exists. Code: conflict.collection_exists),' \ - ' (HTTP Code: 400, Message: Random(?) quantum improbability. Code: quantum_improbability)' - with pytest.raises(AnsibleError, match=re.escape(expected)): - collection.publish_collection(artifact_path, galaxy_server, True, 0) + assert mock_display.mock_calls[0][1][0] == "Collection has been published to the Galaxy server test_server %s" \ + % galaxy_server.api_server def test_find_existing_collections(tmp_path_factory, monkeypatch): @@ -962,91 +563,3 @@ def test_extract_tar_file_missing_parent_dir(tmp_tarfile): collection._extract_tar_file(tfile, filename, output_dir, temp_dir, checksum) os.path.isfile(output_file) - - -def test_get_available_api_versions_v2_auth_not_required_without_auth(galaxy_server, collection_artifact, monkeypatch): - # mock_avail_ver = MagicMock() - # mock_avail_ver.side_effect = {api_version: '/api/%s' % api_version} - # monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - response_obj = { - "description": "GALAXY REST API", - "current_version": "v1", - "available_versions": { - "v1": "/api/v1/", - "v2": "/api/v2/" - }, - "server_version": "3.2.4", - "version_name": "Doin' it Right", - "team_members": [ - "chouseknecht", - "cutwater", - "alikins", - "newswangerd", - "awcrosby", - "tima", - "gregdek" - ] - } - - artifact_path, mock_open = collection_artifact - - return_content = StringIO(to_text(json.dumps(response_obj))) - mock_open.return_value = return_content - res = collection.get_available_api_versions(galaxy_server) - - assert res == {'v1': '/api/v1/', 'v2': '/api/v2/'} - - -def test_get_available_api_versions_v3_auth_required_without_auth(galaxy_server, collection_artifact, monkeypatch): - # mock_avail_ver = MagicMock() - # mock_avail_ver.side_effect = {api_version: '/api/%s' % api_version} - # monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - error_response = {'code': 'unauthorized', 'detail': 'The request was not authorized'} - artifact_path, mock_open = collection_artifact - - return_content = StringIO(to_text(json.dumps(error_response))) - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 401, 'msg', {'WWW-Authenticate': 'Bearer'}, return_content) - with pytest.raises(AnsibleError): - collection.get_available_api_versions(galaxy_server) - - -def test_get_available_api_versions_v3_auth_required_with_auth_on_retry(galaxy_server, collection_artifact, monkeypatch): - # mock_avail_ver = MagicMock() - # mock_avail_ver.side_effect = {api_version: '/api/%s' % api_version} - # monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - error_obj = {'code': 'unauthorized', 'detail': 'The request was not authorized'} - success_obj = { - "description": "GALAXY REST API", - "current_version": "v1", - "available_versions": { - "v3": "/api/v3/" - }, - "server_version": "3.2.4", - "version_name": "Doin' it Right", - "team_members": [ - "chouseknecht", - "cutwater", - "alikins", - "newswangerd", - "awcrosby", - "tima", - "gregdek" - ] - } - - artifact_path, mock_open = collection_artifact - - error_response = StringIO(to_text(json.dumps(error_obj))) - success_response = StringIO(to_text(json.dumps(success_obj))) - mock_open.side_effect = [ - urllib_error.HTTPError('https://galaxy.server.com', 401, 'msg', {'WWW-Authenticate': 'Bearer'}, error_response), - success_response, - ] - - try: - res = collection.get_available_api_versions(galaxy_server) - except AnsibleError as err: - print(err) - raise - - assert res == {'v3': '/api/v3/'} diff --git a/test/units/galaxy/test_collection_install.py b/test/units/galaxy/test_collection_install.py index 1f5b385807..658c54a69c 100644 --- a/test/units/galaxy/test_collection_install.py +++ b/test/units/galaxy/test_collection_install.py @@ -290,21 +290,10 @@ def test_build_requirement_from_tar_invalid_manifest(tmp_path_factory): collection.CollectionRequirement.from_tar(tar_path, True, True) -@pytest.mark.parametrize("api_version,exp_api_url", [ - ('v2', '/api/v2/collections/namespace/collection/versions/'), - ('v3', '/api/v3/collections/namespace/collection/versions/') -]) -def test_build_requirement_from_name(api_version, exp_api_url, galaxy_server, monkeypatch): - mock_avail_ver = MagicMock() - avail_api_versions = {api_version: '/api/%s' % api_version} - mock_avail_ver.return_value = avail_api_versions - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - json_str = artifact_versions_json('namespace', 'collection', ['2.1.9', '2.1.10'], galaxy_server, avail_api_versions) - mock_open = MagicMock() - mock_open.return_value = StringIO(json_str) - - monkeypatch.setattr(collection, 'open_url', mock_open) +def test_build_requirement_from_name(galaxy_server, monkeypatch): + mock_get_versions = MagicMock() + mock_get_versions.return_value = ['2.1.9', '2.1.10'] + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_get_versions) actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '*', True, True) @@ -317,27 +306,14 @@ def test_build_requirement_from_name(api_version, exp_api_url, galaxy_server, mo assert actual.latest_version == u'2.1.10' assert actual.dependencies is None - assert mock_open.call_count == 1 - assert mock_open.mock_calls[0][1][0] == '%s%s' % (galaxy_server.api_server, exp_api_url) - assert mock_open.mock_calls[0][2] == {'validate_certs': True, "headers": {}} - + assert mock_get_versions.call_count == 1 + assert mock_get_versions.mock_calls[0][1] == ('namespace', 'collection') -@pytest.mark.parametrize("api_version,exp_api_url", [ - ('v2', '/api/v2/collections/namespace/collection/versions/'), - ('v3', '/api/v3/collections/namespace/collection/versions/') -]) -def test_build_requirement_from_name_with_prerelease(api_version, exp_api_url, galaxy_server, monkeypatch): - mock_avail_ver = MagicMock() - avail_api_versions = {api_version: '/api/%s' % api_version} - mock_avail_ver.return_value = avail_api_versions - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - json_str = artifact_versions_json('namespace', 'collection', ['1.0.1', '2.0.1-beta.1', '2.0.1'], - galaxy_server, avail_api_versions) - mock_open = MagicMock() - mock_open.return_value = StringIO(json_str) - monkeypatch.setattr(collection, 'open_url', mock_open) +def test_build_requirement_from_name_with_prerelease(galaxy_server, monkeypatch): + mock_get_versions = MagicMock() + mock_get_versions.return_value = ['1.0.1', '2.0.1-beta.1', '2.0.1'] + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_get_versions) actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '*', True, True) @@ -350,28 +326,15 @@ def test_build_requirement_from_name_with_prerelease(api_version, exp_api_url, g assert actual.latest_version == u'2.0.1' assert actual.dependencies is None - assert mock_open.call_count == 1 - assert mock_open.mock_calls[0][1][0] == '%s%s' % (galaxy_server.api_server, exp_api_url) - assert mock_open.mock_calls[0][2] == {'validate_certs': True, "headers": {}} - - -@pytest.mark.parametrize("api_version,exp_api_url", [ - ('v2', '/api/v2/collections/namespace/collection/versions/2.0.1-beta.1/'), - ('v3', '/api/v3/collections/namespace/collection/versions/2.0.1-beta.1/') -]) -def test_build_requirment_from_name_with_prerelease_explicit(api_version, exp_api_url, galaxy_server, monkeypatch): - mock_avail_ver = MagicMock() - avail_api_versions = {api_version: '/api/%s' % api_version} - mock_avail_ver.return_value = avail_api_versions - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) + assert mock_get_versions.call_count == 1 + assert mock_get_versions.mock_calls[0][1] == ('namespace', 'collection') - json_str = artifact_json('namespace', 'collection', '2.0.1-beta.1', {}, galaxy_server.api_server) - mock_open = MagicMock() - mock_open.side_effect = ( - StringIO(json_str), - ) - monkeypatch.setattr(collection, 'open_url', mock_open) +def test_build_requirment_from_name_with_prerelease_explicit(galaxy_server, monkeypatch): + mock_get_info = MagicMock() + mock_get_info.return_value = api.CollectionVersionMetadata('namespace', 'collection', '2.0.1-beta.1', None, None, + {}) + monkeypatch.setattr(galaxy_server, 'get_collection_version_metadata', mock_get_info) actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '2.0.1-beta.1', True, True) @@ -385,32 +348,22 @@ def test_build_requirment_from_name_with_prerelease_explicit(api_version, exp_ap assert actual.latest_version == u'2.0.1-beta.1' assert actual.dependencies == {} - assert mock_open.call_count == 1 - assert mock_open.mock_calls[0][1][0] == '%s%s' % (galaxy_server.api_server, exp_api_url) - assert mock_open.mock_calls[0][2] == {'validate_certs': True, "headers": {}} - + assert mock_get_info.call_count == 1 + assert mock_get_info.mock_calls[0][1] == ('namespace', 'collection', '2.0.1-beta.1') -@pytest.mark.parametrize("api_version,exp_api_url", [ - ('v2', '/api/v2/collections/namespace/collection/versions/'), - ('v3', '/api/v3/collections/namespace/collection/versions/') -]) -def test_build_requirement_from_name_second_server(api_version, exp_api_url, galaxy_server, monkeypatch): - mock_avail_ver = MagicMock() - avail_api_versions = {api_version: '/api/%s' % api_version} - mock_avail_ver.return_value = avail_api_versions - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - json_str = artifact_versions_json('namespace', 'collection', ['1.0.1', '1.0.2', '1.0.3'], galaxy_server, avail_api_versions) - mock_open = MagicMock() - mock_open.side_effect = ( - urllib_error.HTTPError('https://galaxy.server.com', 404, 'msg', {}, None), - StringIO(json_str) - ) - monkeypatch.setattr(collection, 'open_url', mock_open) +def test_build_requirement_from_name_second_server(galaxy_server, monkeypatch): + mock_get_versions = MagicMock() + mock_get_versions.return_value = ['1.0.1', '1.0.2', '1.0.3'] + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_get_versions) broken_server = copy.copy(galaxy_server) broken_server.api_server = 'https://broken.com/' + mock_404 = MagicMock() + mock_404.side_effect = api.GalaxyError(urllib_error.HTTPError('https://galaxy.server.com', 404, 'msg', {}, + StringIO()), "custom msg") + monkeypatch.setattr(broken_server, 'get_collection_versions', mock_404) + actual = collection.CollectionRequirement.from_name('namespace.collection', [broken_server, galaxy_server], '>1.0.1', False, True) @@ -423,99 +376,46 @@ def test_build_requirement_from_name_second_server(api_version, exp_api_url, gal assert actual.latest_version == u'1.0.3' assert actual.dependencies is None - assert mock_open.call_count == 2 - assert mock_open.mock_calls[0][1][0] == u"https://broken.com%s" % exp_api_url - assert mock_open.mock_calls[1][1][0] == u"%s%s" % (galaxy_server.api_server, exp_api_url) - assert mock_open.mock_calls[1][2] == {'validate_certs': True, "headers": {}} + assert mock_404.call_count == 1 + assert mock_404.mock_calls[0][1] == ('namespace', 'collection') + + assert mock_get_versions.call_count == 1 + assert mock_get_versions.mock_calls[0][1] == ('namespace', 'collection') def test_build_requirement_from_name_missing(galaxy_server, monkeypatch): mock_open = MagicMock() - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 404, 'msg', {}, None) - - monkeypatch.setattr(collection, 'open_url', mock_open) + mock_open.side_effect = api.GalaxyError(urllib_error.HTTPError('https://galaxy.server.com', 404, 'msg', {}, + StringIO()), "") - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_open) expected = "Failed to find collection namespace.collection:*" with pytest.raises(AnsibleError, match=expected): - collection.CollectionRequirement.from_name('namespace.collection', - [galaxy_server, galaxy_server], '*', False, True) - - -@pytest.mark.parametrize("api_version,errors_to_return,expected", [ - ('v2', - [], - 'Error fetching info for .*\\..* \\(HTTP Code: 400, Message: Unknown error returned by Galaxy server. Code: Unknown\\)'), - ('v2', - [{'message': 'Polarization error. Try flipping it over.', 'code': 'polarization_error'}], - 'Error fetching info for .*\\..* \\(HTTP Code: 400, Message: Polarization error. Try flipping it over. Code: polarization_error\\)'), - ('v3', - [], - 'Error fetching info for .*\\..* \\(HTTP Code: 400, Message: Unknown error returned by Galaxy server. Code: Unknown\\)'), - ('v3', - [{'code': 'invalid_param', 'detail': '"easy" is not a valid query param'}], - 'Error fetching info for .*\\..* \\(HTTP Code: 400, Message: "easy" is not a valid query param Code: invalid_param\\)'), -]) -def test_build_requirement_from_name_400_bad_request(api_version, errors_to_return, expected, galaxy_server, monkeypatch): - mock_avail_ver = MagicMock() - available_api_versions = {api_version: '/api/%s' % api_version} - mock_avail_ver.return_value = available_api_versions - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) + collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server, galaxy_server], '*', False, + True) - json_str = error_json(galaxy_server, errors_to_return=errors_to_return, available_api_versions=available_api_versions) +def test_build_requirement_from_name_401_unauthorized(galaxy_server, monkeypatch): mock_open = MagicMock() - monkeypatch.setattr(collection, 'open_url', mock_open) - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 400, 'msg', {}, StringIO(json_str)) + mock_open.side_effect = api.GalaxyError(urllib_error.HTTPError('https://galaxy.server.com', 401, 'msg', {}, + StringIO()), "error") - with pytest.raises(AnsibleError, match=expected): - collection.CollectionRequirement.from_name('namespace.collection', - [galaxy_server, galaxy_server], '*', False) - - -@pytest.mark.parametrize("api_version,errors_to_return,expected", [ - ('v2', - [], - 'Error fetching info for .*\\..* \\(HTTP Code: 401, Message: Unknown error returned by Galaxy server. Code: Unknown\\)'), - ('v3', - [], - 'Error fetching info for .*\\..* \\(HTTP Code: 401, Message: Unknown error returned by Galaxy server. Code: Unknown\\)'), - ('v3', - [{'code': 'unauthorized', 'detail': 'The request was not authorized'}], - 'Error fetching info for .*\\..* \\(HTTP Code: 401, Message: The request was not authorized Code: unauthorized\\)'), -]) -def test_build_requirement_from_name_401_unauthorized(api_version, errors_to_return, expected, galaxy_server, monkeypatch): - mock_avail_ver = MagicMock() - available_api_versions = {api_version: '/api/%s' % api_version} - mock_avail_ver.return_value = available_api_versions - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) - - json_str = error_json(galaxy_server, errors_to_return=errors_to_return, available_api_versions=available_api_versions) + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_open) - mock_open = MagicMock() - monkeypatch.setattr(collection, 'open_url', mock_open) - mock_open.side_effect = urllib_error.HTTPError('https://galaxy.server.com', 401, 'msg', {}, StringIO(json_str)) - - with pytest.raises(AnsibleError, match=expected): - collection.CollectionRequirement.from_name('namespace.collection', - [galaxy_server, galaxy_server], '*', False) + expected = "error (HTTP Code: 401, Message: Unknown error returned by Galaxy server.)" + with pytest.raises(api.GalaxyError, match=re.escape(expected)): + collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server, galaxy_server], '*', False) def test_build_requirement_from_name_single_version(galaxy_server, monkeypatch): - json_str = artifact_json('namespace', 'collection', '2.0.0', {}, galaxy_server.api_server) - mock_open = MagicMock() - mock_open.return_value = StringIO(json_str) - - monkeypatch.setattr(collection, 'open_url', mock_open) - - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) + mock_get_info = MagicMock() + mock_get_info.return_value = api.CollectionVersionMetadata('namespace', 'collection', '2.0.0', None, None, + {}) + monkeypatch.setattr(galaxy_server, 'get_collection_version_metadata', mock_get_info) - actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '2.0.0', True, True) + actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '2.0.0', True, + True) assert actual.namespace == u'namespace' assert actual.name == u'collection' @@ -526,24 +426,19 @@ def test_build_requirement_from_name_single_version(galaxy_server, monkeypatch): assert actual.latest_version == u'2.0.0' assert actual.dependencies == {} - assert mock_open.call_count == 1 - assert mock_open.mock_calls[0][1][0] == u"%s/api/v2/collections/namespace/collection/versions/2.0.0/" \ - % galaxy_server.api_server - assert mock_open.mock_calls[0][2] == {'validate_certs': True, "headers": {}} + assert mock_get_info.call_count == 1 + assert mock_get_info.mock_calls[0][1] == ('namespace', 'collection', '2.0.0') def test_build_requirement_from_name_multiple_versions_one_match(galaxy_server, monkeypatch): - json_str1 = artifact_versions_json('namespace', 'collection', ['2.0.0', '2.0.1', '2.0.2'], - galaxy_server) - json_str2 = artifact_json('namespace', 'collection', '2.0.1', {}, galaxy_server.api_server) - mock_open = MagicMock() - mock_open.side_effect = (StringIO(json_str1), StringIO(json_str2)) + mock_get_versions = MagicMock() + mock_get_versions.return_value = ['2.0.0', '2.0.1', '2.0.2'] + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_get_versions) - monkeypatch.setattr(collection, 'open_url', mock_open) - - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) + mock_get_info = MagicMock() + mock_get_info.return_value = api.CollectionVersionMetadata('namespace', 'collection', '2.0.1', None, None, + {}) + monkeypatch.setattr(galaxy_server, 'get_collection_version_metadata', mock_get_info) actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '>=2.0.1,<2.0.2', True, True) @@ -557,62 +452,17 @@ def test_build_requirement_from_name_multiple_versions_one_match(galaxy_server, assert actual.latest_version == u'2.0.1' assert actual.dependencies == {} - assert mock_open.call_count == 2 - assert mock_open.mock_calls[0][1][0] == u"%s/api/v2/collections/namespace/collection/versions/" \ - % galaxy_server.api_server - assert mock_open.mock_calls[0][2] == {'validate_certs': True, "headers": {}} - assert mock_open.mock_calls[1][1][0] == u"%s/api/v2/collections/namespace/collection/versions/2.0.1/" \ - % galaxy_server.api_server - assert mock_open.mock_calls[1][2] == {'validate_certs': True, "headers": {}} + assert mock_get_versions.call_count == 1 + assert mock_get_versions.mock_calls[0][1] == ('namespace', 'collection') + assert mock_get_info.call_count == 1 + assert mock_get_info.mock_calls[0][1] == ('namespace', 'collection', '2.0.1') -def test_build_requirement_from_name_multiple_version_results(galaxy_server, monkeypatch): - json_str1 = json.dumps({ - 'count': 6, - 'next': '%s/api/v2/collections/namespace/collection/versions/?page=2' % galaxy_server.api_server, - 'previous': None, - 'results': [ - { - 'href': '%s/api/v2/collections/namespace/collection/versions/2.0.0/' % galaxy_server.api_server, - 'version': '2.0.0', - }, - { - 'href': '%s/api/v2/collections/namespace/collection/versions/2.0.1/' % galaxy_server.api_server, - 'version': '2.0.1', - }, - { - 'href': '%s/api/v2/collections/namespace/collection/versions/2.0.2/' % galaxy_server.api_server, - 'version': '2.0.2', - }, - ] - }) - json_str2 = json.dumps({ - 'count': 6, - 'next': None, - 'previous': '%s/api/v2/collections/namespace/collection/versions/?page=1' % galaxy_server.api_server, - 'results': [ - { - 'href': '%s/api/v2/collections/namespace/collection/versions/2.0.3/' % galaxy_server.api_server, - 'version': '2.0.3', - }, - { - 'href': '%s/api/v2/collections/namespace/collection/versions/2.0.4/' % galaxy_server.api_server, - 'version': '2.0.4', - }, - { - 'href': '%s/api/v2/collections/namespace/collection/versions/2.0.5/' % galaxy_server.api_server, - 'version': '2.0.5', - }, - ] - }) - mock_open = MagicMock() - mock_open.side_effect = (StringIO(to_text(json_str1)), StringIO(to_text(json_str2))) - monkeypatch.setattr(collection, 'open_url', mock_open) - - mock_avail_ver = MagicMock() - mock_avail_ver.return_value = {'v2': '/api/v2'} - monkeypatch.setattr(collection, 'get_available_api_versions', mock_avail_ver) +def test_build_requirement_from_name_multiple_version_results(galaxy_server, monkeypatch): + mock_get_versions = MagicMock() + mock_get_versions.return_value = ['2.0.0', '2.0.1', '2.0.2', '2.0.3', '2.0.4', '2.0.5'] + monkeypatch.setattr(galaxy_server, 'get_collection_versions', mock_get_versions) actual = collection.CollectionRequirement.from_name('namespace.collection', [galaxy_server], '!=2.0.2', True, True) @@ -626,13 +476,8 @@ def test_build_requirement_from_name_multiple_version_results(galaxy_server, mon assert actual.latest_version == u'2.0.5' assert actual.dependencies is None - assert mock_open.call_count == 2 - assert mock_open.mock_calls[0][1][0] == u"%s/api/v2/collections/namespace/collection/versions/" \ - % galaxy_server.api_server - assert mock_open.mock_calls[0][2] == {'validate_certs': True, "headers": {}} - assert mock_open.mock_calls[1][1][0] == u"%s/api/v2/collections/namespace/collection/versions/?page=2" \ - % galaxy_server.api_server - assert mock_open.mock_calls[1][2] == {'validate_certs': True, "headers": {}} + assert mock_get_versions.call_count == 1 + assert mock_get_versions.mock_calls[0][1] == ('namespace', 'collection') @pytest.mark.parametrize('versions, requirement, expected_filter, expected_latest', [ @@ -767,14 +612,10 @@ def test_install_collection_with_download(galaxy_server, collection_artifact, mo temp_path = os.path.join(os.path.split(collection_tar)[0], b'temp') os.makedirs(temp_path) + meta = api.CollectionVersionMetadata('ansible_namespace', 'collection', '0.1.0', 'https://downloadme.com', + 'myhash', {}) req = collection.CollectionRequirement('ansible_namespace', 'collection', None, galaxy_server, - ['0.1.0'], '*', False) - req._galaxy_info = { - 'download_url': 'https://downloadme.com', - 'artifact': { - 'sha256': 'myhash', - }, - } + ['0.1.0'], '*', False, metadata=meta) req.install(to_text(output_path), temp_path) # Ensure the temp directory is empty, nothing is left behind -- cgit v1.2.1