Prepare release engine

This commit is contained in:
Jan-Niclas Loosen
2026-11-04 14:46:54 +01:00
parent 8fe4eb63c8
commit b2759fba73
13 changed files with 159 additions and 390 deletions
+5 -104
View File
@@ -8,7 +8,6 @@ use Brain\Monkey\Filters;
use Brain\Monkey\Functions;
use LIT\MCP\Tests\Unit\TestCase;
use LIT\MCP\Updater\GiteaUpdater;
use LIT\MCP\Updater\PackageSignature;
use Mockery;
use WP_Error;
@@ -20,40 +19,26 @@ final class GiteaUpdaterTest extends TestCase
private string $logFile = '';
private string|false $previousLog = '';
private string $secretKey = '';
private string $publicKey = '';
/** @var list<string> */
private array $tempFiles = [];
protected function setUp(): void
{
parent::setUp();
Functions\stubTranslationFunctions();
// error_log() is a PHP internal and cannot be stubbed; redirect it to a temp file instead.
$this->logFile = $this->tempFile();
$this->logFile = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$this->previousLog = ini_set('error_log', $this->logFile);
$pair = sodium_crypto_sign_keypair();
$this->secretKey = sodium_crypto_sign_secretkey($pair);
$this->publicKey = base64_encode(sodium_crypto_sign_publickey($pair));
}
protected function tearDown(): void
{
ini_set('error_log', (string) $this->previousLog);
foreach ($this->tempFiles as $file) {
if (file_exists($file)) {
unlink($file);
}
}
unlink($this->logFile);
parent::tearDown();
}
public function testRegistersItsFilters(): void
public function testRegistersItselfForTheGiteaHost(): void
{
$updater = $this->updater();
Filters\expectAdded('update_plugins_' . GiteaUpdater::HOST)->once()->with([$updater, 'offer'], 10, 3);
Filters\expectAdded('upgrader_pre_download')->once()->with([$updater, 'verifyDownload'], 10, 2);
$updater->register();
}
@@ -84,8 +69,7 @@ final class GiteaUpdaterTest extends TestCase
Functions\expect('wp_remote_get')->never();
try {
$updater = new GiteaUpdater(self::BASENAME, $dir, new PackageSignature($this->publicKey));
self::assertFalse($updater->offer(false, [], self::BASENAME));
self::assertFalse((new GiteaUpdater(self::BASENAME, $dir))->offer(false, [], self::BASENAME));
} finally {
rmdir($dir . '/.git');
rmdir($dir);
@@ -136,81 +120,10 @@ final class GiteaUpdaterTest extends TestCase
];
}
public function testDownloadWithAValidSignatureIsHandedToWordPress(): void
{
$zip = $this->tempFile('release bytes');
$this->givenDownload($zip, $this->sign(self::ZIP_NAME, $zip));
self::assertSame($zip, $this->updater()->verifyDownload(false, self::ZIP));
self::assertFileExists($zip);
}
public function testDownloadWithAForeignSignatureIsDeletedAndRefused(): void
{
$zip = $this->tempFile('release bytes');
$other = $this->tempFile('other bytes');
$this->givenDownload($zip, $this->sign(self::ZIP_NAME, $other));
$result = $this->updater()->verifyDownload(false, self::ZIP);
self::assertInstanceOf(WP_Error::class, $result);
self::assertFileDoesNotExist($zip);
self::assertStringContainsString('update failed: signature of ' . self::ZIP_NAME, $this->log());
}
public function testSignatureOfAnotherVersionDoesNotPassAsThisOne(): void
{
$zip = $this->tempFile('old release bytes');
$this->givenDownload($zip, $this->sign('wp-lit-mcp-0.9.0.zip', $zip));
self::assertInstanceOf(WP_Error::class, $this->updater()->verifyDownload(false, self::ZIP));
}
public function testDownloadWithoutSignatureFileIsRefused(): void
{
$zip = $this->tempFile('release bytes');
$this->givenDownload($zip, '');
self::assertInstanceOf(WP_Error::class, $this->updater()->verifyDownload(false, self::ZIP));
self::assertFileDoesNotExist($zip);
}
public function testFailedDownloadIsReturnedAsIs(): void
{
$error = new WP_Error('download_failed', 'cURL error 28');
Functions\expect('download_url')->once()->with(self::ZIP)->andReturn($error);
self::assertSame($error, $this->updater()->verifyDownload(false, self::ZIP));
}
public function testOtherPackagesAndEarlierDecisionsAreNotTouched(): void
{
Functions\expect('download_url')->never();
self::assertFalse($this->updater()->verifyDownload(false, 'https://downloads.wordpress.org/plugin/x.zip'));
self::assertSame('/tmp/earlier.zip', $this->updater()->verifyDownload('/tmp/earlier.zip', self::ZIP));
}
private function updater(): GiteaUpdater
{
// __DIR__ has no .git folder.
return new GiteaUpdater(self::BASENAME, __DIR__, new PackageSignature($this->publicKey));
}
private function tempFile(string $contents = ''): string
{
$file = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-test');
file_put_contents($file, $contents);
$this->tempFiles[] = $file;
return $file;
}
private function sign(string $assetName, string $zipPath): string
{
$message = PackageSignature::message($assetName, $zipPath);
return base64_encode(sodium_crypto_sign_detached($message, $this->secretKey));
return new GiteaUpdater(self::BASENAME, __DIR__);
}
private function log(): string
@@ -218,18 +131,6 @@ final class GiteaUpdaterTest extends TestCase
return (string) file_get_contents($this->logFile);
}
private function givenDownload(string $zip, string $signatureBody): void
{
Functions\expect('download_url')->once()->with(self::ZIP)->andReturn($zip);
Functions\expect('wp_safe_remote_get')->once()
->with(self::ZIP . '.sig', Mockery::type('array'))
->andReturn(['body' => $signatureBody]);
Functions\when('wp_remote_retrieve_body')->justReturn($signatureBody);
Functions\when('wp_delete_file')->alias(static function (string $file): void {
unlink($file);
});
}
private function givenRelease(int $status, string $body): void
{
$latestRelease = Mockery::pattern('#^https://git\.loosen-it\.de/api/v1/repos/.+/releases/latest$#');