diff --git a/composer.json b/composer.json index e6c51f4..764afe3 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "scripts": { "test": "phpunit" }, - "version": "2.1.9", + "version": "2.1.10", "config": { "platform": { "php": "8.0" diff --git a/composer.lock b/composer.lock index c66fa13..df89d95 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4ac95a3f4972a0ed4582734bf5daebfe", + "content-hash": "9bd541a5718548541c1e9547b558d2fd", "packages": [ { "name": "brick/math", @@ -1151,16 +1151,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -1203,9 +1203,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -1646,16 +1646,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.30", + "version": "9.6.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b69489b312503bf8fa6d75a76916919d7d2fa6d4" + "reference": "945d0b7f346a084ce5549e95289962972c4272e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b69489b312503bf8fa6d75a76916919d7d2fa6d4", - "reference": "b69489b312503bf8fa6d75a76916919d7d2fa6d4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5", + "reference": "945d0b7f346a084ce5549e95289962972c4272e5", "shasum": "" }, "require": { @@ -1729,7 +1729,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.30" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31" }, "funding": [ { @@ -1753,7 +1753,7 @@ "type": "tidelift" } ], - "time": "2025-12-01T07:35:08+00:00" + "time": "2025-12-06T07:45:52+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/Client/ApiClientV2.php b/src/Client/ApiClientV2.php index ca19ee3..4630e6c 100644 --- a/src/Client/ApiClientV2.php +++ b/src/Client/ApiClientV2.php @@ -5,6 +5,7 @@ namespace Qase\PhpCommons\Client; use Exception; +use GuzzleHttp\Client; use Qase\APIClientV2\Api\ResultsApi; use Qase\APIClientV2\Configuration; use Qase\APIClientV2\Model\CreateResultsRequestV2; @@ -22,12 +23,14 @@ use Qase\PhpCommons\Models\Relation; use Qase\PhpCommons\Models\Result; use Qase\PhpCommons\Models\Step; +use Qase\PhpCommons\Utils\HostInfo; class ApiClientV2 extends ApiClientV1 { private Configuration $clientV2Config; + private Client $clientV2; - public function __construct(LoggerInterface $logger, TestopsConfig $config) + public function __construct(LoggerInterface $logger, TestopsConfig $config, string $framework = "", string $reporterName = "", array $hostData = []) { parent::__construct($logger, $config); @@ -40,6 +43,12 @@ public function __construct(LoggerInterface $logger, TestopsConfig $config) } else { $this->clientV2Config->setHost('https://api-' . $host . '/v2'); } + + // Create GuzzleHttp Client with default headers + $headers = $this->buildHeaders($framework, $reporterName, $hostData); + $this->clientV2 = new Client([ + 'headers' => $headers + ]); } public function sendResults(string $code, int $runId, array $results): void @@ -56,7 +65,7 @@ public function sendResults(string $code, int $runId, array $results): void $this->logger->debug("Send results to project: " . json_encode($model)); - $resultsApi = new ResultsApi($this->client, $this->clientV2Config); + $resultsApi = new ResultsApi($this->clientV2, $this->clientV2Config); $resultsApi->createResultsV2($code, $runId, $model); } catch (Exception $e) { $this->logger->error("Error send results to project: " . $code . ', run: ' . $runId); @@ -168,4 +177,135 @@ public function runUpdateExternalIssue(string $code, string $type, array $links) // Delegate to parent class (ApiClientV1) implementation parent::runUpdateExternalIssue($code, $type, $links); } + + /** + * Build X-Client and X-Platform headers based on HostInfo data + * + * @param string $framework Framework name + * @param string $reporterName Reporter name + * @param array $hostData Host data from HostInfo + * @return array Headers array + */ + private function buildHeaders(string $framework = "", string $reporterName = "", array $hostData = []): array + { + $headers = []; + + // If hostData is empty, try to get it from HostInfo (fallback for backward compatibility) + if (empty($hostData)) { + $hostInfo = new HostInfo(); + $hostData = $hostInfo->getHostInfo($framework, $reporterName); + } + + // Build X-Client header + $xClientParts = []; + + if (!empty($reporterName)) { + $xClientParts[] = 'reporter=' . $reporterName; + } + + if (!empty($hostData['reporter'])) { + $reporterVersion = $this->normalizeVersion($hostData['reporter']); + if (!empty($reporterVersion)) { + $xClientParts[] = 'reporter_version=v' . $reporterVersion; + } + } + + if (!empty($framework)) { + $xClientParts[] = 'framework=' . $framework; + } + + if (!empty($hostData['framework'])) { + $frameworkVersion = $this->normalizeVersion($hostData['framework']); + if (!empty($frameworkVersion)) { + $xClientParts[] = 'framework_version=v' . $frameworkVersion; + } + } + + if (!empty($hostData['apiClientV1'])) { + $clientV1Version = $this->normalizeVersion($hostData['apiClientV1']); + if (!empty($clientV1Version)) { + $xClientParts[] = 'client_version_v1=v' . $clientV1Version; + } + } + + if (!empty($hostData['apiClientV2'])) { + $clientV2Version = $this->normalizeVersion($hostData['apiClientV2']); + if (!empty($clientV2Version)) { + $xClientParts[] = 'client_version_v2=v' . $clientV2Version; + } + } + + if (!empty($hostData['commons'])) { + $commonsVersion = $this->normalizeVersion($hostData['commons']); + if (!empty($commonsVersion)) { + $xClientParts[] = 'core_version=v' . $commonsVersion; + } + } + + if (!empty($xClientParts)) { + $headers['X-Client'] = implode(';', $xClientParts); + } + + // Build X-Platform header + $xPlatformParts = []; + + if (!empty($hostData['system'])) { + $osName = ucfirst($hostData['system']); + $xPlatformParts[] = 'os=' . $osName; + } + + if (!empty($hostData['arch'])) { + $xPlatformParts[] = 'arch=' . $hostData['arch']; + } + + if (!empty($hostData['php'])) { + $xPlatformParts[] = 'php=' . $hostData['php']; + } + + if (!empty($hostData['composer'])) { + $xPlatformParts[] = 'composer=' . $hostData['composer']; + } + + if (!empty($xPlatformParts)) { + $headers['X-Platform'] = implode(';', $xPlatformParts); + } + + return $headers; + } + + /** + * Normalize version string by removing constraints and prefixes + * + * @param string $version Version string from composer.json/composer.lock + * @return string Normalized version (e.g., "1.0.0" from "^1.0.0" or "v1.0.0") + */ + private function normalizeVersion(string $version): string + { + if (empty($version)) { + return ''; + } + + // Remove version constraints (^, ~, >=, etc.) + $version = preg_replace('/^[^0-9]*/', '', $version); + + // Remove 'v' prefix if present + $version = ltrim($version, 'v'); + + // Extract version number (e.g., "1.0.0" from "1.0.0.0" or "1.0.0-dev") + if (preg_match('/^(\d+\.\d+\.\d+)/', $version, $matches)) { + return $matches[1]; + } + + // If no match, try to extract at least major.minor + if (preg_match('/^(\d+\.\d+)/', $version, $matches)) { + return $matches[1] . '.0'; + } + + // If still no match, try to extract at least major + if (preg_match('/^(\d+)/', $version, $matches)) { + return $matches[1] . '.0.0'; + } + + return ''; + } } diff --git a/src/Reporters/ReporterFactory.php b/src/Reporters/ReporterFactory.php index 95f099e..64a9f61 100644 --- a/src/Reporters/ReporterFactory.php +++ b/src/Reporters/ReporterFactory.php @@ -27,8 +27,8 @@ public static function create(String $framework = "", String $reporterName = "") $hostData = $hostInfo->getHostInfo($framework, $reporterName); $logger->debug("Host data: " . json_encode($hostData)); $state = new StateManager(); - $reporter = self::createInternalReporter($logger, $config, $state); - $fallbackReporter = self::createInternalReporter($logger, $config, $state, true); + $reporter = self::createInternalReporter($logger, $config, $state, false, $framework, $reporterName, $hostData); + $fallbackReporter = self::createInternalReporter($logger, $config, $state, true, $framework, $reporterName, $hostData); // Create status mapping utility $statusMapping = new StatusMapping($logger); @@ -37,12 +37,12 @@ public static function create(String $framework = "", String $reporterName = "") return new CoreReporter($logger, $reporter, $fallbackReporter, $config->getRootSuite(), $statusMapping); } - private static function createInternalReporter(LoggerInterface $logger, QaseConfig $config, StateInterface $state, bool $fallback = false): ?InternalReporterInterface + private static function createInternalReporter(LoggerInterface $logger, QaseConfig $config, StateInterface $state, bool $fallback = false, string $framework = "", string $reporterName = "", array $hostData = []): ?InternalReporterInterface { $mode = $fallback ? $config->getFallback() : $config->getMode(); if ($mode === 'testops') { - return self::prepareTestopsReporter($logger, $config, $state); + return self::prepareTestopsReporter($logger, $config, $state, $framework, $reporterName, $hostData); } if ($mode === 'report') { @@ -52,9 +52,9 @@ private static function createInternalReporter(LoggerInterface $logger, QaseConf return null; } - private static function prepareTestopsReporter(LoggerInterface $logger, QaseConfig $config, StateInterface $state): InternalReporterInterface + private static function prepareTestopsReporter(LoggerInterface $logger, QaseConfig $config, StateInterface $state, string $framework = "", string $reporterName = "", array $hostData = []): InternalReporterInterface { - $client = new ApiClientV2($logger, $config->testops); + $client = new ApiClientV2($logger, $config->testops, $framework, $reporterName, $hostData); return new TestOpsReporter($client, $config, $state, $logger); } }