diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 999d2555..7c3e4d8e 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -33,7 +33,7 @@ def pkg_util(test_env): """Fixture to interact with the packaging utility :return: Packaging utility configured for the tests - :rtype: object of utils.PackagingUtility + :rtype: utils.PackagingUtility """ return utils.PackagingUtility(test_env["packaging_utility"], test_env["mbs_api"]) @@ -99,10 +99,7 @@ def clone_and_start_build(repo, pkg_util): builds = pkg_util.run("--optional", "rebuild_strategy=all") yield repo, builds for build in builds: - try: - pkg_util.cancel(build) - except sh.ErrorReturnCode: - pass # we don't need to bother with clean-up errors + pkg_util.cancel(build, ignore_errors=True) @pytest.fixture(scope="function") diff --git a/tests/integration/example.test.env.yaml b/tests/integration/example.test.env.yaml index 37d37770..1ea2549c 100644 --- a/tests/integration/example.test.env.yaml +++ b/tests/integration/example.test.env.yaml @@ -136,3 +136,6 @@ testdata: static_context: module: testmodule branch: test-static-context + rest_submit_module_build: + module: testmodule + branch: test-submit-module diff --git a/tests/integration/test_rest_api.py b/tests/integration/test_rest_build_state.py similarity index 100% rename from tests/integration/test_rest_api.py rename to tests/integration/test_rest_build_state.py diff --git a/tests/integration/test_rest_submit_build.py b/tests/integration/test_rest_submit_build.py new file mode 100644 index 00000000..43275f8c --- /dev/null +++ b/tests/integration/test_rest_submit_build.py @@ -0,0 +1,48 @@ +from requests import HTTPError + + +def assert_build_in_build_state(mbs, build): + """Assert build state was reached and then cancel the build using REST.""" + try: + mbs.wait_for_module_build(build, lambda bld: bld.get("state") == 2, timeout=10) + finally: + mbs.cancel_module_build(build.id) + + +def test_rest_submit_module_build(pkg_util, scenario, repo, mbs): + """Test module build submission. Tests only whether or not + build gets accepted and transitions successfully to the build state. + + Two variants: + * submit module build with modulemd yaml (test YAMLFileHandler) + * submit module build with scmurl (test SCMHandler) + ..are combined into one method to reuse 1 single test branch. + + Steps: + * Submit module build using module's SCM URL (HTTP POST). + * Assert that build reaches 'build' state. + * Cancel the build (HTTP PATCH) + """ + + # 1) SCMURL submission + repo.bump() + + scmurl = pkg_util.giturl().replace("#", "?#") + branch = scenario["branch"] + data = {"scmurl": scmurl, "branch": branch} + + builds = mbs.submit_module_build(data) + assert len(builds) == 1 + assert_build_in_build_state(mbs, builds[0]) + + # 2) YAML submission (might not be enabled, but if it is, let's test it) + repo.bump() + + data = {"modulemd": str(repo.modulemd)} + try: + builds = mbs.submit_module_build(data) + except HTTPError as e: + if "YAML submission is not enabled" not in e.response.text: + raise + else: + assert_build_in_build_state(mbs, builds[0]) diff --git a/tests/integration/utils.py b/tests/integration/utils.py index a94c5525..abbd0b34 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -20,6 +20,13 @@ our_sh = sh(_out=sys.stdout, _err=sys.stderr, _tee=True) from our_sh import Command, git, pushd # noqa +def get_kerberos_auth(): + """Get the 'default' Kerberos auth header field""" + # (!) User executing this request must be allowed to do so on the target MBS instance. + # MBS server does not support mutual auth, so make it optional (inspired by mbs-cli). + return requests_kerberos.HTTPKerberosAuth(mutual_authentication=requests_kerberos.OPTIONAL) + + class Koji: """Wrapper class to work with Koji @@ -113,14 +120,12 @@ class Repo: :attribute string module_name: name of the module stored in this repo :attribute string branch: name of the branch, the repo is checked-out - :attribute string giturl: GIT URL of the repo/branch :attribute dict _modulemd: Modulemd file as read from the repo """ def __init__(self, module_name, branch): self.module_name = module_name self.branch = branch - self._modulemd = None self._version = None @@ -243,18 +248,23 @@ class PackagingUtility: return stdout - def cancel(self, build): + def cancel(self, build, ignore_errors=False): """Cancel the module build :param Build build: the Build object of the module build to be cancelled. + :param bool ignore_errors: ignore when command fails (ErrorReturnCode exception) :return: Standard output of the "module-build-cancel command :rtype: str """ - stdout = self._packaging_utility("module-build-cancel", build.id).stdout.decode( - "utf-8") - return stdout + cmd = "module-build-cancel" + try: + return self._packaging_utility(cmd, build.id).stdout.decode("utf-8") + except sh.ErrorReturnCode: + if not ignore_errors: + raise def giturl(self): + """Get target URL of the current repository/branch""" return self._packaging_utility("giturl").stdout.decode("utf-8").strip() def clone(self, *args): @@ -519,9 +529,10 @@ class MBS: :param str stream: Stream name :param str order_desc_by: Optional sorting parameter e.g. "version" :return: list of Build objects - :rtype: list + :rtype: list[Build] """ url = f"{self._mbs_api}module-builds/" + payload = {'name': module, "stream": stream} if order_desc_by: payload.update({"order_desc_by": order_desc_by}) @@ -530,40 +541,25 @@ class MBS: return [Build(self._mbs_api, build["id"]) for build in r.json()["items"]] def import_module(self, scmurl): - """Import module from SCM URL (modulemd). + """Import module from SCM URL. :param str scmurl: :return: requests response :rtype: requests response object """ url = f"{self._mbs_api}import-module/" - headers = {"Content-Type": "application/json"} + data = json.dumps({'scmurl': scmurl}) - - # (!) User executing this request must be allowed to do so on the target MBS instance. - # MBS server does not support mutual auth, so make it optional (inspired by mbs-cli). - auth = requests_kerberos.HTTPKerberosAuth(mutual_authentication=requests_kerberos.OPTIONAL) - - response = requests.post( - url, - auth=auth, - headers=headers, - verify=False, - data=data - ) - try: - response.raise_for_status() - return response - except requests.exceptions.HTTPError: - # response message contains useful information, which requests module omits - pytest.fail(response.text) + r = requests.post(url, auth=get_kerberos_auth(), verify=False, data=data) + r.raise_for_status() + return r def get_module_builds(self, **kwargs): """Query MBS API on module-builds endpoint - :attribute **kwargs: options for the HTTP GET - :return: list of Build objects - :rtype: list + :return: matched build entries + :rtype: list[Build] + :Keyword Arguments: passed directly to the request as HTTP params. """ url = f"{self._mbs_api}module-builds/" r = requests.get(url, params=kwargs) @@ -574,9 +570,10 @@ class MBS: def get_module_build(self, build_data, **kwargs): """Query MBS API on module-builds endpoint for a specific build - :attribute build_data (int|Build): build ID + :param int|Build build_data: build ID :return: module build object :rtype: Build + :Keyword Arguments: passed directly to the request as HTTP params. """ build_id = self._get_build_id(build_data) url = f"{self._mbs_api}module-builds/{build_id}" @@ -585,6 +582,29 @@ class MBS: r.raise_for_status() return Build(self._mbs_api, r.json()["id"]) + def submit_module_build(self, data): + """Submit a module build with arbitrary payload. + + :param dict data: payload of the POST request + 1) SCMURL submission: data = {scmurl, branch} + 2) YAML submission: data = {modulemd: } + :return: submitted build(s) + :rtype: list[Build] + """ + url = f"{self._mbs_api}module-builds/" + + r = requests.post(url, verify=False, auth=get_kerberos_auth(), data=json.dumps(data)) + r.raise_for_status() + return [Build(self._mbs_api, build["id"]) for build in r.json()] + + def cancel_module_build(self, build_id): + """PATCH the state field of a module build to cancel it""" + url = f"{self._mbs_api}module-builds/{build_id}" + + data = json.dumps({"state": "failed"}) + response = requests.patch(url, auth=get_kerberos_auth(), verify=False, data=data) + response.raise_for_status() + def wait_for_module_build(self, build_data, predicate_func, timeout=60, interval=5): """Wait for module build. Wait until the specified function returns True.