diff --git a/CHANGES/111.misc b/CHANGES/111.misc new file mode 100644 index 0000000..e073048 --- /dev/null +++ b/CHANGES/111.misc @@ -0,0 +1 @@ +Converted functional tests to use pytest fixtures. diff --git a/pulp_maven/tests/functional/api/test_crud_remotes.py b/pulp_maven/tests/functional/api/test_crud_remotes.py index 70b2e47..ff664c3 100644 --- a/pulp_maven/tests/functional/api/test_crud_remotes.py +++ b/pulp_maven/tests/functional/api/test_crud_remotes.py @@ -1,122 +1,69 @@ """Tests that CRUD maven remotes.""" -import unittest - -from requests.exceptions import HTTPError - -from pulp_smash import api, config, utils - -from pulp_maven.tests.functional.constants import MAVEN_REMOTE_PATH -from pulp_maven.tests.functional.utils import skip_if, gen_maven_remote -from pulp_maven.tests.functional.utils import set_up_module as setUpModule # noqa:F401 - - -class CRUDRemotesTestCase(unittest.TestCase): - """CRUD remotes.""" - - @classmethod - def setUpClass(cls): - """Create class-wide variables.""" - cls.cfg = config.get_config() - cls.client = api.Client(cls.cfg, api.json_handler) - - def test_01_create_remote(self): - """Create a remote.""" - body = _gen_verbose_remote() - type(self).remote = self.client.post(MAVEN_REMOTE_PATH, body) - for key in ("username", "password"): - del body[key] - for key, val in body.items(): - with self.subTest(key=key): - self.assertEqual(self.remote[key], val) - - @skip_if(bool, "remote", False) - def test_02_create_same_name(self): - """Try to create a second remote with an identical name. - - See: `Pulp Smash #1055 - `_. - """ - body = gen_maven_remote() - body["name"] = self.remote["name"] - with self.assertRaises(HTTPError): - self.client.post(MAVEN_REMOTE_PATH, body) - - @skip_if(bool, "remote", False) - def test_02_read_remote(self): - """Read a remote by its href.""" - remote = self.client.get(self.remote["pulp_href"]) - for key, val in self.remote.items(): - with self.subTest(key=key): - self.assertEqual(remote[key], val) - - @skip_if(bool, "remote", False) - def test_02_read_remotes(self): - """Read a remote by its name.""" - page = self.client.get(MAVEN_REMOTE_PATH, params={"name": self.remote["name"]}) - self.assertEqual(len(page["results"]), 1) - for key, val in self.remote.items(): - with self.subTest(key=key): - self.assertEqual(page["results"][0][key], val) - - @skip_if(bool, "remote", False) - def test_03_partially_update(self): - """Update a remote using HTTP PATCH.""" - body = _gen_verbose_remote() - self.client.patch(self.remote["pulp_href"], body) - for key in ("username", "password"): - del body[key] - type(self).remote = self.client.get(self.remote["pulp_href"]) - for key, val in body.items(): - with self.subTest(key=key): - self.assertEqual(self.remote[key], val) - - @skip_if(bool, "remote", False) - def test_04_fully_update(self): - """Update a remote using HTTP PUT.""" - body = _gen_verbose_remote() - self.client.put(self.remote["pulp_href"], body) - for key in ("username", "password"): - del body[key] - type(self).remote = self.client.get(self.remote["pulp_href"]) - for key, val in body.items(): - with self.subTest(key=key): - self.assertEqual(self.remote[key], val) - - @skip_if(bool, "remote", False) - def test_05_delete(self): - """Delete a remote.""" - self.client.delete(self.remote["pulp_href"]) - with self.assertRaises(HTTPError): - self.client.get(self.remote["pulp_href"]) - - -class CreateRemoteNoURLTestCase(unittest.TestCase): - """Verify whether is possible to create a remote without a URL.""" - - def test_all(self): - """Verify whether is possible to create a remote without a URL. - - This test targets the following issues: - - * `Pulp #3395 `_ - * `Pulp Smash #984 `_ - """ - body = gen_maven_remote() - del body["url"] - with self.assertRaises(HTTPError): - api.Client(config.get_config()).post(MAVEN_REMOTE_PATH, body) - - -def _gen_verbose_remote(): - """Return a semi-random dict for use in defining a remote. - - For most tests, it's desirable to create remotes with as few attributes - as possible, so that the tests can specifically target and attempt to break - specific features. This module specifically targets remotes, so it makes - sense to provide as many attributes as possible. - - Note that 'username' and 'password' are write-only attributes. - """ - attrs = gen_maven_remote() - attrs.update({"password": utils.uuid4(), "username": utils.uuid4()}) - return attrs +import json +import uuid + +import pytest + +from pulpcore.client.pulp_maven.exceptions import ApiException + + +@pytest.mark.parallel +def test_remote_crud_workflow(maven_remote_api_client, gen_object_with_cleanup, monitor_task): + remote_data = {"name": str(uuid.uuid4()), "url": "http://example.com"} + remote = gen_object_with_cleanup(maven_remote_api_client, remote_data) + assert remote.url == remote_data["url"] + assert remote.name == remote_data["name"] + + with pytest.raises(ApiException) as exc: + gen_object_with_cleanup(maven_remote_api_client, remote_data) + assert exc.value.status == 400 + assert json.loads(exc.value.body) == {"name": ["This field must be unique."]} + + update_response = maven_remote_api_client.partial_update( + remote.pulp_href, {"url": "https://example.com"} + ) + task = monitor_task(update_response.task) + assert task.created_resources == [] + + remote = maven_remote_api_client.read(remote.pulp_href) + assert remote.url == "https://example.com" + + all_new_remote_data = {"name": str(uuid.uuid4()), "url": "http://example.com"} + update_response = maven_remote_api_client.update(remote.pulp_href, all_new_remote_data) + task = monitor_task(update_response.task) + assert task.created_resources == [] + + remote = maven_remote_api_client.read(remote.pulp_href) + assert remote.name == all_new_remote_data["name"] + assert remote.url == all_new_remote_data["url"] + + +@pytest.mark.parallel +def test_create_maven_remote_with_invalid_parameter( + maven_remote_api_client, gen_object_with_cleanup +): + unexpected_field_remote_data = { + "name": str(uuid.uuid4()), + "url": "http://example.com", + "foo": "bar", + } + + with pytest.raises(ApiException) as exc: + gen_object_with_cleanup(maven_remote_api_client, unexpected_field_remote_data) + assert exc.value.status == 400 + assert json.loads(exc.value.body) == {"foo": ["Unexpected field"]} + + +@pytest.mark.parallel +def test_create_maven_remote_without_url(maven_remote_api_client, gen_object_with_cleanup): + with pytest.raises(ApiException) as exc: + gen_object_with_cleanup(maven_remote_api_client, {"name": str(uuid.uuid4())}) + assert exc.value.status == 400 + assert json.loads(exc.value.body) == {"url": ["This field is required."]} + + +@pytest.mark.parallel +def test_default_remote_policy_immediate(maven_remote_api_client, gen_object_with_cleanup): + remote_data = {"name": str(uuid.uuid4()), "url": "http://example.com"} + remote = gen_object_with_cleanup(maven_remote_api_client, remote_data) + assert remote.policy == "immediate" diff --git a/pulp_maven/tests/functional/api/test_download_content.py b/pulp_maven/tests/functional/api/test_download_content.py index 0ed19d3..c71f04f 100644 --- a/pulp_maven/tests/functional/api/test_download_content.py +++ b/pulp_maven/tests/functional/api/test_download_content.py @@ -1,82 +1,43 @@ """Tests that verify download of content served by Pulp.""" import hashlib -import unittest -from random import choice from urllib.parse import urljoin -from pulp_smash import api, config, utils -from pulp_smash.pulp3.utils import gen_distribution, gen_repo +from pulp_maven.tests.functional.utils import download_file -from pulp_maven.tests.functional.utils import gen_maven_remote, get_maven_content_paths -from pulp_maven.tests.functional.constants import ( - MAVEN_CONTENT_PATH, - MAVEN_DISTRIBUTION_PATH, - MAVEN_FIXTURE_URL, - MAVEN_REMOTE_PATH, - MAVEN_REPO_PATH, -) -from pulp_maven.tests.functional.utils import set_up_module as setUpModule # noqa:F401 +def test_download_content( + maven_distribution_factory, maven_remote_factory, maven_artifact_api_client +): + """Verify whether content served by pulp can be downloaded. -class DownloadContentTestCase(unittest.TestCase): - """Verify whether content served by pulp can be downloaded.""" + The process of creating a Maven mirror is: - def test_all(self): - """Verify whether content served by pulp can be downloaded. + 1. Create a Maven Remote with a URL pointing to the root of a Maven repository. + 2. Create a distribution with the remote set HREF from 1. - The process of creating a Maven mirror is simple: + Do the following: - 1. Create a Maven Remote with a URL pointing to the root of a Maven repository. - 2. Create a distribution with the remote set HREF from 1. + 1. Create a Maven Remote and a Distribution. + 2. Select a random content unit in the distribution. Download that + content unit from Pulp, and verify that the content unit has the + same checksum when fetched directly from Maven Central. + """ + remote = maven_remote_factory(url="https://repo1.maven.org/maven2/") + distribution = maven_distribution_factory(remote=remote.pulp_href) - Do the following: + # Pick a content unit, and download it from the remote repository + unit_path = "academy/alex/custommatcher/1.0/custommatcher-1.0-javadoc.jar.sha1" + remote_unit_url = urljoin(remote.url, unit_path) + downloaded_file = download_file(remote_unit_url) + remote_unit_checksum = hashlib.sha256(downloaded_file.body).hexdigest() - 1. Create a Maven Remote and a Distribution. - 2. Select a random content unit in the distribution. Download that - content unit from Pulp, and verify that the content unit has the - same checksum when fetched directly from Maven Central. + # And from Pulp + pulp_unit_url = urljoin(distribution.base_url, unit_path) + downloaded_file = download_file(pulp_unit_url) + pulp_unit_checksum = hashlib.sha256(downloaded_file.body).hexdigest() - This test targets the following issues: + assert remote_unit_checksum == pulp_unit_checksum - * `Pulp #2895 `_ - * `Pulp Smash #872 `_ - """ - cfg = config.get_config() - client = api.Client(cfg, api.json_handler) - - repo = client.post(MAVEN_REPO_PATH, gen_repo()) - self.addCleanup(client.delete, repo["pulp_href"]) - - body = gen_maven_remote() - remote = client.post(MAVEN_REMOTE_PATH, body) - self.addCleanup(client.delete, remote["pulp_href"]) - - repo = client.get(repo["pulp_href"]) - - # Create a distribution. - body = gen_distribution() - body["remote"] = remote["pulp_href"] - response_dict = client.post(MAVEN_DISTRIBUTION_PATH, body) - dist_task = client.get(response_dict["task"]) - distribution_href = dist_task["created_resources"][0] - distribution = client.get(distribution_href) - self.addCleanup(client.delete, distribution["pulp_href"]) - - # Pick a content unit, and download it from both Pulp Fixtures… - unit_path = choice(get_maven_content_paths(repo)) - fixtures_hash = hashlib.sha256( - utils.http_get(urljoin(MAVEN_FIXTURE_URL, unit_path)) - ).hexdigest() - - # …and Pulp. - client.response_handler = api.safe_handler - - unit_url = urljoin(distribution["base_url"] + "/", unit_path) - - pulp_hash = hashlib.sha256(client.get(unit_url).content).hexdigest() - self.assertEqual(fixtures_hash, pulp_hash) - - # Check that Pulp created a MavenArtifact - content_filter_url = MAVEN_CONTENT_PATH + "?filename=custommatcher-1.0-javadoc.jar.sha1" - content_unit = client.get(content_filter_url) - self.assertEqual(1, content_unit.json()["count"]) + # Check that Pulp created a MavenArtifact + content_response = maven_artifact_api_client.list(filename="custommatcher-1.0-javadoc.jar.sha1") + assert content_response.count == 1 diff --git a/pulp_maven/tests/functional/conftest.py b/pulp_maven/tests/functional/conftest.py new file mode 100644 index 0000000..f52afe0 --- /dev/null +++ b/pulp_maven/tests/functional/conftest.py @@ -0,0 +1,71 @@ +import uuid + +import pytest + +from pulpcore.client.pulp_maven import ( + ApiClient, + ContentArtifactApi, + DistributionsMavenApi, + RemotesMavenApi, + RepositoriesMavenApi, +) + + +@pytest.fixture(scope="session") +def maven_client(_api_client_set, bindings_cfg): + api_client = ApiClient(bindings_cfg) + _api_client_set.add(api_client) + yield api_client + _api_client_set.remove(api_client) + + +@pytest.fixture(scope="session") +def maven_artifact_api_client(maven_client): + return ContentArtifactApi(maven_client) + + +@pytest.fixture(scope="session") +def maven_distro_api_client(maven_client): + return DistributionsMavenApi(maven_client) + + +@pytest.fixture(scope="session") +def maven_repo_api_client(maven_client): + return RepositoriesMavenApi(maven_client) + + +@pytest.fixture(scope="session") +def maven_remote_api_client(maven_client): + return RemotesMavenApi(maven_client) + + +@pytest.fixture +def maven_distribution_factory(maven_distro_api_client, gen_object_with_cleanup): + def _maven_distribution_factory(**kwargs): + data = {"base_path": str(uuid.uuid4()), "name": str(uuid.uuid4())} + data.update(kwargs) + return gen_object_with_cleanup(maven_distro_api_client, data) + + return _maven_distribution_factory + + +@pytest.fixture +def maven_repo_factory(maven_repo_api_client, gen_object_with_cleanup): + """A factory to generate a Maven Repository with auto-deletion after the test run.""" + + def _maven_repo_factory(**kwargs): + kwargs.setdefault("name", str(uuid.uuid4())) + return gen_object_with_cleanup(maven_repo_api_client, kwargs) + + yield _maven_repo_factory + + +@pytest.fixture +def maven_remote_factory(maven_remote_api_client, gen_object_with_cleanup): + """A factory to generate a Maven Remote with auto-deletion after the test run.""" + + def _maven_remote_factory(**kwargs): + kwargs.setdefault("name", str(uuid.uuid4())) + return gen_object_with_cleanup(maven_remote_api_client, kwargs) + + yield _maven_remote_factory diff --git a/pulp_maven/tests/functional/constants.py b/pulp_maven/tests/functional/constants.py deleted file mode 100644 index a922d79..0000000 --- a/pulp_maven/tests/functional/constants.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Constants for Pulp Maven plugin tests.""" -from urllib.parse import urljoin - -from pulp_smash.pulp3.constants import ( - BASE_CONTENT_PATH, - BASE_DISTRIBUTION_PATH, - BASE_PUBLISHER_PATH, - BASE_REMOTE_PATH, - BASE_REPO_PATH, -) - -DOWNLOAD_POLICIES = ["on_demand"] - -MAVEN_CONTENT_NAME = "maven.artifact" - -MAVEN_CONTENT_PATH = urljoin(BASE_CONTENT_PATH, "maven/artifact/") - -MAVEN_DISTRIBUTION_PATH = urljoin(BASE_DISTRIBUTION_PATH, "maven/maven/") - -MAVEN_REMOTE_PATH = urljoin(BASE_REMOTE_PATH, "maven/maven/") - -MAVEN_REPO_PATH = urljoin(BASE_REPO_PATH, "maven/maven/") - -MAVEN_PUBLISHER_PATH = urljoin(BASE_PUBLISHER_PATH, "maven/maven/") - -MAVEN_FIXTURE_URL = "https://repo1.maven.org/maven2/" diff --git a/pulp_maven/tests/functional/utils.py b/pulp_maven/tests/functional/utils.py index be079e0..1130a04 100644 --- a/pulp_maven/tests/functional/utils.py +++ b/pulp_maven/tests/functional/utils.py @@ -1,98 +1,34 @@ +# coding=utf-8 """Utilities for tests for the maven plugin.""" -from functools import partial -from unittest import SkipTest +import aiohttp +import asyncio +from dataclasses import dataclass -from pulp_smash import api, selectors -from pulp_smash.pulp3.utils import ( - gen_remote, - gen_repo, - get_content, - require_pulp_3, - require_pulp_plugins, - sync, -) -from pulp_maven.tests.functional.constants import ( - MAVEN_CONTENT_NAME, - MAVEN_CONTENT_PATH, - MAVEN_FIXTURE_URL, - MAVEN_REMOTE_PATH, - MAVEN_REPO_PATH, -) +@dataclass +class Download: + """Class for representing a downloaded file.""" + body: bytes + response_obj: aiohttp.ClientResponse -def set_up_module(): - """Skip tests Pulp 3 isn't under test or if pulp_maven isn't installed.""" - require_pulp_3(SkipTest) - require_pulp_plugins({"maven"}, SkipTest) + def __init__(self, body, response_obj): + self.body = body + self.response_obj = response_obj -def gen_maven_remote(**kwargs): - """Return a semi-random dict for use in creating a maven Remote. +def download_file(url, auth=None, headers=None): + """Download a file. - :param url: The URL of an external content source. + :param url: str URL to the file to download + :param auth: `aiohttp.BasicAuth` containing basic auth credentials + :param headers: dict of headers to send with the GET request + :return: Download """ - remote = gen_remote(MAVEN_FIXTURE_URL) - maven_extra_fields = {**kwargs} - remote.update(**maven_extra_fields) - return remote + return asyncio.run(_download_file(url, auth=auth, headers=headers)) -def get_maven_content_paths(repo): - """ - Return a list of content in the fixture repo. - """ - return ["academy/alex/custommatcher/1.0/custommatcher-1.0-javadoc.jar.sha1"] - - -def get_maven_content_unit_paths(repo): - """Return the relative path of content units present in a maven repository. - - :param repo: A dict of information about the repository. - :returns: A list with the paths of units present in a given repository. - """ - # FIXME: The "relative_path" is actually a file path and name - # It's just an example -- this needs to be replaced with an implementation that works - # for repositories of this content type. - return [content_unit["relative_path"] for content_unit in get_content(repo)[MAVEN_CONTENT_NAME]] - - -def gen_maven_content_attrs(artifact): - """Generate a dict with content unit attributes. - - :param: artifact: A dict of info about the artifact. - :returns: A semi-random dict for use in creating a content unit. - """ - # FIXME: Add content specific metadata here. - return {"_artifact": artifact["pulp_href"]} - - -def populate_pulp(cfg, url=MAVEN_FIXTURE_URL): - """Add maven contents to Pulp. - - :param pulp_smash.config.PulpSmashConfig: Information about a Pulp application. - :param url: The maven repository URL. Defaults to - :data:`pulp_smash.constants.MAVEN_FIXTURE_URL` - :returns: A list of dicts, where each dict describes one file content in Pulp. - """ - client = api.Client(cfg, api.json_handler) - remote = {} - repo = {} - try: - remote.update(client.post(MAVEN_REMOTE_PATH, gen_maven_remote(url))) - repo.update(client.post(MAVEN_REPO_PATH, gen_repo())) - sync(cfg, remote, repo) - finally: - if remote: - client.delete(remote["pulp_href"]) - if repo: - client.delete(repo["pulp_href"]) - return client.get(MAVEN_CONTENT_PATH)["results"] - - -skip_if = partial(selectors.skip_if, exc=SkipTest) -"""The ``@skip_if`` decorator, customized for unittest. - -:func:`pulp_smash.selectors.skip_if` is test runner agnostic. This function is -identical, except that ``exc`` has been set to ``unittest.SkipTest``. -""" +async def _download_file(url, auth=None, headers=None): + async with aiohttp.ClientSession(auth=auth, raise_for_status=True) as session: + async with session.get(url, verify_ssl=False, headers=headers) as response: + return Download(body=await response.read(), response_obj=response)