[web-downloads] main: Process Winlibs deletions from the existing build queue

From: Date: Fri, 02 Oct 2026 16:29:56 +0000
Subject: [web-downloads] main: Process Winlibs deletions from the existing build queue
Groups: php.webmaster 
Request: Send a blank email to php-webmaster+get-34065@lists.php.net to get a copy of this message
Author: Shivam Mathur (shivammathur)
Date: 2026-10-02T21:49:18+05:30

Commit: https://github.com/php/web-downloads/commit/92114628ae76c128da0d938e934424f490c1f385
Raw diff: https://github.com/php/web-downloads/commit/92114628ae76c128da0d938e934424f490c1f385.diff

Process Winlibs deletions from the existing build queue

Changed paths:
  M  API.md
  M  src/Actions/DeleteWinlibsBuild.php
  M  src/Console/Command/WinlibsCommand.php
  M  src/Http/Controllers/WinlibsDeleteController.php
  M  tests/Console/Command/WinlibsCommandDeleteTest.php
  M  tests/Http/Controllers/WinlibsDeleteControllerTest.php


Diff:

diff --git a/API.md b/API.md
index 8ad1453..779e438 100644
--- a/API.md
+++ b/API.md
@@ -190,13 +190,14 @@ curl -i -X POST \
 ### POST /api/winlibs-delete
 
 - Auth: Required
-- Purpose: Queue deletion of one published Winlibs ZIP and its package references. The existing
winlibs:add runner processes deletion jobs after uploads. winlibs:add
--delete processes only deletion jobs when run manually.
+- Purpose: Queue deletion of one published Winlibs ZIP and its package references. The API writes a
winlibs/delete-*/data.json job into the existing Winlibs queue. The existing
winlibs:add cron processes uploads first and deletion jobs afterward. winlibs:add
--delete processes only deletion jobs when run manually.
 - Request body (JSON):
     - type (string, required): php or pecl.
     - filename (string, required): Exact ZIP basename, such as
libcurl-8.22.0-1-vs18-x64.zip. Paths and non-ZIP names are rejected.
 - For php, the processor removes exact matching lines from every
php-sdk/deps/series/packages-*.txt file and deletes every copy of that filename under
php-sdk/deps/<VS>/<arch>/. This covers builds copied to multiple VS
targets. An empty series file is removed.
 - For pecl, the processor removes the exact line from
pecl/deps/packages.txt and deletes pecl/deps/<filename>. It leaves
every unrelated index entry unchanged.
 - Success: 200 OK, empty body, meaning the deletion was queued. Repeating a deletion
is safe.
+- The next valid request also moves jobs previously queued under winlibs-delete/ into
the Winlibs queue, so older pending requests can be recovered.
 - Errors:
     - 400 with validation details if the payload is invalid.
     - 500 if BUILDS_DIRECTORY is not configured or the job cannot be
