diff --git a/includes/providers/class-gitea-provider.php b/includes/providers/class-gitea-provider.php index ad2e4f7..64d26b8 100644 --- a/includes/providers/class-gitea-provider.php +++ b/includes/providers/class-gitea-provider.php @@ -186,7 +186,13 @@ class Gitea_Provider implements Repository_Provider { return null; } - $release = $body[0]; + // Gitea's /releases/latest returns a single release OBJECT (associative array), + // not a list. Some endpoints / older Gitea versions may return a list; handle both. + $release = isset( $body['tag_name'] ) ? $body : ( $body[0] ?? null ); + if ( ! is_array( $release ) || empty( $release['tag_name'] ?? '' ) ) { + return null; + } + set_transient( $cache_key, $release, HOUR_IN_SECONDS ); return $release; } diff --git a/tests/test-gitea-provider.php b/tests/test-gitea-provider.php index e7c406c..e8b321d 100644 --- a/tests/test-gitea-provider.php +++ b/tests/test-gitea-provider.php @@ -136,6 +136,29 @@ class Gitea_Provider_Test extends \PHPUnit\Framework\TestCase { ); } + public function test_release_object_shape_picks_first_tag_from_object(): void { + // Gitea's /releases/latest returns a single release OBJECT (associative array), + // not a list. The implementation must accept either shape. + $body = json_encode( [ + 'tag_name' => 'v2.1.0', + 'name' => 'v2.1.0', + 'assets' => [ + [ 'name' => 'release.zip', 'browser_download_url' => 'https://example/release.zip' ], + ], + ] ); + \Brain\Monkey\Functions\expect( 'wp_remote_get' ) + ->twice() // get_release_url and get_latest_version both fetch + ->andReturn( [ + 'response' => [ 'code' => 200 ], + 'headers' => [], + 'body' => $body, + ] ); + + $provider = $this->make(); + $this->assertSame( 'https://example/release.zip', $provider->get_release_url() ); + $this->assertSame( 'v2.1.0', $provider->get_latest_version() ); + } + public function test_release_url_empty_array_returns_null(): void { \Brain\Monkey\Functions\expect( 'wp_remote_get' ) ->once()