logFile = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log'); $this->previousLog = ini_set('error_log', $this->logFile); } protected function tearDown(): void { ini_set('error_log', (string) $this->previousLog); unlink($this->logFile); parent::tearDown(); } public function testRegistersItselfForTheGiteaHost(): void { $updater = $this->updater(); Filters\expectAdded('update_plugins_' . GiteaUpdater::HOST)->once()->with([$updater, 'offer'], 10, 3); $updater->register(); } public function testLatestReleaseIsOfferedWithItsPackage(): void { $this->givenRelease(200, self::releaseBody('v1.0.0', self::ZIP_NAME, self::ZIP)); $update = $this->updater()->offer(false, [], self::BASENAME); self::assertSame('1.0.0', $update['version']); self::assertSame(self::ZIP, $update['package']); self::assertSame('wp-lit-mcp', $update['slug']); self::assertSame('', $this->log()); } /** @dataProvider reportedAddresses */ public function testPackageAddressIsBuiltHereNotTakenFromTheAnswer(string $reported): void { $this->givenRelease(200, self::releaseBody('v1.0.0', self::ZIP_NAME, $reported)); $update = $this->updater()->offer(false, [], self::BASENAME); self::assertSame(self::ZIP, $update['package']); } /** @return array */ public static function reportedAddresses(): array { return [ 'http, as Gitea behind a TLS proxy reports it' => [ 'http://' . GiteaUpdater::HOST . '/janniclas.loosen/WP-LIT-MCP/releases/download/v1.0.0/' . self::ZIP_NAME, ], 'another host' => ['https://evil.example/' . self::ZIP_NAME], ]; } public function testOtherPluginsAreLeftAlone(): void { Functions\expect('wp_remote_get')->never(); self::assertFalse($this->updater()->offer(false, [], 'anderes/anderes.php')); } public function testGitCheckoutIsNeverOffered(): void { $dir = sys_get_temp_dir() . '/lit-mcp-checkout-' . uniqid(); mkdir($dir . '/.git', 0777, true); Functions\expect('wp_remote_get')->never(); try { self::assertFalse((new GiteaUpdater(self::BASENAME, $dir))->offer(false, [], self::BASENAME)); } finally { rmdir($dir . '/.git'); rmdir($dir); } } public function testUnreachableGiteaOffersNothingAndLogsWhy(): void { Functions\when('wp_remote_get')->justReturn(new WP_Error('http_request_failed', 'cURL error 28')); self::assertFalse($this->updater()->offer(false, [], self::BASENAME)); self::assertStringContainsString('update check failed: cURL error 28', $this->log()); } public function testHttpErrorOffersNothingAndLogsTheStatus(): void { $this->givenRelease(404, '{"message":"not found"}'); self::assertFalse($this->updater()->offer(false, [], self::BASENAME)); self::assertStringContainsString('update check failed: HTTP 404', $this->log()); } /** @dataProvider unusableReleases */ public function testUnusableReleaseOffersNothingAndLogsIt(string $body): void { $this->givenRelease(200, $body); self::assertFalse($this->updater()->offer(false, [], self::BASENAME)); self::assertStringContainsString('update check failed', $this->log()); } /** @return array */ public static function unusableReleases(): array { return [ 'no JSON' => [''], 'tag is no version' => [self::releaseBody('latest', self::ZIP_NAME, self::ZIP)], 'pre-release tag' => [self::releaseBody('v1.0.0-rc1', self::ZIP_NAME, self::ZIP)], 'tag with trailing newline' => [self::releaseBody("v1.0.0\n", self::ZIP_NAME, self::ZIP)], 'asset missing' => [self::releaseBody('v1.0.0', 'other.zip', self::ZIP)], 'release without assets' => ['{"tag_name":"v1.0.0"}'], 'asset of another version' => [self::releaseBody('v1.0.0', 'wp-lit-mcp-0.9.0.zip', self::ZIP)], ]; } private function updater(): GiteaUpdater { // __DIR__ has no .git folder. return new GiteaUpdater(self::BASENAME, __DIR__); } private function log(): string { return (string) file_get_contents($this->logFile); } private function givenRelease(int $status, string $body): void { $latestRelease = Mockery::pattern('#^https://git\.loosen-it\.de/api/v1/repos/.+/releases/latest$#'); Functions\expect('wp_remote_get')->once() ->with($latestRelease, Mockery::type('array')) ->andReturn(['body' => $body]); Functions\when('wp_remote_retrieve_response_code')->justReturn($status); Functions\when('wp_remote_retrieve_body')->justReturn($body); } private static function releaseBody(string $tag, string $assetName, string $url): string { // Data providers run before setUp(), so the wp_json_encode() stub does not exist yet. return (string) json_encode([ // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode 'tag_name' => $tag, 'assets' => [['name' => $assetName, 'browser_download_url' => $url]], ]); } }