diff --git a/qase-python-commons/pyproject.toml b/qase-python-commons/pyproject.toml index 386452e1..21f4a212 100644 --- a/qase-python-commons/pyproject.toml +++ b/qase-python-commons/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "qase-python-commons" -version = "4.1.6" +version = "4.1.7" description = "A library for Qase TestOps and Qase Report" readme = "README.md" authors = [{name = "Qase Team", email = "support@qase.io"}] diff --git a/qase-python-commons/src/qase/commons/client/api_v2_client.py b/qase-python-commons/src/qase/commons/client/api_v2_client.py index d8b0868b..69e4898b 100644 --- a/qase-python-commons/src/qase/commons/client/api_v2_client.py +++ b/qase-python-commons/src/qase/commons/client/api_v2_client.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Dict, Union, Optional import certifi from qase.api_client_v2 import ResultsApi, ResultCreateFields @@ -20,11 +20,16 @@ from ..models import Attachment, Result from ..models.config.qaseconfig import QaseConfig from ..models.step import StepType, Step +from ..util.host_data import HostData class ApiV2Client(ApiV1Client): - def __init__(self, config: QaseConfig, logger: Logger): + def __init__(self, config: QaseConfig, logger: Logger, host_data: Optional[HostData] = None, + framework: Union[str, None] = None, reporter_name: Union[str, None] = None): ApiV1Client.__init__(self, config, logger) + self.host_data = host_data or {} + self.framework = framework + self.reporter_name = reporter_name try: self.logger.log_debug("Preparing API V2 client") @@ -40,10 +45,84 @@ def __init__(self, config: QaseConfig, logger: Logger): self.web = f'https://{host}' self.client_v2 = ApiClient(configuration) + + # Add X-Client and X-Platform headers + self._add_client_headers() + self.logger.log_debug("API V2 client prepared") except Exception as e: self.logger.log(f"Error at preparing API V2 client: {e}", "error") raise ReporterException(e) + + def _add_client_headers(self): + """Add X-Client and X-Platform headers to API client""" + try: + # Use host_data passed from Core reporter + host_data = self.host_data + + # Use framework and reporter_name for names in X-Client header + framework = self.framework + reporter_name = self.reporter_name + + # Build X-Client header + # Format: reporter=qase-pytest;reporter_version=v1.0.0;framework=pytest;framework_version=7.0.0;client_version_v1=v1.0.0;client_version_v2=v2.0.0;core_version=v1.5.0 + x_client_parts = [] + + if reporter_name: + x_client_parts.append(f"reporter={reporter_name}") + reporter_version = host_data.get('reporter', '') + if reporter_version: + x_client_parts.append(f"reporter_version=v{reporter_version}") + + if framework: + x_client_parts.append(f"framework={framework}") + framework_version = host_data.get('framework', '') + if framework_version: + x_client_parts.append(f"framework_version={framework_version}") + + client_v1_version = host_data.get('apiClientV1', '') + if client_v1_version: + x_client_parts.append(f"client_version_v1=v{client_v1_version}") + + client_v2_version = host_data.get('apiClientV2', '') + if client_v2_version: + x_client_parts.append(f"client_version_v2=v{client_v2_version}") + + core_version = host_data.get('commons', '') + if core_version: + x_client_parts.append(f"core_version=v{core_version}") + + x_client = ";".join(x_client_parts) + + # Build X-Platform header + # Format: os=Linux;arch=aarch64;python=3.9.0;pip=22.0.0 + x_platform_parts = [] + + os_name = host_data.get('system', '') + if os_name: + x_platform_parts.append(f"os={os_name}") + + arch = host_data.get('arch', '') + if arch: + x_platform_parts.append(f"arch={arch}") + + python_version = host_data.get('python', '') + if python_version: + x_platform_parts.append(f"python={python_version}") + + pip_version = host_data.get('pip', '') + if pip_version: + x_platform_parts.append(f"pip={pip_version}") + + x_platform = ";".join(x_platform_parts) + + # Add headers to client + if x_client: + self.client_v2.default_headers['X-Client'] = x_client + if x_platform: + self.client_v2.default_headers['X-Platform'] = x_platform + except Exception as e: + self.logger.log(f"Error adding client headers: {e}", "error") def send_results(self, project_code: str, run_id: str, results: []) -> None: api_results = ResultsApi(self.client_v2) diff --git a/qase-python-commons/src/qase/commons/reporters/core.py b/qase-python-commons/src/qase/commons/reporters/core.py index d96b7e66..206b8544 100644 --- a/qase-python-commons/src/qase/commons/reporters/core.py +++ b/qase-python-commons/src/qase/commons/reporters/core.py @@ -43,13 +43,22 @@ def __init__(self, config: ConfigManager, framework: Union[str, None] = None, host_data = get_host_info(framework, reporter_name) self.logger.log_debug(f"Host data: {host_data}") + # Store framework and reporter_name for passing to reporters + self.framework = framework + self.reporter_name = reporter_name + self.host_data = host_data + # Reading reporter mode from config file mode = self.config.mode if mode == Mode.testops: try: self._load_testops_plan() - self.reporter = QaseTestOps(config=self.config, logger=self.logger) + # Create API client with host_data for headers + from ..client.api_v2_client import ApiV2Client + api_client = ApiV2Client(self.config, self.logger, host_data=host_data, + framework=framework, reporter_name=reporter_name) + self.reporter = QaseTestOps(config=self.config, logger=self.logger, client=api_client) except Exception as e: self.logger.log('Failed to initialize TestOps reporter. Using fallback.', 'info') self.logger.log(e, 'error') diff --git a/qase-python-commons/src/qase/commons/reporters/testops.py b/qase-python-commons/src/qase/commons/reporters/testops.py index 095fa20e..44a4eb25 100644 --- a/qase-python-commons/src/qase/commons/reporters/testops.py +++ b/qase-python-commons/src/qase/commons/reporters/testops.py @@ -4,7 +4,6 @@ from datetime import datetime from typing import List, Union from .. import Logger, ReporterException -from ..client.api_v2_client import ApiV2Client from ..client.base_api_client import BaseApiClient from ..models import Result from ..models.config.qaseconfig import QaseConfig @@ -15,12 +14,11 @@ class QaseTestOps: - def __init__(self, config: QaseConfig, logger: Logger) -> None: + def __init__(self, config: QaseConfig, logger: Logger, client: BaseApiClient) -> None: self.config = config self.logger = logger self.__baseUrl = self.__get_host(config.testops.api.host) - - self.client = self._prepare_client() + self.client = client run_id = self.config.testops.run.id plan_id = self.config.testops.plan.id @@ -68,9 +66,6 @@ def __init__(self, config: QaseConfig, logger: Logger) -> None: """Verify that project exists in TestOps""" self.client.get_project(self.project_code) - def _prepare_client(self) -> BaseApiClient: - return ApiV2Client(self.config, self.logger) - def _send_results_threaded(self, results): try: self.client.send_results(self.project_code, self.run_id, results) diff --git a/qase-python-commons/tests/tests_qase_commons/test_public_report_link.py b/qase-python-commons/tests/tests_qase_commons/test_public_report_link.py index d58dff13..9b9ab0d5 100644 --- a/qase-python-commons/tests/tests_qase_commons/test_public_report_link.py +++ b/qase-python-commons/tests/tests_qase_commons/test_public_report_link.py @@ -171,13 +171,13 @@ def test_enable_public_report_inherits_from_v1(self): class TestPublicReportLinkReporter: """Test QaseTestOps reporter integration""" - @patch('qase.commons.reporters.testops.ApiV2Client') - def test_complete_run_with_public_report_enabled(self, mock_client_class): + def test_complete_run_with_public_report_enabled(self): """Test complete_run method when public report is enabled""" # Mock client mock_client = Mock() mock_client.enable_public_report.return_value = "https://app.qase.io/public/report/abc123" - mock_client_class.return_value = mock_client + mock_client.get_project.return_value = None + mock_client.get_environment.return_value = None # Mock config mock_config = Mock() @@ -203,8 +203,8 @@ def test_complete_run_with_public_report_enabled(self, mock_client_class): # Mock logger mock_logger = Mock() - # Create reporter - reporter = QaseTestOps(mock_config, mock_logger) + # Create reporter with mock client + reporter = QaseTestOps(mock_config, mock_logger, client=mock_client) reporter.run_id = 123 reporter.project_code = "TEST" reporter.results = [] @@ -217,12 +217,12 @@ def test_complete_run_with_public_report_enabled(self, mock_client_class): mock_client.enable_public_report.assert_called_once_with("TEST", 123) mock_logger.log.assert_called_with("Public report link: https://app.qase.io/public/report/abc123", "info") - @patch('qase.commons.reporters.testops.ApiV2Client') - def test_complete_run_with_public_report_disabled(self, mock_client_class): + def test_complete_run_with_public_report_disabled(self): """Test complete_run method when public report is disabled""" # Mock client mock_client = Mock() - mock_client_class.return_value = mock_client + mock_client.get_project.return_value = None + mock_client.get_environment.return_value = None # Mock config mock_config = Mock() @@ -248,8 +248,8 @@ def test_complete_run_with_public_report_disabled(self, mock_client_class): # Mock logger mock_logger = Mock() - # Create reporter - reporter = QaseTestOps(mock_config, mock_logger) + # Create reporter with mock client + reporter = QaseTestOps(mock_config, mock_logger, client=mock_client) reporter.run_id = 123 reporter.project_code = "TEST" reporter.results = [] @@ -261,13 +261,13 @@ def test_complete_run_with_public_report_disabled(self, mock_client_class): mock_client.complete_run.assert_called_once_with("TEST", 123) mock_client.enable_public_report.assert_not_called() - @patch('qase.commons.reporters.testops.ApiV2Client') - def test_complete_run_public_report_failure(self, mock_client_class): + def test_complete_run_public_report_failure(self): """Test complete_run method when public report generation fails""" # Mock client mock_client = Mock() mock_client.enable_public_report.return_value = None - mock_client_class.return_value = mock_client + mock_client.get_project.return_value = None + mock_client.get_environment.return_value = None # Mock config mock_config = Mock() @@ -293,8 +293,8 @@ def test_complete_run_public_report_failure(self, mock_client_class): # Mock logger mock_logger = Mock() - # Create reporter - reporter = QaseTestOps(mock_config, mock_logger) + # Create reporter with mock client + reporter = QaseTestOps(mock_config, mock_logger, client=mock_client) reporter.run_id = 123 reporter.project_code = "TEST" reporter.results = []