From e47f852eee29c813713a7f47f200c8218b1617d9 Mon Sep 17 00:00:00 2001 From: Jiri Hnidek Date: Fri, 13 Feb 2026 16:27:19 +0100 Subject: [PATCH 1/3] test: Added first behave integration test for productid plugin * Card ID: CCT-1921 * Only one scenario is added. It tests that product certificate is installed, when RPM is installed from testing repository Signed-off-by: Jiri Hnidek --- features/productid.feature | 9 +++ features/steps/test_impl_productid.py | 106 ++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 features/productid.feature create mode 100644 features/steps/test_impl_productid.py diff --git a/features/productid.feature b/features/productid.feature new file mode 100644 index 0000000..89891d7 --- /dev/null +++ b/features/productid.feature @@ -0,0 +1,9 @@ +Feature: Management of productid certificate by libdnf5 plugin productid + + Scenario: Install one productid certificate for one installed RPM + Given system is registered against candlepin server + Given repositories are enabled + | repo_id | + | never-enabled-content-37060 | + When rpm "slow-eagle" is installed from RPM repository + Then productid certificate "37060.pem" is installed in "/etc/pki/product" diff --git a/features/steps/test_impl_productid.py b/features/steps/test_impl_productid.py new file mode 100644 index 0000000..3ce3063 --- /dev/null +++ b/features/steps/test_impl_productid.py @@ -0,0 +1,106 @@ +from behave import * + +import subprocess +import os +import json + +def run(cmd, shell=True, cwd=None): + """ + Run a command. + Return exitcode, stdout, stderr + """ + + proc = subprocess.Popen( + cmd, + shell=shell, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + errors="surrogateescape", + ) + + stdout, stderr = proc.communicate() + return proc.returncode, stdout, stderr + + +def run_in_context(context, cmd, can_fail=False, expected_exit_code=None, **run_args): + """ + Run a command in the context of a behave scenario. + :param context: behave context + :param cmd: command to run + :param can_fail: whether the command can fail without raising an error + :param expected_exit_code: expected exit code of the command + :param run_args: additional arguments to pass to subprocess.Popen + :return: None + """ + if getattr(context, "faketime", None) is not None: + cmd = 'NO_FAKE_STAT=1 ' + context.faketime + cmd + + if getattr(context, "fake_kernel_release", None) is not None: + cmd = context.fake_kernel_release + cmd + + if getattr(context, "lc_all", None) is not None: + cmd = context.lc_all + cmd + + context.cmd = cmd + + if hasattr(context.scenario, "working_dir") and 'cwd' not in run_args: + run_args['cwd'] = context.scenario.working_dir + + context.cmd_exitcode, context.cmd_stdout, context.cmd_stderr = run(cmd, **run_args) + + if not can_fail and context.cmd_exitcode != 0: + raise AssertionError('Running command "%s" failed: %s' % (cmd, context.cmd_exitcode)) + elif expected_exit_code is not None and expected_exit_code != context.cmd_exitcode: + raise AssertionError( + 'Running command "%s" had unexpected exit code: %s' % (cmd, context.cmd_exitcode) + ) + +@given('system is registered against candlepin server') +def step_impl(context): + """ + Check if the system is registered against the candlepin server. If not, register it. + :param context: behave context + :return: None + """ + cmd = "rhc status --format json" + run_in_context(context, cmd, can_fail=False) + result = json.loads(context.cmd_stdout) + if not result["rhsm_connected"]: + cmd = "rhc connect --username admin --password admin --organization donaldduck" + run_in_context(context, cmd, can_fail=False) + +@given('repositories are enabled') +def step_impl(context): + """ + Enable repositories for the system. + :param context: behave context + :return: None + """ + for row in context.table: + cmd = f"dnf5 config-manager setopt {row['repo_id']}.enabled=1" + run_in_context(context, cmd, can_fail=False) + +@when('rpm "{rpm_name}" is installed from RPM repository') +def step_impl(context, rpm_name): + """ + Install RPM package from RPM repository. + :param context: behave context + :param rpm_name: name of the RPM package to install + :return: None + """ + cmd = f"dnf5 install -y {rpm_name}" + run_in_context(context, cmd, can_fail=False) + +@then('productid certificate "{product_cert_name}" is installed in "{product_cert_dir_path}"') +def step_impl(context, product_cert_name, product_cert_dir_path): + """ + Check if the productid certificate is installed in the specified directory. + :param context: behave context + :param product_cert_name: name of the productid certificate + :param product_cert_dir_path: path to the directory where the certificate should be installed + :return: None + """ + product_cert_path = os.path.join(product_cert_dir_path, product_cert_name) + assert os.path.exists(product_cert_path) \ No newline at end of file From 603f909d06b0e869462cf502ab6c530ff4b034f1 Mon Sep 17 00:00:00 2001 From: Jiri Hnidek Date: Fri, 13 Feb 2026 17:09:27 +0100 Subject: [PATCH 2/3] test(beahave): Added one more step * Card ID: CCT-1921 * Added check that productid.json contains wanted repo_id and product_id * Make black more happy Signed-off-by: Jiri Hnidek --- features/productid.feature | 3 ++ features/steps/test_impl_productid.py | 53 ++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/features/productid.feature b/features/productid.feature index 89891d7..a3208e2 100644 --- a/features/productid.feature +++ b/features/productid.feature @@ -7,3 +7,6 @@ Feature: Management of productid certificate by libdnf5 plugin productid | never-enabled-content-37060 | When rpm "slow-eagle" is installed from RPM repository Then productid certificate "37060.pem" is installed in "/etc/pki/product" + And product database contains + | repo_id | product_id | + | never-enabled-content-37060 | 37060 | diff --git a/features/steps/test_impl_productid.py b/features/steps/test_impl_productid.py index 3ce3063..1650bb9 100644 --- a/features/steps/test_impl_productid.py +++ b/features/steps/test_impl_productid.py @@ -4,6 +4,7 @@ import os import json + def run(cmd, shell=True, cwd=None): """ Run a command. @@ -35,7 +36,7 @@ def run_in_context(context, cmd, can_fail=False, expected_exit_code=None, **run_ :return: None """ if getattr(context, "faketime", None) is not None: - cmd = 'NO_FAKE_STAT=1 ' + context.faketime + cmd + cmd = "NO_FAKE_STAT=1 " + context.faketime + cmd if getattr(context, "fake_kernel_release", None) is not None: cmd = context.fake_kernel_release + cmd @@ -45,19 +46,23 @@ def run_in_context(context, cmd, can_fail=False, expected_exit_code=None, **run_ context.cmd = cmd - if hasattr(context.scenario, "working_dir") and 'cwd' not in run_args: - run_args['cwd'] = context.scenario.working_dir + if hasattr(context.scenario, "working_dir") and "cwd" not in run_args: + run_args["cwd"] = context.scenario.working_dir context.cmd_exitcode, context.cmd_stdout, context.cmd_stderr = run(cmd, **run_args) if not can_fail and context.cmd_exitcode != 0: - raise AssertionError('Running command "%s" failed: %s' % (cmd, context.cmd_exitcode)) + raise AssertionError( + 'Running command "%s" failed: %s' % (cmd, context.cmd_exitcode) + ) elif expected_exit_code is not None and expected_exit_code != context.cmd_exitcode: raise AssertionError( - 'Running command "%s" had unexpected exit code: %s' % (cmd, context.cmd_exitcode) + 'Running command "%s" had unexpected exit code: %s' + % (cmd, context.cmd_exitcode) ) -@given('system is registered against candlepin server') + +@given("system is registered against candlepin server") def step_impl(context): """ Check if the system is registered against the candlepin server. If not, register it. @@ -71,7 +76,8 @@ def step_impl(context): cmd = "rhc connect --username admin --password admin --organization donaldduck" run_in_context(context, cmd, can_fail=False) -@given('repositories are enabled') + +@given("repositories are enabled") def step_impl(context): """ Enable repositories for the system. @@ -82,6 +88,7 @@ def step_impl(context): cmd = f"dnf5 config-manager setopt {row['repo_id']}.enabled=1" run_in_context(context, cmd, can_fail=False) + @when('rpm "{rpm_name}" is installed from RPM repository') def step_impl(context, rpm_name): """ @@ -93,7 +100,10 @@ def step_impl(context, rpm_name): cmd = f"dnf5 install -y {rpm_name}" run_in_context(context, cmd, can_fail=False) -@then('productid certificate "{product_cert_name}" is installed in "{product_cert_dir_path}"') + +@then( + 'productid certificate "{product_cert_name}" is installed in "{product_cert_dir_path}"' +) def step_impl(context, product_cert_name, product_cert_dir_path): """ Check if the productid certificate is installed in the specified directory. @@ -103,4 +113,29 @@ def step_impl(context, product_cert_name, product_cert_dir_path): :return: None """ product_cert_path = os.path.join(product_cert_dir_path, product_cert_name) - assert os.path.exists(product_cert_path) \ No newline at end of file + assert os.path.exists(product_cert_path) + + +PRODUCTDB_PATH = "/var/lib/rhsm/productid.json" + + +@then("product database contains") +def step_impl(contex): + """ + Check if the product database contains the expected products and repositories. + :param contex: behave context + :return: None + """ + productdb_content = None + assert os.path.exists(PRODUCTDB_PATH) + with open(PRODUCTDB_PATH) as f: + productdb_content = json.loads(f.read()) + + assert productdb_content is not None + assert isinstance(productdb_content, dict) + for row in contex.table: + product_id = row["product_id"] + repo_id = row["repo_id"] + assert product_id in productdb_content + repo_list = productdb_content[product_id] + assert repo_id in repo_list From fa1e222230de7d03d97dd772ffc81820789c7e39 Mon Sep 17 00:00:00 2001 From: Jiri Hnidek Date: Tue, 24 Feb 2026 15:32:23 +0100 Subject: [PATCH 3/3] test: Extended integration tests * Added more tests and extend current steps Signed-off-by: Jiri Hnidek --- features/productid.feature | 85 ++++++++++++++++++++++++++- features/steps/test_impl_productid.py | 30 ++++++++-- 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/features/productid.feature b/features/productid.feature index a3208e2..1172085 100644 --- a/features/productid.feature +++ b/features/productid.feature @@ -1,12 +1,95 @@ +# Notes: if you want to extend your tests, then it could be useful to get +# the list of RPMs in given repository using following command: +# $ dnf list --available --repo=never-enabled-content-37060 + + Feature: Management of productid certificate by libdnf5 plugin productid + The productid libdnf5 plugin should install product certificate + from testing repositories to /etc/pki/product and add record + to "database" in /var/lib/rhsm/productid.json. When the last RPM + from given repository is removed, then corresponding product + certificate should be removed, when it is not needed by any other + repository. + Scenario: Install one productid certificate for one installed RPM Given system is registered against candlepin server Given repositories are enabled | repo_id | | never-enabled-content-37060 | - When rpm "slow-eagle" is installed from RPM repository + When rpms are installed from RPM repository + | rpm_name | + | slow-eagle | + Then productid certificate "37060.pem" is installed in "/etc/pki/product" + And product database contains + | repo_id | product_id | + | never-enabled-content-37060 | 37060 | + Then rpms are removed from system + | rpm_name | + | slow-eagle | + + + Scenario: Install one productid certificate for two installed RPM + Given system is registered against candlepin server + Given repositories are enabled + | repo_id | + | never-enabled-content-37060 | + When rpms are installed from RPM repository + | rpm_name | + | slow-eagle | + | tricky-frog | + Then productid certificate "37060.pem" is installed in "/etc/pki/product" + And product database contains + | repo_id | product_id | + | never-enabled-content-37060 | 37060 | + Then rpms are removed from system + | rpm_name | + | slow-eagle | + | tricky-frog | + + + Scenario: Install two productid certificates for two installed RPM + Given system is registered against candlepin server + Given repositories are enabled + | repo_id | + | never-enabled-content-37060 | + | awesomeos-100000000000002 | + When rpms are installed from RPM repository + | rpm_name | + | slow-eagle | + | awesome-rabbit | + Then productid certificate "37060.pem" is installed in "/etc/pki/product" + And productid certificate "100000000000002.pem" is installed in "/etc/pki/product" + And product database contains + | repo_id | product_id | + | never-enabled-content-37060 | 37060 | + | awesomeos-100000000000002 | 100000000000002 | + When rpms are removed from system + | rpm_name | + | awesome-rabbit | + Then product database contains + | repo_id | product_id | + | never-enabled-content-37060 | 37060 | + Then rpms are removed from system + | rpm_name | + | slow-eagle | + + Scenario: Install one productid certificates for two RPMs installed from different repository + Given system is registered against candlepin server + Given repositories are enabled + | repo_id | + | never-enabled-content-37060 | + | content-label-37060 | + When rpms are installed from RPM repository + | rpm_name | + | slow-eagle | + | cool-mouse | Then productid certificate "37060.pem" is installed in "/etc/pki/product" And product database contains | repo_id | product_id | | never-enabled-content-37060 | 37060 | + | content-label-37060 | 37060 | + Then rpms are removed from system + | rpm_name | + | slow-eagle | + | cool-mouse | diff --git a/features/steps/test_impl_productid.py b/features/steps/test_impl_productid.py index 1650bb9..a8ee8be 100644 --- a/features/steps/test_impl_productid.py +++ b/features/steps/test_impl_productid.py @@ -70,7 +70,7 @@ def step_impl(context): :return: None """ cmd = "rhc status --format json" - run_in_context(context, cmd, can_fail=False) + run_in_context(context, cmd, can_fail=True) result = json.loads(context.cmd_stdout) if not result["rhsm_connected"]: cmd = "rhc connect --username admin --password admin --organization donaldduck" @@ -89,15 +89,33 @@ def step_impl(context): run_in_context(context, cmd, can_fail=False) -@when('rpm "{rpm_name}" is installed from RPM repository') -def step_impl(context, rpm_name): +@when('rpms are installed from RPM repository') +def step_impl(context): + """ + Install RPM packages from the RPM repository. + :param context: behave context + :return: None + """ + rpm_list = [] + for row in context.table: + rpm_list.append(row['rpm_name']) + assert len(rpm_list) > 0 + cmd = f"dnf5 install -y {' '.join(rpm_list)}" + run_in_context(context, cmd, can_fail=False) + + +@step("rpms are removed from system") +def step_impl(context): """ - Install RPM package from RPM repository. + Remove RPMSs from the system. :param context: behave context - :param rpm_name: name of the RPM package to install :return: None """ - cmd = f"dnf5 install -y {rpm_name}" + rpm_list = [] + for row in context.table: + rpm_list.append(row['rpm_name']) + assert len(rpm_list) > 0 + cmd = f"dnf5 remove -y {' '.join(rpm_list)}" run_in_context(context, cmd, can_fail=False)