From f0e660dcf72094ec15afb9a9e6946676d0fa6871 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Thu, 20 Nov 2025 12:25:00 +0100 Subject: [PATCH 01/14] Fix check for default branch on received webhook, allowing for a custom one --- inc/WebhookHandler/Base.php | 32 ++++++++++++++++++++++++++++++++ inc/WebhookHandler/Bitbucket.php | 12 ++++++++---- inc/WebhookHandler/GitHub.php | 18 ++++++++---------- inc/WebhookHandler/GitLab.php | 16 +++++++++------- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/inc/WebhookHandler/Base.php b/inc/WebhookHandler/Base.php index f59f339..01a8e61 100644 --- a/inc/WebhookHandler/Base.php +++ b/inc/WebhookHandler/Base.php @@ -8,8 +8,10 @@ namespace Required\Traduttore\WebhookHandler; use Required\Traduttore\Project; +use Required\Traduttore\ProjectLocator; use Required\Traduttore\WebhookHandler; use WP_REST_Request; +use WP_REST_Response; /** * Base webhook handler class. @@ -85,4 +87,34 @@ protected function get_secret( ?Project $project = null ): ?string { */ return apply_filters( 'traduttore.webhook_secret', $secret, $this, $project ); } + + /** + * Return a valid project or errors. Allows to customize the pulled branch. + * + * @param string $repository Metadata to find a GlotPress project. + * @param string $default_branch Name of the repository's default branch. + * @param string $ref Name of the received branch through the webhook. + * @return \Required\Traduttore\Project|\WP_REST_Response|\WP_Error + */ + protected function get_validated_project( string $repository, string $default_branch = '', string $ref = '' ): Project|WP_REST_Response|\WP_Error { + $locator = new ProjectLocator( $repository ); + $project = $locator->get_project(); + + if ( ! $project ) { + return new \WP_Error( '404', 'Could not find project for this repository', [ 'status' => 404 ] ); + } + + if ( empty( $default_branch ) || empty( $ref ) ) { + return $project; + } + + $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch ); + + // We only care about the default or custom branch but don't want to send an error still. + if ( $branch !== $ref ) { + return new WP_REST_Response( [ 'result' => 'Not the default or custom branch' ] ); + } + + return $project; + } } diff --git a/inc/WebhookHandler/Bitbucket.php b/inc/WebhookHandler/Bitbucket.php index 45011ec..f38eef0 100644 --- a/inc/WebhookHandler/Bitbucket.php +++ b/inc/WebhookHandler/Bitbucket.php @@ -7,6 +7,7 @@ namespace Required\Traduttore\WebhookHandler; +use Required\Traduttore\Project; use Required\Traduttore\ProjectLocator; use Required\Traduttore\Repository; use Required\Traduttore\Updater; @@ -80,11 +81,14 @@ public function callback(): \WP_Error|\WP_REST_Response { */ $params = $this->request->get_params(); - $locator = new ProjectLocator( $params['repository']['links']['html']['href'] ); - $project = $locator->get_project(); + if ( ! isset( $params['repository']['links']['html']['href'] ) ) { + return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); + } + + $project = $this->get_validated_project( $params['repository']['links']['html']['href'] ); - if ( ! $project ) { - return new \WP_Error( '404', 'Could not find project for this repository' ); + if ( ! $project instanceof Project ) { + return $project; } if ( ! $project->get_repository_vcs_type() ) { diff --git a/inc/WebhookHandler/GitHub.php b/inc/WebhookHandler/GitHub.php index 7df5dcc..6b1ea45 100644 --- a/inc/WebhookHandler/GitHub.php +++ b/inc/WebhookHandler/GitHub.php @@ -7,6 +7,7 @@ namespace Required\Traduttore\WebhookHandler; +use Required\Traduttore\Project; use Required\Traduttore\ProjectLocator; use Required\Traduttore\Repository; use Required\Traduttore\Updater; @@ -117,20 +118,17 @@ public function callback(): \WP_Error|\WP_REST_Response { * @var array{repository: array{ default_branch?: string, html_url: string, full_name: string, ssh_url: string, clone_url: string, private: bool }, ref: string } $params */ - if ( ! isset( $params['repository']['default_branch'] ) ) { + if ( ! isset( $params['repository']['default_branch'] ) + | ! isset( $params['repository']['html_url'] ) + | ! isset( $params['ref'] ) + ) { return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); } - // We only care about the default branch but don't want to send an error still. - if ( 'refs/heads/' . $params['repository']['default_branch'] !== $params['ref'] ) { - return new WP_REST_Response( [ 'result' => 'Not the default branch' ] ); - } - - $locator = new ProjectLocator( $params['repository']['html_url'] ); - $project = $locator->get_project(); + $project = $this->get_validated_project( $params['repository']['html_url'], $params['repository']['default_branch'], $params['ref'] ); - if ( ! $project ) { - return new \WP_Error( '404', 'Could not find project for this repository', [ 'status' => 404 ] ); + if ( ! $project instanceof Project ) { + return $project; } $project->set_repository_name( $params['repository']['full_name'] ); diff --git a/inc/WebhookHandler/GitLab.php b/inc/WebhookHandler/GitLab.php index 065d60c..f95c84b 100644 --- a/inc/WebhookHandler/GitLab.php +++ b/inc/WebhookHandler/GitLab.php @@ -7,6 +7,7 @@ namespace Required\Traduttore\WebhookHandler; +use Required\Traduttore\Project; use Required\Traduttore\ProjectLocator; use Required\Traduttore\Repository; use Required\Traduttore\Updater; @@ -79,16 +80,17 @@ public function callback(): \WP_Error|\WP_REST_Response { */ $params = $this->request->get_params(); - // We only care about the default branch but don't want to send an error still. - if ( 'refs/heads/' . $params['project']['default_branch'] !== $params['ref'] ) { - return new WP_REST_Response( [ 'result' => 'Not the default branch' ] ); + if ( ! isset( $params['project']['default_branch'] ) + | ! isset( $params['project']['homepage'] ) + | ! isset( $params['ref'] ) + ) { + return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); } - $locator = new ProjectLocator( $params['project']['homepage'] ); - $project = $locator->get_project(); + $project = $this->get_validated_project( $params['project']['homepage'], $params['project']['default_branch'], $params['ref'] ); - if ( ! $project ) { - return new \WP_Error( '404', 'Could not find project for this repository' ); + if ( ! $project instanceof Project ) { + return $project; } $project->set_repository_name( $params['project']['path_with_namespace'] ); From 0a2f2d972e12ac289f5287128e671a9c69f30be8 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Thu, 20 Nov 2025 14:20:34 +0100 Subject: [PATCH 02/14] Add local variables --- inc/WebhookHandler/Base.php | 2 +- inc/WebhookHandler/Bitbucket.php | 7 ++++--- inc/WebhookHandler/GitHub.php | 14 +++++++++----- inc/WebhookHandler/GitLab.php | 17 ++++++++++------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/inc/WebhookHandler/Base.php b/inc/WebhookHandler/Base.php index 01a8e61..b2e82ce 100644 --- a/inc/WebhookHandler/Base.php +++ b/inc/WebhookHandler/Base.php @@ -96,7 +96,7 @@ protected function get_secret( ?Project $project = null ): ?string { * @param string $ref Name of the received branch through the webhook. * @return \Required\Traduttore\Project|\WP_REST_Response|\WP_Error */ - protected function get_validated_project( string $repository, string $default_branch = '', string $ref = '' ): Project|WP_REST_Response|\WP_Error { + protected function resolve_project( string $repository, string $default_branch = '', string $ref = '' ): Project|WP_REST_Response|\WP_Error { $locator = new ProjectLocator( $repository ); $project = $locator->get_project(); diff --git a/inc/WebhookHandler/Bitbucket.php b/inc/WebhookHandler/Bitbucket.php index f38eef0..6cfec77 100644 --- a/inc/WebhookHandler/Bitbucket.php +++ b/inc/WebhookHandler/Bitbucket.php @@ -80,12 +80,13 @@ public function callback(): \WP_Error|\WP_REST_Response { * @var array{repository: array{scm: string, full_name: string, links: array{html: array{href: string}}, is_private: bool}} $params */ $params = $this->request->get_params(); + $href = (string) $params['repository']['links']['html']['href']; - if ( ! isset( $params['repository']['links']['html']['href'] ) ) { + if ( '' === $href ) { return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); } - $project = $this->get_validated_project( $params['repository']['links']['html']['href'] ); + $project = $this->resolve_project( $href ); if ( ! $project instanceof Project ) { return $project; @@ -96,7 +97,7 @@ public function callback(): \WP_Error|\WP_REST_Response { } $project->set_repository_name( $params['repository']['full_name'] ); - $project->set_repository_url( $params['repository']['links']['html']['href'] ); + $project->set_repository_url( $href ); $ssh_url = sprintf( 'git@bitbucket.org:%s.git', $project->get_repository_name() ); $https_url = sprintf( 'https://bitbucket.org/%s.git', $project->get_repository_name() ); diff --git a/inc/WebhookHandler/GitHub.php b/inc/WebhookHandler/GitHub.php index 6b1ea45..ceae8a4 100644 --- a/inc/WebhookHandler/GitHub.php +++ b/inc/WebhookHandler/GitHub.php @@ -118,21 +118,25 @@ public function callback(): \WP_Error|\WP_REST_Response { * @var array{repository: array{ default_branch?: string, html_url: string, full_name: string, ssh_url: string, clone_url: string, private: bool }, ref: string } $params */ - if ( ! isset( $params['repository']['default_branch'] ) - | ! isset( $params['repository']['html_url'] ) - | ! isset( $params['ref'] ) + $default_branch = (string) $params['repository']['default_branch'] ?? ''; + $html_url = (string) $params['repository']['html_url']; + $ref = (string) $params['ref']; + + if ( '' === $default_branch + | '' === $html_url + | '' === $ref ) { return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); } - $project = $this->get_validated_project( $params['repository']['html_url'], $params['repository']['default_branch'], $params['ref'] ); + $project = $this->resolve_project( $html_url, $default_branch, $ref ); if ( ! $project instanceof Project ) { return $project; } $project->set_repository_name( $params['repository']['full_name'] ); - $project->set_repository_url( $params['repository']['html_url'] ); + $project->set_repository_url( $html_url ); $project->set_repository_ssh_url( $params['repository']['ssh_url'] ); $project->set_repository_https_url( $params['repository']['clone_url'] ); $project->set_repository_visibility( false === $params['repository']['private'] ? 'public' : 'private' ); diff --git a/inc/WebhookHandler/GitLab.php b/inc/WebhookHandler/GitLab.php index f95c84b..1b12846 100644 --- a/inc/WebhookHandler/GitLab.php +++ b/inc/WebhookHandler/GitLab.php @@ -78,23 +78,26 @@ public function callback(): \WP_Error|\WP_REST_Response { * * @var array{project: array{default_branch: string, homepage: string, path_with_namespace: string, ssh_url: string, http_url: string, visibility_level: int}, ref: string} $params */ - $params = $this->request->get_params(); - - if ( ! isset( $params['project']['default_branch'] ) - | ! isset( $params['project']['homepage'] ) - | ! isset( $params['ref'] ) + $params = $this->request->get_params(); + $default_branch = (string) $params['project']['default_branch']; + $homepage = (string) $params['project']['homepage']; + $ref = (string) $params['ref']; + + if ( '' === $default_branch + | '' === $homepage + | '' === $ref ) { return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); } - $project = $this->get_validated_project( $params['project']['homepage'], $params['project']['default_branch'], $params['ref'] ); + $project = $this->resolve_project( $homepage, $default_branch, $ref ); if ( ! $project instanceof Project ) { return $project; } $project->set_repository_name( $params['project']['path_with_namespace'] ); - $project->set_repository_url( $params['project']['homepage'] ); + $project->set_repository_url( $homepage ); $project->set_repository_ssh_url( $params['project']['ssh_url'] ); $project->set_repository_https_url( $params['project']['http_url'] ); $project->set_repository_visibility( 20 === $params['project']['visibility_level'] ? 'public' : 'private' ); From 2b2b9ea7021b36280b02e9a104cb02d8c0511e08 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 21 Nov 2025 10:53:26 +0100 Subject: [PATCH 03/14] Add project to the filter --- inc/WebhookHandler/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/WebhookHandler/Base.php b/inc/WebhookHandler/Base.php index b2e82ce..4ddafea 100644 --- a/inc/WebhookHandler/Base.php +++ b/inc/WebhookHandler/Base.php @@ -108,7 +108,7 @@ protected function resolve_project( string $repository, string $default_branch = return $project; } - $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch ); + $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch, $project ); // We only care about the default or custom branch but don't want to send an error still. if ( $branch !== $ref ) { From b145ad1ebf12a2fe38edc5ee7eaff2d3467cea9f Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 21 Nov 2025 12:02:03 +0100 Subject: [PATCH 04/14] Add project name to the filter --- inc/Loader/Git.php | 2 +- inc/WebhookHandler/Base.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/Loader/Git.php b/inc/Loader/Git.php index 796fadf..e085bb7 100644 --- a/inc/Loader/Git.php +++ b/inc/Loader/Git.php @@ -52,7 +52,7 @@ public function download(): ?string { * * @param string $branch Name of the Git branch to clone. Empty string clones the default branch. */ - $branch = apply_filters( 'traduttore.git_clone_branch', '' ); + $branch = apply_filters( 'traduttore.git_clone_branch', '', $this->repository->get_name() ); if ( '' !== $branch ) { $cmd .= ' --branch ' . escapeshellarg( $branch ); } diff --git a/inc/WebhookHandler/Base.php b/inc/WebhookHandler/Base.php index 4ddafea..9d103bb 100644 --- a/inc/WebhookHandler/Base.php +++ b/inc/WebhookHandler/Base.php @@ -108,7 +108,7 @@ protected function resolve_project( string $repository, string $default_branch = return $project; } - $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch, $project ); + $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch, $project->get_name() ); // We only care about the default or custom branch but don't want to send an error still. if ( $branch !== $ref ) { From fb24c1130489efc65f0541924b19ae75296b73cb Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 21 Nov 2025 12:07:02 +0100 Subject: [PATCH 05/14] Add documentation --- inc/Loader/Git.php | 1 + 1 file changed, 1 insertion(+) diff --git a/inc/Loader/Git.php b/inc/Loader/Git.php index e085bb7..87ad27a 100644 --- a/inc/Loader/Git.php +++ b/inc/Loader/Git.php @@ -51,6 +51,7 @@ public function download(): ?string { * @since 4.0.0 * * @param string $branch Name of the Git branch to clone. Empty string clones the default branch. + * @param string|null $repository Name of the repository. Can be used to resolve the project. */ $branch = apply_filters( 'traduttore.git_clone_branch', '', $this->repository->get_name() ); if ( '' !== $branch ) { From 48cdc6408dd1147e88591bbda4cacbc9a20901e2 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 21 Nov 2025 12:08:36 +0100 Subject: [PATCH 06/14] Change to repository name --- inc/WebhookHandler/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/WebhookHandler/Base.php b/inc/WebhookHandler/Base.php index 9d103bb..2a79c15 100644 --- a/inc/WebhookHandler/Base.php +++ b/inc/WebhookHandler/Base.php @@ -108,7 +108,7 @@ protected function resolve_project( string $repository, string $default_branch = return $project; } - $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch, $project->get_name() ); + $branch = 'refs/heads/' . (string) apply_filters( 'traduttore.git_clone_branch', $default_branch, $project->get_repository_name() ); // We only care about the default or custom branch but don't want to send an error still. if ( $branch !== $ref ) { From 79369fb2547769068771212fb8fcbfba0d15b8f4 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 21 Nov 2025 12:20:37 +0100 Subject: [PATCH 07/14] Fix QA check --- inc/WebhookHandler/GitHub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/WebhookHandler/GitHub.php b/inc/WebhookHandler/GitHub.php index ceae8a4..4b8efcf 100644 --- a/inc/WebhookHandler/GitHub.php +++ b/inc/WebhookHandler/GitHub.php @@ -118,7 +118,7 @@ public function callback(): \WP_Error|\WP_REST_Response { * @var array{repository: array{ default_branch?: string, html_url: string, full_name: string, ssh_url: string, clone_url: string, private: bool }, ref: string } $params */ - $default_branch = (string) $params['repository']['default_branch'] ?? ''; + $default_branch = (string) $params['repository']['default_branch'] ?: ''; $html_url = (string) $params['repository']['html_url']; $ref = (string) $params['ref']; From ed0a5cd8bde448e8d5784900f7c6e1771b2afe17 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 12 Dec 2025 11:12:54 +0100 Subject: [PATCH 08/14] Fix QA checks --- inc/WebhookHandler/GitHub.php | 2 +- tests/phpunit/tests/WebhookHandler/GitHub.php | 2 +- tests/phpunit/tests/WebhookHandler/GitLab.php | 2 +- tests/phpunit/tests/WebhookHandler/LegacyGitHub.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/inc/WebhookHandler/GitHub.php b/inc/WebhookHandler/GitHub.php index 4b8efcf..bd7f1d6 100644 --- a/inc/WebhookHandler/GitHub.php +++ b/inc/WebhookHandler/GitHub.php @@ -118,7 +118,7 @@ public function callback(): \WP_Error|\WP_REST_Response { * @var array{repository: array{ default_branch?: string, html_url: string, full_name: string, ssh_url: string, clone_url: string, private: bool }, ref: string } $params */ - $default_branch = (string) $params['repository']['default_branch'] ?: ''; + $default_branch = (string) ( $params['repository']['default_branch'] ?? '' ); $html_url = (string) $params['repository']['html_url']; $ref = (string) $params['ref']; diff --git a/tests/phpunit/tests/WebhookHandler/GitHub.php b/tests/phpunit/tests/WebhookHandler/GitHub.php index f7dc835..551864a 100644 --- a/tests/phpunit/tests/WebhookHandler/GitHub.php +++ b/tests/phpunit/tests/WebhookHandler/GitHub.php @@ -96,7 +96,7 @@ public function test_invalid_branch(): void { $response = rest_get_server()->dispatch( $request ); $this->assertSame( 200, $response->get_status() ); - $this->assertSame( [ 'result' => 'Not the default branch' ], $response->get_data() ); + $this->assertSame( [ 'result' => 'Not the default or custom branch' ], $response->get_data() ); } public function test_invalid_project(): void { diff --git a/tests/phpunit/tests/WebhookHandler/GitLab.php b/tests/phpunit/tests/WebhookHandler/GitLab.php index 19597df..99229a8 100644 --- a/tests/phpunit/tests/WebhookHandler/GitLab.php +++ b/tests/phpunit/tests/WebhookHandler/GitLab.php @@ -81,7 +81,7 @@ public function test_invalid_branch(): void { $response = rest_get_server()->dispatch( $request ); $this->assertSame( 200, $response->get_status() ); - $this->assertSame( [ 'result' => 'Not the default branch' ], $response->get_data() ); + $this->assertSame( [ 'result' => 'Not the default or custom branch' ], $response->get_data() ); } public function test_invalid_project(): void { diff --git a/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php b/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php index f18d72d..922021a 100644 --- a/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php +++ b/tests/phpunit/tests/WebhookHandler/LegacyGitHub.php @@ -93,7 +93,7 @@ public function test_invalid_branch(): void { $response = rest_get_server()->dispatch( $request ); $this->assertSame( 200, $response->get_status() ); - $this->assertSame( [ 'result' => 'Not the default branch' ], $response->get_data() ); + $this->assertSame( [ 'result' => 'Not the default or custom branch' ], $response->get_data() ); } public function test_invalid_project(): void { From 5949cdba7c8fc8fadd010c097313b6eb4142f26d Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 12 Dec 2025 11:33:04 +0100 Subject: [PATCH 09/14] Add tests for more coverage --- .../tests/WebhookHandler/Bitbucket.php | 28 +++++++++++++++++++ tests/phpunit/tests/WebhookHandler/GitLab.php | 22 +++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/tests/phpunit/tests/WebhookHandler/Bitbucket.php b/tests/phpunit/tests/WebhookHandler/Bitbucket.php index f6a284f..bbc86a0 100644 --- a/tests/phpunit/tests/WebhookHandler/Bitbucket.php +++ b/tests/phpunit/tests/WebhookHandler/Bitbucket.php @@ -110,6 +110,34 @@ public function test_invalid_project(): void { $this->assertErrorResponse( 404, $response ); } + public function test_request_incomplete(): void { + $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + (string) wp_json_encode( + [ + 'ref' => 'refs/heads/master', + 'repository' => [ + 'links' => [ + 'html' => [ + 'href' => '', + ], + ], + 'full_name' => 'wearerequired/traduttore', + 'scm' => 'git', + 'is_private' => false, + ], + ] + ) + ); + $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); + $request->add_header( 'x-event-key', 'repo:push' ); + $request->add_header( 'x-hub-signature-256', $signature ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 400, $response ); + } + public function test_valid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); $request->add_header( 'Content-Type', 'application/json' ); diff --git a/tests/phpunit/tests/WebhookHandler/GitLab.php b/tests/phpunit/tests/WebhookHandler/GitLab.php index 99229a8..8007b83 100644 --- a/tests/phpunit/tests/WebhookHandler/GitLab.php +++ b/tests/phpunit/tests/WebhookHandler/GitLab.php @@ -106,6 +106,28 @@ public function test_invalid_project(): void { $this->assertErrorResponse( 404, $response ); } + public function test_request_incomplete(): void { + $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); + $request->set_body_params( + [ + 'ref' => '', + 'project' => [ + 'default_branch' => 'master', + 'path_with_namespace' => 'wearerequired/traduttore', + 'homepage' => 'https://gitlab.com/wearerequired/traduttore', + 'http_url' => 'https://gitlab.com/wearerequired/traduttore.git', + 'ssh_url' => 'git@gitlab.com/wearerequired/traduttore.git', + 'visibility_level' => 20, + ], + ] + ); + $request->add_header( 'x-gitlab-event', 'Push Hook' ); + $request->add_header( 'x-gitlab-token', 'traduttore-test' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 400, $response ); + } + public function test_valid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); $request->set_body_params( From 6f2ff80fa637fef6382c0bab1d9be86b0ffcd903 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 12 Dec 2025 12:31:13 +0100 Subject: [PATCH 10/14] Fix test --- tests/phpunit/tests/WebhookHandler/Bitbucket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/WebhookHandler/Bitbucket.php b/tests/phpunit/tests/WebhookHandler/Bitbucket.php index bbc86a0..d15e1e5 100644 --- a/tests/phpunit/tests/WebhookHandler/Bitbucket.php +++ b/tests/phpunit/tests/WebhookHandler/Bitbucket.php @@ -135,7 +135,7 @@ public function test_request_incomplete(): void { $request->add_header( 'x-hub-signature-256', $signature ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 400, $response ); + $this->assertErrorResponse( 'rest_forbidden', $response, 400 ); } public function test_valid_project(): void { From d80467cbdd0de4cd2dca005ef067bb22bd88a6ac Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Mon, 15 Dec 2025 10:53:22 +0100 Subject: [PATCH 11/14] Fix test and remove the check of href presence from the callback because it is already checked in the permission callback --- inc/WebhookHandler/Bitbucket.php | 4 --- .../tests/WebhookHandler/Bitbucket.php | 28 ------------------- 2 files changed, 32 deletions(-) diff --git a/inc/WebhookHandler/Bitbucket.php b/inc/WebhookHandler/Bitbucket.php index 6cfec77..d45573b 100644 --- a/inc/WebhookHandler/Bitbucket.php +++ b/inc/WebhookHandler/Bitbucket.php @@ -82,10 +82,6 @@ public function callback(): \WP_Error|\WP_REST_Response { $params = $this->request->get_params(); $href = (string) $params['repository']['links']['html']['href']; - if ( '' === $href ) { - return new \WP_Error( '400', 'Request incomplete', [ 'status' => 400 ] ); - } - $project = $this->resolve_project( $href ); if ( ! $project instanceof Project ) { diff --git a/tests/phpunit/tests/WebhookHandler/Bitbucket.php b/tests/phpunit/tests/WebhookHandler/Bitbucket.php index d15e1e5..f6a284f 100644 --- a/tests/phpunit/tests/WebhookHandler/Bitbucket.php +++ b/tests/phpunit/tests/WebhookHandler/Bitbucket.php @@ -110,34 +110,6 @@ public function test_invalid_project(): void { $this->assertErrorResponse( 404, $response ); } - public function test_request_incomplete(): void { - $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); - $request->add_header( 'Content-Type', 'application/json' ); - $request->set_body( - (string) wp_json_encode( - [ - 'ref' => 'refs/heads/master', - 'repository' => [ - 'links' => [ - 'html' => [ - 'href' => '', - ], - ], - 'full_name' => 'wearerequired/traduttore', - 'scm' => 'git', - 'is_private' => false, - ], - ] - ) - ); - $signature = 'sha256=' . hash_hmac( 'sha256', $request->get_body(), 'traduttore-test' ); - $request->add_header( 'x-event-key', 'repo:push' ); - $request->add_header( 'x-hub-signature-256', $signature ); - $response = rest_get_server()->dispatch( $request ); - - $this->assertErrorResponse( 'rest_forbidden', $response, 400 ); - } - public function test_valid_project(): void { $request = new WP_REST_Request( 'POST', '/traduttore/v1/incoming-webhook' ); $request->add_header( 'Content-Type', 'application/json' ); From 4eb57cd52fe11a5d3495a28c846e169246ba8f50 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 16 Jan 2026 12:05:56 +0100 Subject: [PATCH 12/14] Pass the full repository instance to the filter instead of only the name --- inc/Loader/Git.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inc/Loader/Git.php b/inc/Loader/Git.php index 87ad27a..afa816a 100644 --- a/inc/Loader/Git.php +++ b/inc/Loader/Git.php @@ -51,9 +51,9 @@ public function download(): ?string { * @since 4.0.0 * * @param string $branch Name of the Git branch to clone. Empty string clones the default branch. - * @param string|null $repository Name of the repository. Can be used to resolve the project. + * @param \Required\Traduttore\Repository $repository Full repository instance. */ - $branch = apply_filters( 'traduttore.git_clone_branch', '', $this->repository->get_name() ); + $branch = apply_filters( 'traduttore.git_clone_branch', '', $this->repository ); if ( '' !== $branch ) { $cmd .= ' --branch ' . escapeshellarg( $branch ); } @@ -82,7 +82,7 @@ protected function get_clone_url(): string { * @param bool $use_https Whether to use HTTPS instead of SSH for * cloning repositories. * Defaults to true for public repositories. - * @param \Required\Traduttore\Repository $repository The current repository. + * @param Repository $repository The current repository. */ $use_https = apply_filters( 'traduttore.git_clone_use_https', $this->repository->is_public(), $this->repository ); @@ -98,7 +98,7 @@ protected function get_clone_url(): string { * @since 3.0.0 * * @param string $clone_url The URL to clone a Git repository. - * @param \Required\Traduttore\Repository $repository The current repository. + * @param Repository $repository The current repository. */ return apply_filters( 'traduttore.git_clone_url', (string) $clone_url, $this->repository ); } From deb2d4d236bfed9518ad020031f0a0e13f11baf9 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 16 Jan 2026 12:34:01 +0100 Subject: [PATCH 13/14] Fix code sniffer error --- inc/Loader/Git.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inc/Loader/Git.php b/inc/Loader/Git.php index afa816a..12a4773 100644 --- a/inc/Loader/Git.php +++ b/inc/Loader/Git.php @@ -79,10 +79,10 @@ protected function get_clone_url(): string { * * @since 3.0.0 * - * @param bool $use_https Whether to use HTTPS instead of SSH for - * cloning repositories. - * Defaults to true for public repositories. - * @param Repository $repository The current repository. + * @param bool $use_https Whether to use HTTPS instead of SSH for + * cloning repositories. + * Defaults to true for public repositories. + * @param \Required\Traduttore\Loader\Repository $repository The current repository. */ $use_https = apply_filters( 'traduttore.git_clone_use_https', $this->repository->is_public(), $this->repository ); @@ -97,8 +97,8 @@ protected function get_clone_url(): string { * * @since 3.0.0 * - * @param string $clone_url The URL to clone a Git repository. - * @param Repository $repository The current repository. + * @param string $clone_url The URL to clone a Git repository. + * @param \Required\Traduttore\Loader\Repository $repository The current repository. */ return apply_filters( 'traduttore.git_clone_url', (string) $clone_url, $this->repository ); } From 5e39df7b29b5d354dbda0148db3c10f9b5964b42 Mon Sep 17 00:00:00 2001 From: Erika Gili Date: Fri, 16 Jan 2026 14:03:55 +0100 Subject: [PATCH 14/14] Fix for static analysis --- inc/Loader/Git.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inc/Loader/Git.php b/inc/Loader/Git.php index 12a4773..cb90316 100644 --- a/inc/Loader/Git.php +++ b/inc/Loader/Git.php @@ -79,10 +79,10 @@ protected function get_clone_url(): string { * * @since 3.0.0 * - * @param bool $use_https Whether to use HTTPS instead of SSH for - * cloning repositories. - * Defaults to true for public repositories. - * @param \Required\Traduttore\Loader\Repository $repository The current repository. + * @param bool $use_https Whether to use HTTPS instead of SSH for + * cloning repositories. + * Defaults to true for public repositories. + * @param \Required\Traduttore\Repository $repository The current repository. */ $use_https = apply_filters( 'traduttore.git_clone_use_https', $this->repository->is_public(), $this->repository ); @@ -97,8 +97,8 @@ protected function get_clone_url(): string { * * @since 3.0.0 * - * @param string $clone_url The URL to clone a Git repository. - * @param \Required\Traduttore\Loader\Repository $repository The current repository. + * @param string $clone_url The URL to clone a Git repository. + * @param \Required\Traduttore\Repository $repository The current repository. */ return apply_filters( 'traduttore.git_clone_url', (string) $clone_url, $this->repository ); }