Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion qase-python-commons/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}]
Expand Down
83 changes: 81 additions & 2 deletions qase-python-commons/src/qase/commons/client/api_v2_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict
from typing import Dict, Union, Optional

import certifi
from qase.api_client_v2 import ResultsApi, ResultCreateFields
Expand All @@ -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")
Expand All @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion qase-python-commons/src/qase/commons/reporters/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
9 changes: 2 additions & 7 deletions qase-python-commons/src/qase/commons/reporters/testops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 = []
Expand All @@ -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()
Expand All @@ -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 = []
Expand All @@ -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()
Expand All @@ -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 = []
Expand Down