queued.
diff --git a/src/Actions/DeleteWinlibsBuild.php b/src/Actions/DeleteWinlibsBuild.php
index 81b62e3..1cb0e6a 100644
--- a/src/Actions/DeleteWinlibsBuild.php
+++ b/src/Actions/DeleteWinlibsBuild.php
@@ -8,62 +8,21 @@
 class DeleteWinlibsBuild
 {
     public function __construct(
-        private readonly string $baseDirectory,
-        private readonly string $buildsDirectory
+        private readonly string $baseDirectory
     ) {
     }
 
-    public function handle(): void
+    public function handle(string $type, string $filename): void
     {
-        $queueDirectory = rtrim($this->buildsDirectory, '/') .
'/winlibs-delete';
-        if (!is_dir($queueDirectory)) {
-            return;
-        }
-
-        $tasks = glob($queueDirectory . '/winlibs-delete-*.json');
-        if ($tasks === false) {
-            throw new Exception("Unable to list Winlibs deletion queue:
$queueDirectory");
+        if (!in_array($type, ['php', 'pecl'], true)
+            || preg_match('/\A[A-Za-z0-9][A-Za-z0-9._-]*\.zip\z/', $filename) !== 1) {
+            throw new Exception('Invalid Winlibs deletion job');
         }
-        foreach ($tasks as $taskFile) {
-            $lockPath = $taskFile . '.lock';
-            $lock = fopen($lockPath, 'c');
-            if ($lock === false) {
-                throw new Exception("Unable to open lock file: $lockPath");
-            }
-
-            try {
-                if (!flock($lock, LOCK_EX | LOCK_NB)) {
-                    continue;
-                }
-                if (!is_file($taskFile)) {
-                    continue;
-                }
 
-                $data = json_decode((string) file_get_contents($taskFile), true, 512,
JSON_THROW_ON_ERROR);
-                $type = $data['type'] ?? null;
-                $filename = $data['filename'] ?? null;
-                if (!in_array($type, ['php', 'pecl'], true)
-                    || !is_string($filename)
-                    || preg_match('/\A[A-Za-z0-9][A-Za-z0-9._-]*\.zip\z/', $filename) !==
1) {
-                    throw new Exception("Invalid Winlibs deletion task: $taskFile");
-                }
-
-                if ($type === 'php') {
-                    $this->deletePhpBuild(rtrim($this->baseDirectory, '/'),
$filename);
-                } else {
-                    $this->deletePeclBuild(rtrim($this->baseDirectory, '/'),
$filename);
-                }
-
-                if (!unlink($taskFile)) {
-                    throw new Exception("Unable to remove task: $taskFile");
-                }
-            } finally {
-                flock($lock, LOCK_UN);
-                fclose($lock);
-                if (!is_file($taskFile)) {
-                    @unlink($lockPath);
-                }
-            }
+        if ($type === 'php') {
+            $this->deletePhpBuild(rtrim($this->baseDirectory, '/'), $filename);
+        } else {
+            $this->deletePeclBuild(rtrim($this->baseDirectory, '/'), $filename);
         }
     }
 
diff --git a/src/Console/Command/WinlibsCommand.php b/src/Console/Command/WinlibsCommand.php
index bfe6b17..0d9b2c9 100644
--- a/src/Console/Command/WinlibsCommand.php
+++ b/src/Console/Command/WinlibsCommand.php
@@ -8,6 +8,7 @@
 use App\Helpers\Helpers;
 use Exception;
 use JsonException;
+use Throwable;
 
 class WinlibsCommand extends Command
 {
@@ -29,60 +30,103 @@ public function handle(): int
                 throw new Exception('Build directory is required');
             }
 
-            $deletions = new DeleteWinlibsBuild($this->baseDirectory, $buildsDirectory);
-            if (($this->options['delete'] ?? false) === true) {
-                $deletions->handle();
-                return Command::SUCCESS;
-            }
-
             $buildDirectories = glob($buildsDirectory . '/winlibs/*', GLOB_ONLYDIR);
+            if ($buildDirectories === false) {
+                throw new Exception('Unable to list Winlibs jobs');
+            }
 
-            // We lock the Directories we are working on
-            // so that we don't process them again if the command is run again
-            $filteredDirectories = [];
+            $uploadDirectories = [];
+            $deleteDirectories = [];
             foreach ($buildDirectories as $directoryPath) {
-                $lockFile = $directoryPath . '.lock';
-                if (!file_exists($lockFile)) {
-                    touch($lockFile);
-                    $filteredDirectories[] = $directoryPath;
+                if (str_starts_with(basename($directoryPath), 'delete-')) {
+                    $deleteDirectories[] = $directoryPath;
+                } else {
+                    $uploadDirectories[] = $directoryPath;
                 }
             }
+            $deleteOnly = ($this->options['delete'] ?? false) === true;
+            $jobs = $deleteOnly ? $deleteDirectories : array_merge($uploadDirectories,
$deleteDirectories);
+            $deletions = new DeleteWinlibsBuild($this->baseDirectory);
+            $errors = [];
 
-            foreach ($filteredDirectories as $directoryPath) {
-                $data = json_decode(file_get_contents($directoryPath . '/data.json'),
true, 512, JSON_THROW_ON_ERROR);
-                $files = glob($directoryPath . '/*.zip');
-                $files = $this->parseFiles($files);
-                if (empty($files)) {
-                    throw new Exception('No valid files found in ' .
basename($directoryPath));
-                }
-                if($data['type'] === 'php') {
-                    $this->copyPhpFiles($files, $data['library'],
$data['vs_version_targets']);
-                    $updateSeries = $data['update_series'] ?? 'true';
-                    if ($updateSeries === 'true') {
-                        $this->updatePhpSeriesFiles(
-                            $files,
-                            $data['library'],
-                            $data['php_versions'],
-                            $data['vs_version_targets'],
-                            $data['stability']
-                        );
+            foreach ($jobs as $directoryPath) {
+                $lockFile = $directoryPath . '.lock';
+                $isDeleteDirectory = str_starts_with(basename($directoryPath),
'delete-');
+                $deleteLock = null;
+                if ($isDeleteDirectory) {
+                    $deleteLock = fopen($lockFile, 'c');
+                    if ($deleteLock === false) {
+                        throw new Exception('Unable to lock Winlibs job: ' .
basename($directoryPath));
+                    }
+                    if (!flock($deleteLock, LOCK_EX | LOCK_NB)) {
+                        fclose($deleteLock);
+                        continue;
                     }
                 } else {
-                    $this->copyPeclFiles($files, $data['library']);
-                    $this->updatePackagesFile();
+                    if (file_exists($lockFile)) {
+                        continue;
+                    }
+                    if (!touch($lockFile)) {
+                        throw new Exception('Unable to lock Winlibs job: ' .
basename($directoryPath));
+                    }
                 }
 
-                Helpers::rmdirr($directoryPath);
+                try {
+                    $data = json_decode((string) file_get_contents($directoryPath .
'/data.json'), true, 512, JSON_THROW_ON_ERROR);
+                    $isDeleteJob = ($data['delete'] ?? false) === true;
+                    if ($isDeleteJob) {
+                        if (!str_starts_with(basename($directoryPath), 'delete-')
+                            || !is_string($data['type'] ?? null)
+                            || !is_string($data['filename'] ?? null)) {
+                            throw new Exception('Invalid Winlibs deletion job: ' .
basename($directoryPath));
+                        }
+                        $deletions->handle($data['type'],
$data['filename']);
+                    } else {
+                        if (str_starts_with(basename($directoryPath), 'delete-')) {
+                            throw new Exception('Invalid Winlibs deletion job: ' .
basename($directoryPath));
+                        }
+                        $files = glob($directoryPath . '/*.zip');
+                        $files = $this->parseFiles($files);
+                        if (empty($files)) {
+                            throw new Exception('No valid files found in ' .
basename($directoryPath));
+                        }
+                        if ($data['type'] === 'php') {
+                            $this->copyPhpFiles($files, $data['library'],
$data['vs_version_targets']);
+                            $updateSeries = $data['update_series'] ?? 'true';
+                            if ($updateSeries === 'true') {
+                                $this->updatePhpSeriesFiles(
+                                    $files,
+                                    $data['library'],
+                                    $data['php_versions'],
+                                    $data['vs_version_targets'],
+                                    $data['stability']
+                                );
+                            }
+                        } else {
+                            $this->copyPeclFiles($files, $data['library']);
+                            $this->updatePackagesFile();
+                        }
+                    }
 
-                unlink($directoryPath . '.lock');
+                    if (!Helpers::rmdirr($directoryPath)) {
+                        throw new Exception('Unable to remove Winlibs job: ' .
basename($directoryPath));
+                    }
+                    unlink($lockFile);
+                } catch (Throwable $error) {
+                    $errors[] = $error->getMessage();
+                } finally {
+                    if ($deleteLock !== null) {
+                        flock($deleteLock, LOCK_UN);
+                        fclose($deleteLock);
+                    }
+                }
             }
 
-            // Process deletions after uploads so a queued upload cannot restore
-            // a build that was also queued for deletion.
-            $deletions->handle();
-
+            if ($errors !== []) {
+                throw new Exception(implode("\n", $errors));
+            }
             return Command::SUCCESS;
-        } catch (Exception $e) {
+        } catch (Throwable $e) {
             echo $e->getMessage();
             return Command::FAILURE;
         }
diff --git a/src/Http/Controllers/WinlibsDeleteController.php
b/src/Http/Controllers/WinlibsDeleteController.php
index c72d84a..e41533e 100644
--- a/src/Http/Controllers/WinlibsDeleteController.php
+++ b/src/Http/Controllers/WinlibsDeleteController.php
@@ -3,8 +3,10 @@
 
 namespace App\Http\Controllers;
 
+use App\Helpers\Helpers;
 use App\Http\BaseController;
 use App\Validator;
+use Throwable;
 
 class WinlibsDeleteController extends BaseController
 {
@@ -34,28 +36,79 @@ protected function execute(array $data): void
             return;
         }
 
-        $queueDirectory = $buildsDirectory . '/winlibs-delete';
-        if (!is_dir($queueDirectory) && !mkdir($queueDirectory, 0755, true) &&
!is_dir($queueDirectory)) {
+        $queueDirectory = $buildsDirectory . '/winlibs';
+        if (!is_dir($queueDirectory) || !is_writable($queueDirectory)) {
             http_response_code(500);
-            echo 'Unable to create Winlibs deletion queue.';
+            echo 'Winlibs build queue is not writable.';
             return;
         }
 
-        $taskFile = @tempnam($queueDirectory, 'winlibs-delete-');
-        $payload = json_encode([
-            'type' => $data['type'],
-            'filename' => $data['filename'],
-        ], JSON_THROW_ON_ERROR);
-        if ($taskFile === false || realpath(dirname($taskFile)) !== realpath($queueDirectory)
-            || file_put_contents($taskFile, $payload, LOCK_EX) === false
-            || !chmod($taskFile, 0644)
-            || !rename($taskFile, $taskFile . '.json')) {
-            if ($taskFile !== false) {
-                unlink($taskFile);
-            }
+        try {
+            $this->migrateLegacyJobs($buildsDirectory, $queueDirectory);
+            $this->queueJob($queueDirectory, $data['type'],
$data['filename']);
+        } catch (Throwable $error) {
             http_response_code(500);
-            echo 'Unable to queue Winlibs deletion.';
+            echo 'Unable to queue Winlibs deletion: ' . $error->getMessage();
+        }
+    }
+
+    private function migrateLegacyJobs(string $buildsDirectory, string $queueDirectory): void
+    {
+        $legacyDirectory = $buildsDirectory . '/winlibs-delete';
+        if (!is_dir($legacyDirectory)) {
             return;
         }
+
+        $legacyJobs = glob($legacyDirectory . '/winlibs-delete-*.json');
+        if ($legacyJobs === false) {
+            throw new \RuntimeException('Unable to list previous deletion jobs.');
+        }
+        foreach ($legacyJobs as $job) {
+            $payload = json_decode((string) file_get_contents($job), true, 512,
JSON_THROW_ON_ERROR);
+            $type = is_string($payload['type'] ?? null) ?
rtrim($payload['type'], "\r\n") : null;
+            $filename = is_string($payload['filename'] ?? null) ?
rtrim($payload['filename'], "\r\n") : null;
+            if (!in_array($type, ['php', 'pecl'], true) ||
!is_string($filename)
+                || preg_match('/\A[A-Za-z0-9][A-Za-z0-9._-]*\.zip\z/', $filename) !== 1)
{
+                throw new \RuntimeException('Invalid previous deletion job: ' .
basename($job));
+            }
+
+            $this->queueJob($queueDirectory, $type, $filename);
+            if (!unlink($job)) {
+                throw new \RuntimeException('Unable to remove previous deletion job: ' .
basename($job));
+            }
+        }
+    }
+
+    private function queueJob(string $queueDirectory, string $type, string $filename): void
+    {
+        $id = bin2hex(random_bytes(12));
+        $stagingDirectory = $queueDirectory . '/.delete-' . $id;
+        $jobDirectory = $queueDirectory . '/delete-' . $id;
+        $previousUmask = umask(0);
+        try {
+            if (!@mkdir($stagingDirectory, 0777)) {
+                throw new \RuntimeException('Unable to create deletion job directory.');
+            }
+        } finally {
+            umask($previousUmask);
+        }
+
+        try {
+            $payload = json_encode([
+                'delete' => true,
+                'type' => $type,
+                'filename' => $filename,
+            ], JSON_THROW_ON_ERROR);
+            $dataFile = $stagingDirectory . '/data.json';
+            if (file_put_contents($dataFile, $payload, LOCK_EX) === false
+                || !chmod($dataFile, 0644)
+                || !rename($stagingDirectory, $jobDirectory)) {
+                throw new \RuntimeException('Unable to publish deletion job.');
+            }
+        } finally {
+            if (is_dir($stagingDirectory)) {
+                Helpers::rmdirr($stagingDirectory);
+            }
+        }
     }
 }
diff --git a/tests/Console/Command/WinlibsCommandDeleteTest.php
b/tests/Console/Command/WinlibsCommandDeleteTest.php
index 0fcdb6a..26fbf07 100644
--- a/tests/Console/Command/WinlibsCommandDeleteTest.php
+++ b/tests/Console/Command/WinlibsCommandDeleteTest.php
@@ -145,7 +145,7 @@ public function testInvalidTaskFailsAndRemainsQueued(string $type, string $filen
         $result = $this->runCommand();
         $output = (string) ob_get_clean();
         $this->assertSame(1, $result);
-        $this->assertStringContainsString('Invalid Winlibs deletion task', $output);
+        $this->assertStringContainsString('Invalid Winlibs deletion job', $output);
         $this->assertFileExists($task);
     }
 
@@ -218,7 +218,7 @@ public function testLockedJobIsProcessedOnNextRun(): void
         $target = 'libfoo-1.0-vs18-x64.zip';
         file_put_contents($directory . '/' . $target, 'zip');
         $task = $this->queue('pecl', $target);
-        $lock = fopen($task . '.lock', 'c');
+        $lock = fopen(dirname($task) . '.lock', 'c');
         flock($lock, LOCK_EX);
         try {
             $this->assertSame(0, $this->runCommand());
@@ -294,6 +294,32 @@ public function testDefaultRunnerDeletesBuildUploadedInSameRun(): void
         $this->assertSame([], $this->queuedTasks());
     }
 
+    public function testFailedUploadDoesNotBlockDeletionJobs(): void
+    {
+        $directory = $this->baseDirectory . '/pecl/deps';
+        mkdir($directory, 0755, true);
+        $target = 'libfoo-1.0-vs18-x64.zip';
+        file_put_contents($directory . '/' . $target, 'zip');
+        file_put_contents($directory . '/packages.txt', $target);
+        $this->queue('pecl', $target);
+
+        $failedUpload = $this->buildsDirectory . '/winlibs/invalid';
+        mkdir($failedUpload, 0755, true);
+        file_put_contents($failedUpload . '/data.json',
'{"type":"pecl","library":"libfoo"}');
+
+        $command = new WinlibsCommand();
+        $command->options = ['base-directory' => $this->baseDirectory,
'builds-directory' => $this->buildsDirectory];
+        ob_start();
+        $result = $command->handle();
+        $output = (string) ob_get_clean();
+
+        $this->assertSame(1, $result);
+        $this->assertStringContainsString('No valid files found in invalid', $output);
+        $this->assertFileDoesNotExist($directory . '/' . $target);
+        $this->assertSame('', file_get_contents($directory .
'/packages.txt'));
+        $this->assertSame([], $this->queuedTasks());
+    }
+
     public function testRequiresBothDirectories(): void
     {
         $command = new WinlibsCommand();
@@ -310,18 +336,24 @@ public function testRequiresBothDirectories(): void
 
     private function queue(string $type, string $filename): string
     {
-        $queue = $this->buildsDirectory . '/winlibs-delete';
+        $queue = $this->buildsDirectory . '/winlibs';
         if (!is_dir($queue)) {
             mkdir($queue, 0755, true);
         }
-        $path = $queue . '/winlibs-delete-' . uniqid() . '.json';
-        file_put_contents($path, json_encode(['type' => $type, 'filename'
=> $filename], JSON_THROW_ON_ERROR));
+        $jobDirectory = $queue . '/delete-' . uniqid();
+        mkdir($jobDirectory, 0755, true);
+        $path = $jobDirectory . '/data.json';
+        file_put_contents($path, json_encode([
+            'delete' => true,
+            'type' => $type,
+            'filename' => $filename,
+        ], JSON_THROW_ON_ERROR));
         return $path;
     }
 
     private function queuedTasks(): array
     {
-        return glob($this->buildsDirectory . '/winlibs-delete/*.json') ?: [];
+        return glob($this->buildsDirectory . '/winlibs/delete-*/data.json') ?: [];
     }
 
     private function runCommand(): int
diff --git a/tests/Http/Controllers/WinlibsDeleteControllerTest.php
b/tests/Http/Controllers/WinlibsDeleteControllerTest.php
index 011960a..fca7876 100644
--- a/tests/Http/Controllers/WinlibsDeleteControllerTest.php
+++ b/tests/Http/Controllers/WinlibsDeleteControllerTest.php
@@ -19,6 +19,7 @@ protected function setUp(): void
         parent::setUp();
         $this->buildsDirectory = sys_get_temp_dir() . '/winlibs_delete_controller_' .
uniqid();
         mkdir($this->buildsDirectory, 0755, true);
+        mkdir($this->buildsDirectory . '/winlibs', 0777);
         $this->originalBuildsDirectory = getenv('BUILDS_DIRECTORY');
         putenv('BUILDS_DIRECTORY=' . $this->buildsDirectory);
         http_response_code(200);
@@ -39,13 +40,16 @@ public function testQueuesExactBuildForBothTypes(): void
         $this->request(['type' => 'php', 'filename' =>
'libcurl-8.22.0-1-vs18-x64.zip']);
         $this->request(['type' => 'pecl', 'filename' =>
'OpenBLAS-0.3.34-vs18-x86.zip']);
 
-        $tasks = glob($this->buildsDirectory .
'/winlibs-delete/winlibs-delete-*.json');
+        $tasks = $this->queuedTasks();
         $this->assertCount(2, $tasks);
         $payloads = array_map(static fn (string $path): array => json_decode(
             (string) file_get_contents($path), true, 512, JSON_THROW_ON_ERROR
         ), $tasks);
-        $this->assertContains(['type' => 'php', 'filename'
=> 'libcurl-8.22.0-1-vs18-x64.zip'], $payloads);
-        $this->assertContains(['type' => 'pecl', 'filename'
=> 'OpenBLAS-0.3.34-vs18-x86.zip'], $payloads);
+        $this->assertContains(['delete' => true, 'type' =>
'php', 'filename' => 'libcurl-8.22.0-1-vs18-x64.zip'], $payloads);
+        $this->assertContains(['delete' => true, 'type' =>
'pecl', 'filename' => 'OpenBLAS-0.3.34-vs18-x86.zip'], $payloads);
+        foreach ($tasks as $task) {
+            $this->assertSame(0777, fileperms(dirname($task)) & 0777);
+        }
         $this->assertSame(200, http_response_code());
     }
 
@@ -55,7 +59,7 @@ public function testRejectsInvalidPayload(array $payload): void
         $output = $this->request($payload);
         $this->assertSame(400, http_response_code());
         $this->assertStringContainsString('Invalid request:', $output);
-        $this->assertSame([], glob($this->buildsDirectory .
'/winlibs-delete/*.json') ?: []);
+        $this->assertSame([], $this->queuedTasks());
     }
 
     public static function invalidPayloads(): array
@@ -83,6 +87,15 @@ public function testRejectsMissingBuildsDirectoryConfiguration(): void
         $this->assertStringContainsString('BUILDS_DIRECTORY is not set', $output);
     }
 
+    public function testRequiresExistingWinlibsQueue(): void
+    {
+        rmdir($this->buildsDirectory . '/winlibs');
+        $output = $this->request(['type' => 'php', 'filename'
=> 'zlib-1.3.2-vs18-x64.zip']);
+        $this->assertSame(500, http_response_code());
+        $this->assertStringContainsString('Winlibs build queue is not writable',
$output);
+        $this->assertSame([], $this->queuedTasks());
+    }
+
     public function testQueuedRequestIsAppliedByExistingWinlibsRunner(): void
     {
         $baseDirectory = sys_get_temp_dir() . '/winlibs_delete_api_base_' . uniqid();
@@ -105,12 +118,56 @@ public function testQueuedRequestIsAppliedByExistingWinlibsRunner(): void
             $this->assertSame(0, $command->handle());
             $this->assertSame($older, file_get_contents($index));
             $this->assertFileDoesNotExist($depsDirectory . '/vs18/x64/' . $target);
-            $this->assertSame([], glob($this->buildsDirectory .
'/winlibs-delete/*.json') ?: []);
+            $this->assertSame([], $this->queuedTasks());
         } finally {
             Helpers::rmdirr($baseDirectory);
         }
     }
 
+    public function testMovesPreviousJobsIntoTheWinlibsQueue(): void
+    {
+        $legacyDirectory = $this->buildsDirectory . '/winlibs-delete';
+        mkdir($legacyDirectory, 0755, true);
+        $previous = [
+            ['type' => 'php', 'filename' =>
'libcurl-8.22.0-1-vs18-x64.zip'],
+            ['type' => 'pecl', 'filename' =>
'OpenBLAS-0.3.34-vs18-x86.zip'],
+            ['type' => "php\n", 'filename' =>
'zlib-1.3.1-vs18-x64.zip'],
+            ['type' => 'pecl', 'filename' =>
"libfoo-1.0-vs17-x86.zip\n"],
+        ];
+        foreach ($previous as $index => $payload) {
+            file_put_contents($legacyDirectory . '/winlibs-delete-' . $index .
'.json', json_encode($payload));
+        }
+
+        $this->request(['type' => 'php', 'filename' =>
'zlib-1.3.2-vs18-x64.zip']);
+
+        $this->assertSame(200, http_response_code());
+        $this->assertSame([], glob($legacyDirectory . '/*.json') ?: []);
+        $this->assertCount(5, $this->queuedTasks());
+        foreach ($this->queuedTasks() as $task) {
+            $payload = json_decode((string) file_get_contents($task), true, 512,
JSON_THROW_ON_ERROR);
+            $this->assertSame(1, preg_match('/\A[A-Za-z0-9][A-Za-z0-9._-]*\.zip\z/',
$payload['filename']));
+        }
+
+        $baseDirectory = sys_get_temp_dir() . '/winlibs_delete_migration_base_' .
uniqid();
+        mkdir($baseDirectory, 0755, true);
+        try {
+            $command = new WinlibsCommand();
+            $command->options = [
+                'base-directory' => $baseDirectory,
+                'builds-directory' => $this->buildsDirectory,
+            ];
+            $this->assertSame(0, $command->handle());
+            $this->assertSame([], $this->queuedTasks());
+        } finally {
+            Helpers::rmdirr($baseDirectory);
+        }
+    }
+
+    private function queuedTasks(): array
+    {
+        return glob($this->buildsDirectory . '/winlibs/delete-*/data.json') ?: [];
+    }
+
     private function request(array $payload): string
     {
         $input = tempnam(sys_get_temp_dir(), 'winlibs-delete-input-');


Thread (1 message)

  • Shivam Mathur
« previous php.webmaster (#34065) next »