[web-downloads] main: Separate Winlibs deletion processing from uploads

From: Date: Fri, 02 Oct 2026 21:03:36 +0000
Subject: [web-downloads] main: Separate Winlibs deletion processing from uploads
Groups: php.webmaster 
Request: Send a blank email to php-webmaster+get-34068@lists.php.net to get a copy of this message
Author: Shivam Mathur (shivammathur)
Date: 2026-10-03T02:33:30+05:30

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

Separate Winlibs deletion processing from uploads

Changed paths:
  A  src/Console/Command/WinlibsDeleteCommand.php
  A  tests/Console/Command/WinlibsDeleteCommandTest.php
  D  tests/Console/Command/WinlibsCommandDeleteTest.php
  M  API.md
  M  src/Console/Command/WinlibsCommand.php
  M  src/Http/Controllers/DeletePendingJobController.php
  M  src/Http/Controllers/WinlibsDeleteController.php
  M  tests/Http/Controllers/DeletePendingJobControllerTest.php
  M  tests/Http/Controllers/WinlibsDeleteControllerTest.php


Diff:

diff --git a/API.md b/API.md
index 6f35452..00b50bb 100644
--- a/API.md
+++ b/API.md
@@ -64,8 +64,8 @@ curl -i -X GET \
 - Auth: Required
 - Purpose: Remove a queued build job before it is processed.
 - Request body (JSON):
-    - type (string, required): One of php, pecl,
winlibs, or winlibs-delete.
-    - job (string, required): The job filename (for
php/pecl), directory name (for winlibs), or
winlibs-delete-*.json filename (for legacy winlibs-delete jobs).
+    - type (string, required): One of php, pecl, or
winlibs.
+    - job (string, required): The job filename (for
php/pecl) or directory name (for winlibs).
 - Success: 200 OK with { "status": "deleted" }.
 - Errors:
     - 400 if validation fails (missing/invalid fields).
@@ -85,14 +85,6 @@ curl -i -X POST \
     https://downloads.php.net/api/delete-pending-job
 ```
 
-To remove a legacy Winlibs deletion request, pass the filename from GET
/api/list-builds without the winlibs-delete/ prefix:
-
-```json
-{"type":"winlibs-delete","job":"winlibs-delete-7coij6somubocMYxf6G.json"}
-```
-
-The winlibs-delete/ directory is removed when its last job is deleted.
-
 ---
 
 ### POST /api/php
@@ -198,14 +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 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.
+- 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.
winlibs:add processes upload jobs only.
+- Processor: php runner.php winlibs:delete --base-directory=<downloads-root>
--builds-directory=<builds-root>. Deletion jobs remain queued until this command runs.
 - 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/Console/Command/WinlibsCommand.php b/src/Console/Command/WinlibsCommand.php
index 0d9b2c9..3be1677 100644
--- a/src/Console/Command/WinlibsCommand.php
+++ b/src/Console/Command/WinlibsCommand.php
@@ -3,7 +3,6 @@
 
 namespace App\Console\Command;
 
-use App\Actions\DeleteWinlibsBuild;
 use App\Console\Command;
 use App\Helpers\Helpers;
 use Exception;
@@ -12,14 +11,18 @@
 
 class WinlibsCommand extends Command
 {
-    public string $signature = 'winlibs:add --base-directory= --builds-directory=
--delete';
-    public string $description = 'Add winlibs dependencies and process queued deletions';
+    public string $signature = 'winlibs:add --base-directory= --builds-directory=';
+    public string $description = 'Add winlibs dependencies';
 
     protected ?string $baseDirectory = null;
 
     public function handle(): int
     {
         try {
+            if (array_diff(array_keys($this->options), ['base-directory',
'builds-directory']) !== []) {
+                throw new Exception('Unsupported option for winlibs:add');
+            }
+
             $this->baseDirectory = $this->options['base-directory'] ?? null;
             if (!$this->baseDirectory) {
                 throw new Exception('Base directory is required');
@@ -35,77 +38,44 @@ public function handle(): int
                 throw new Exception('Unable to list Winlibs jobs');
             }
 
-            $uploadDirectories = [];
-            $deleteDirectories = [];
+            $errors = [];
+
             foreach ($buildDirectories as $directoryPath) {
                 if (str_starts_with(basename($directoryPath), 'delete-')) {
-                    $deleteDirectories[] = $directoryPath;
-                } else {
-                    $uploadDirectories[] = $directoryPath;
+                    continue;
                 }
-            }
-            $deleteOnly = ($this->options['delete'] ?? false) === true;
-            $jobs = $deleteOnly ? $deleteDirectories : array_merge($uploadDirectories,
$deleteDirectories);
-            $deletions = new DeleteWinlibsBuild($this->baseDirectory);
-            $errors = [];
 
-            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 {
-                    if (file_exists($lockFile)) {
-                        continue;
-                    }
-                    if (!touch($lockFile)) {
-                        throw new Exception('Unable to lock Winlibs job: ' .
basename($directoryPath));
-                    }
+                if (file_exists($lockFile)) {
+                    continue;
+                }
+                if (!touch($lockFile)) {
+                    $errors[] = 'Unable to lock Winlibs job: ' .
basename($directoryPath);
+                    continue;
                 }
 
                 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));
+                    $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']
+                            );
                         }
-                        $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();
-                        }
+                        $this->copyPeclFiles($files, $data['library']);
+                        $this->updatePackagesFile();
                     }
 
                     if (!Helpers::rmdirr($directoryPath)) {
@@ -114,11 +84,6 @@ public function handle(): int
                     unlink($lockFile);
                 } catch (Throwable $error) {
                     $errors[] = $error->getMessage();
-                } finally {
-                    if ($deleteLock !== null) {
-                        flock($deleteLock, LOCK_UN);
-                        fclose($deleteLock);
-                    }
                 }
             }
 
diff --git a/src/Console/Command/WinlibsDeleteCommand.php
b/src/Console/Command/WinlibsDeleteCommand.php
new file mode 100644
index 0000000..b30411a
--- /dev/null
+++ b/src/Console/Command/WinlibsDeleteCommand.php
@@ -0,0 +1,80 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Console\Command;
+
+use App\Actions\DeleteWinlibsBuild;
+use App\Console\Command;
+use App\Helpers\Helpers;
+use Exception;
+use Throwable;
+
+class WinlibsDeleteCommand extends Command
+{
+    public string $signature = 'winlibs:delete --base-directory= --builds-directory=';
+    public string $description = 'Process queued Winlibs deletions';
+
+    public function handle(): int
+    {
+        try {
+            $baseDirectory = $this->options['base-directory'] ?? null;
+            if (!$baseDirectory) {
+                throw new Exception('Base directory is required');
+            }
+
+            $buildsDirectory = $this->options['builds-directory'] ?? null;
+            if (!$buildsDirectory) {
+                throw new Exception('Build directory is required');
+            }
+
+            $jobDirectories = glob($buildsDirectory . '/winlibs/delete-*', GLOB_ONLYDIR);
+            if ($jobDirectories === false) {
+                throw new Exception('Unable to list Winlibs deletion jobs');
+            }
+
+            $deletions = new DeleteWinlibsBuild($baseDirectory);
+            $errors = [];
+            foreach ($jobDirectories as $directoryPath) {
+                $lockFile = $directoryPath . '.lock';
+                $lock = @fopen($lockFile, 'c');
+                if ($lock === false) {
+                    $errors[] = 'Unable to lock Winlibs job: ' .
basename($directoryPath);
+                    continue;
+                }
+
+                if (!flock($lock, LOCK_EX | LOCK_NB)) {
+                    fclose($lock);
+                    continue;
+                }
+
+                try {
+                    $data = json_decode((string) file_get_contents($directoryPath .
'/data.json'), true, 512, JSON_THROW_ON_ERROR);
+                    if (($data['delete'] ?? false) !== true
+                        || !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']);
+                    if (!Helpers::rmdirr($directoryPath)) {
+                        throw new Exception('Unable to remove Winlibs job: ' .
basename($directoryPath));
+                    }
+                    unlink($lockFile);
+                } catch (Throwable $error) {
+                    $errors[] = $error->getMessage();
+                } finally {
+                    flock($lock, LOCK_UN);
+                    fclose($lock);
+                }
+            }
+
+            if ($errors !== []) {
+                throw new Exception(implode("\n", $errors));
+            }
+            return Command::SUCCESS;
+        } catch (Throwable $error) {
+            echo $error->getMessage();
+            return Command::FAILURE;
+        }
+    }
+}
diff --git a/src/Http/Controllers/DeletePendingJobController.php
b/src/Http/Controllers/DeletePendingJobController.php
index 69ff67c..d0d3288 100644
--- a/src/Http/Controllers/DeletePendingJobController.php
+++ b/src/Http/Controllers/DeletePendingJobController.php
@@ -23,7 +23,7 @@ public function __construct(string $inputPath = 'php://input', ?string
$buildsDi
     protected function validate(array $data): bool
     {
         $validator = new Validator([
-            'type' =>
'required|string|regex:/\A(?:php|pecl|winlibs|winlibs-delete)\z/i',
+            'type' => 'required|string|regex:/^(php|pecl|winlibs)$/i',
             'job' =>
'required|string|regex:/\A(?!\.{1,2}\z)[A-Za-z0-9._-]+\z/',
         ]);
 
@@ -34,11 +34,6 @@ protected function validate(array $data): bool
         if (!$valid) {
             http_response_code(400);
             echo 'Invalid request: ' . $validator;
-        } elseif (strtolower($data['type']) === 'winlibs-delete'
-            && preg_match('/\Awinlibs-delete-[A-Za-z0-9]+\.json\z/',
$data['job']) !== 1) {
-            http_response_code(400);
-            echo 'Invalid request: job must be a winlibs-delete-*.json filename.';
-            return false;
         }
 
         return $valid;
@@ -77,25 +72,13 @@ private function deleteJob(string $type, string $jobName): void
             $this->deleteDirectoryJob($path);
         } else {
             $this->deleteFileJob($path);
-
-            if ($type === 'winlibs-delete') {
-                $directory = dirname($path);
-                $entries = @scandir($directory);
-                if ($entries === false) {
-                    throw new RuntimeException('Unable to inspect Winlibs deletion
directory.', 500);
-                }
-
-                if ($entries === ['.', '..'] && !@rmdir($directory)) {
-                    throw new RuntimeException('Unable to remove empty Winlibs deletion
directory.', 500);
-                }
-            }
         }
     }
 
     private function resolvePath(string $type, string $jobName): string
     {
         return match ($type) {
-            'php', 'pecl', 'winlibs-delete' =>
$this->buildsDirectory . '/' . $type . '/' . $jobName,
+            'php', 'pecl' => $this->buildsDirectory . '/' .
$type . '/' . $jobName,
             'winlibs' => $this->buildsDirectory . '/winlibs/' . $jobName,
             default => $this->buildsDirectory,
         };
diff --git a/src/Http/Controllers/WinlibsDeleteController.php
b/src/Http/Controllers/WinlibsDeleteController.php
index e41533e..c189914 100644
--- a/src/Http/Controllers/WinlibsDeleteController.php
+++ b/src/Http/Controllers/WinlibsDeleteController.php
@@ -44,7 +44,6 @@ protected function execute(array $data): void
         }
 
         try {
-            $this->migrateLegacyJobs($buildsDirectory, $queueDirectory);
             $this->queueJob($queueDirectory, $data['type'],
$data['filename']);
         } catch (Throwable $error) {
             http_response_code(500);
@@ -52,33 +51,6 @@ protected function execute(array $data): void
         }
     }
 
-    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));
diff --git a/tests/Console/Command/WinlibsCommandDeleteTest.php
b/tests/Console/Command/WinlibsDeleteCommandTest.php
similarity index 79%
rename from tests/Console/Command/WinlibsCommandDeleteTest.php
rename to tests/Console/Command/WinlibsDeleteCommandTest.php
index 26fbf07..8c04e46 100644
--- a/tests/Console/Command/WinlibsCommandDeleteTest.php
+++ b/tests/Console/Command/WinlibsDeleteCommandTest.php
@@ -4,11 +4,12 @@
 namespace Console\Command;
 
 use App\Console\Command\WinlibsCommand;
+use App\Console\Command\WinlibsDeleteCommand;
 use App\Helpers\Helpers;
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 
-class WinlibsCommandDeleteTest extends TestCase
+class WinlibsDeleteCommandTest extends TestCase
 {
     private string $baseDirectory;
     private string $buildsDirectory;
@@ -160,6 +161,44 @@ public static function invalidTasks(): array
         ];
     }
 
+    public function testInvalidDeleteMarkerDoesNotBlockOtherJobsAndCanBeRetried(): void
+    {
+        $directory = $this->baseDirectory . '/pecl/deps';
+        mkdir($directory, 0755, true);
+        $invalidTarget = 'libfoo-1.0-vs18-x64.zip';
+        $validTarget = 'libbar-1.0-vs18-x64.zip';
+        file_put_contents($directory . '/' . $invalidTarget, 'zip');
+        file_put_contents($directory . '/' . $validTarget, 'zip');
+        file_put_contents($directory . '/packages.txt',
"$invalidTarget\n$validTarget");
+
+        $invalidTask = $this->queue('pecl', $invalidTarget);
+        file_put_contents($invalidTask, json_encode([
+            'delete' => false,
+            'type' => 'pecl',
+            'filename' => $invalidTarget,
+        ], JSON_THROW_ON_ERROR));
+        $this->queue('pecl', $validTarget);
+
+        ob_start();
+        $result = $this->runCommand();
+        $output = (string) ob_get_clean();
+        $this->assertSame(1, $result);
+        $this->assertStringContainsString('Invalid Winlibs deletion job', $output);
+        $this->assertFileExists($invalidTask);
+        $this->assertFileExists($directory . '/' . $invalidTarget);
+        $this->assertFileDoesNotExist($directory . '/' . $validTarget);
+        $this->assertSame([$invalidTask], $this->queuedTasks());
+
+        file_put_contents($invalidTask, json_encode([
+            'delete' => true,
+            'type' => 'pecl',
+            'filename' => $invalidTarget,
+        ], JSON_THROW_ON_ERROR));
+        $this->assertSame(0, $this->runCommand());
+        $this->assertFileDoesNotExist($directory . '/' . $invalidTarget);
+        $this->assertSame([], $this->queuedTasks());
+    }
+
     public function testIndexFailureRetainsTaskAndZipForRetry(): void
     {
         $directory = $this->baseDirectory . '/pecl/deps';
@@ -233,7 +272,7 @@ public function testLockedJobIsProcessedOnNextRun(): void
         $this->assertFileDoesNotExist($directory . '/' . $target);
     }
 
-    public function testExistingWinlibsRunnerProcessesDeleteQueue(): void
+    public function testAddCommandLeavesDeletionQueued(): void
     {
         $directory = $this->baseDirectory . '/pecl/deps';
         mkdir($directory, 0755, true);
@@ -245,20 +284,23 @@ public function testExistingWinlibsRunnerProcessesDeleteQueue(): void
         $command = new WinlibsCommand();
         $command->options = ['base-directory' => $this->baseDirectory,
'builds-directory' => $this->buildsDirectory];
         $this->assertSame(0, $command->handle());
+        $this->assertFileExists($directory . '/' . $target);
+        $this->assertSame($target, file_get_contents($directory . '/packages.txt'));
+        $this->assertCount(1, $this->queuedTasks());
+
+        $this->assertSame(0, $this->runCommand());
         $this->assertFileDoesNotExist($directory . '/' . $target);
         $this->assertSame('', file_get_contents($directory .
'/packages.txt'));
         $this->assertSame([], $this->queuedTasks());
     }
 
-    public function testDeleteFlagSkipsPendingUploads(): void
+    public function testRemovedDeleteOptionDoesNotProcessJobs(): void
     {
         $directory = $this->baseDirectory . '/pecl/deps';
         mkdir($directory, 0755, true);
         $target = 'libfoo-1.0-vs18-x64.zip';
         file_put_contents($directory . '/' . $target, 'zip');
-        $this->queue('pecl', $target);
-        $pendingUpload = $this->buildsDirectory . '/winlibs/pending';
-        mkdir($pendingUpload, 0755, true);
+        $task = $this->queue('pecl', $target);
 
         $command = new WinlibsCommand();
         $command->cliArguments = [
@@ -268,12 +310,39 @@ public function testDeleteFlagSkipsPendingUploads(): void
             '--builds-directory=' . $this->buildsDirectory,
             '--delete',
         ];
+        ob_start();
+        $result = $command->handle();
+        $output = (string) ob_get_clean();
+
+        $this->assertSame(1, $result);
+        $this->assertSame('Unsupported option for winlibs:add', $output);
+        $this->assertFileExists($directory . '/' . $target);
+        $this->assertFileExists($task);
+    }
+
+    public function testDeleteCommandSkipsPendingUploads(): void
+    {
+        $directory = $this->baseDirectory . '/pecl/deps';
+        mkdir($directory, 0755, true);
+        $target = 'libfoo-1.0-vs18-x64.zip';
+        file_put_contents($directory . '/' . $target, 'zip');
+        $this->queue('pecl', $target);
+        $pendingUpload = $this->buildsDirectory . '/winlibs/pending';
+        mkdir($pendingUpload, 0755, true);
+
+        $command = new WinlibsDeleteCommand();
+        $command->cliArguments = [
+            'runner.php',
+            'winlibs:delete',
+            '--base-directory=' . $this->baseDirectory,
+            '--builds-directory=' . $this->buildsDirectory,
+        ];
         $this->assertSame(0, $command->handle());
         $this->assertFileDoesNotExist($directory . '/' . $target);
         $this->assertDirectoryExists($pendingUpload);
     }
 
-    public function testDefaultRunnerDeletesBuildUploadedInSameRun(): void
+    public function testSeparateCommandsProcessUploadedBuildThenItsDeletion(): void
     {
         $target = 'libfoo-1.0-vs18-x64.zip';
         $pendingUpload = $this->buildsDirectory . '/winlibs/1234';
@@ -288,13 +357,18 @@ public function testDefaultRunnerDeletesBuildUploadedInSameRun(): void
         $command = new WinlibsCommand();
         $command->options = ['base-directory' => $this->baseDirectory,
'builds-directory' => $this->buildsDirectory];
         $this->assertSame(0, $command->handle());
+        $this->assertFileExists($this->baseDirectory . '/pecl/deps/' . $target);
+        $this->assertSame($target, file_get_contents($this->baseDirectory .
'/pecl/deps/packages.txt'));
+        $this->assertDirectoryDoesNotExist($pendingUpload);
+        $this->assertCount(1, $this->queuedTasks());
+
+        $this->assertSame(0, $this->runCommand());
         $this->assertFileDoesNotExist($this->baseDirectory . '/pecl/deps/' .
$target);
         $this->assertSame('', file_get_contents($this->baseDirectory .
'/pecl/deps/packages.txt'));
-        $this->assertDirectoryDoesNotExist($pendingUpload);
         $this->assertSame([], $this->queuedTasks());
     }
 
-    public function testFailedUploadDoesNotBlockDeletionJobs(): void
+    public function testFailedUploadDoesNotBlockSeparateDeletionCommand(): void
     {
         $directory = $this->baseDirectory . '/pecl/deps';
         mkdir($directory, 0755, true);
@@ -315,6 +389,10 @@ public function testFailedUploadDoesNotBlockDeletionJobs(): void
 
         $this->assertSame(1, $result);
         $this->assertStringContainsString('No valid files found in invalid', $output);
+        $this->assertFileExists($directory . '/' . $target);
+        $this->assertCount(1, $this->queuedTasks());
+
+        $this->assertSame(0, $this->runCommand());
         $this->assertFileDoesNotExist($directory . '/' . $target);
         $this->assertSame('', file_get_contents($directory .
'/packages.txt'));
         $this->assertSame([], $this->queuedTasks());
@@ -322,7 +400,7 @@ public function testFailedUploadDoesNotBlockDeletionJobs(): void
 
     public function testRequiresBothDirectories(): void
     {
-        $command = new WinlibsCommand();
+        $command = new WinlibsDeleteCommand();
         $command->options = ['builds-directory' => $this->buildsDirectory];
         ob_start();
         $this->assertSame(1, $command->handle());
@@ -358,11 +436,10 @@ private function queuedTasks(): array
 
     private function runCommand(): int
     {
-        $command = new WinlibsCommand();
+        $command = new WinlibsDeleteCommand();
         $command->options = [
             'base-directory' => $this->baseDirectory,
             'builds-directory' => $this->buildsDirectory,
-            'delete' => true,
         ];
         return $command->handle();
     }
diff --git a/tests/Http/Controllers/DeletePendingJobControllerTest.php
b/tests/Http/Controllers/DeletePendingJobControllerTest.php
index 253dc6a..1f172b8 100644
--- a/tests/Http/Controllers/DeletePendingJobControllerTest.php
+++ b/tests/Http/Controllers/DeletePendingJobControllerTest.php
@@ -85,47 +85,10 @@ public function testDeletesWinlibsJobDirectory(): void
     /**
      * @throws JsonException
      */
-    public function testDeletesLegacyWinlibsDeleteFileAndLockWithoutTouchingActiveQueue(): void
+    public function testReturns404WhenJobMissing(): void
     {
-        $legacyDir = $this->tempDir . '/winlibs-delete';
-        $activeDir = $this->tempDir . '/winlibs/delete-active';
-        mkdir($legacyDir, 0755, true);
-        mkdir($activeDir, 0755, true);
-
-        $jobName = 'winlibs-delete-7coij6somubocMYxf6G.json';
-        $jobFile = $legacyDir . '/' . $jobName;
-        file_put_contents($jobFile, '{}');
-        file_put_contents($jobFile . '.lock', '');
-        $lastJobName = 'winlibs-delete-966s7gpgm2goaDpODXH.json';
-        $lastJobFile = $legacyDir . '/' . $lastJobName;
-        file_put_contents($lastJobFile, '{}');
-        file_put_contents($activeDir . '/data.json', '{}');
-
-        $inputFile = $this->createInputFile(json_encode([
-            'type' => 'WINLIBS-DELETE',
-            'job' => $jobName,
-        ], JSON_THROW_ON_ERROR));
-
-        http_response_code(200);
-        $controller = new DeletePendingJobController($inputFile, $this->tempDir);
-        ob_start();
-        $controller->handle();
-        $output = ob_get_clean();
-
-        static::assertSame(200, http_response_code());
-       
static::assertJsonStringEqualsJsonString('{"status":"deleted"}',
$output);
-        static::assertFileDoesNotExist($jobFile);
-        static::assertFileDoesNotExist($jobFile . '.lock');
-        static::assertDirectoryExists($legacyDir);
-        static::assertFileExists($lastJobFile);
-        static::assertFileExists($activeDir . '/data.json');
-
-        unlink($inputFile);
-
-        $inputFile = $this->createInputFile(json_encode([
-            'type' => 'winlibs-delete',
-            'job' => $lastJobName,
-        ], JSON_THROW_ON_ERROR));
+        $payload = json_encode(['type' => 'pecl', 'job' =>
'missing.zip'], JSON_THROW_ON_ERROR);
+        $inputFile = $this->createInputFile($payload);
 
         http_response_code(200);
         $controller = new DeletePendingJobController($inputFile, $this->tempDir);
@@ -133,10 +96,8 @@ public function testDeletesLegacyWinlibsDeleteFileAndLockWithoutTouchingActiveQu
         $controller->handle();
         $output = ob_get_clean();
 
-        static::assertSame(200, http_response_code());
-       
static::assertJsonStringEqualsJsonString('{"status":"deleted"}',
$output);
-        static::assertDirectoryDoesNotExist($legacyDir);
-        static::assertFileExists($activeDir . '/data.json');
+        static::assertSame(404, http_response_code());
+        static::assertStringContainsString('Job not found', $output);
 
         unlink($inputFile);
     }
@@ -144,40 +105,10 @@ public function
testDeletesLegacyWinlibsDeleteFileAndLockWithoutTouchingActiveQu
     /**
      * @throws JsonException
      */
-    public function testRejectsOtherFilesAndParentDirectoryNames(): void
+    public function testRejectsParentDirectoryAsWinlibsJob(): void
     {
-        $legacyDir = $this->tempDir . '/winlibs-delete';
-        mkdir($legacyDir, 0755, true);
-        file_put_contents($legacyDir . '/other.json', '{}');
-
-        foreach ([
-            ['type' => 'winlibs-delete', 'job' =>
'other.json'],
-            ['type' => 'winlibs-delete', 'job' =>
'winlibs-delete-..json'],
-            ['type' => 'winlibs', 'job' => '..'],
-        ] as $request) {
-            $inputFile = $this->createInputFile(json_encode($request, JSON_THROW_ON_ERROR));
-
-            http_response_code(200);
-            $controller = new DeletePendingJobController($inputFile, $this->tempDir);
-            ob_start();
-            $controller->handle();
-            $output = ob_get_clean();
-
-            static::assertSame(400, http_response_code());
-            static::assertStringContainsString('Invalid request', $output);
-            static::assertDirectoryExists($this->tempDir);
-            static::assertFileExists($legacyDir . '/other.json');
-
-            unlink($inputFile);
-        }
-    }
-
-    /**
-     * @throws JsonException
-     */
-    public function testReturns404WhenJobMissing(): void
-    {
-        $payload = json_encode(['type' => 'pecl', 'job' =>
'missing.zip'], JSON_THROW_ON_ERROR);
+        mkdir($this->tempDir . '/winlibs', 0755, true);
+        $payload = json_encode(['type' => 'winlibs', 'job' =>
'..'], JSON_THROW_ON_ERROR);
         $inputFile = $this->createInputFile($payload);
 
         http_response_code(200);
@@ -186,8 +117,9 @@ public function testReturns404WhenJobMissing(): void
         $controller->handle();
         $output = ob_get_clean();
 
-        static::assertSame(404, http_response_code());
-        static::assertStringContainsString('Job not found', $output);
+        static::assertSame(400, http_response_code());
+        static::assertStringContainsString('Invalid request', $output);
+        static::assertDirectoryExists($this->tempDir);
 
         unlink($inputFile);
     }
diff --git a/tests/Http/Controllers/WinlibsDeleteControllerTest.php
b/tests/Http/Controllers/WinlibsDeleteControllerTest.php
index fca7876..7ab5ff4 100644
--- a/tests/Http/Controllers/WinlibsDeleteControllerTest.php
+++ b/tests/Http/Controllers/WinlibsDeleteControllerTest.php
@@ -3,7 +3,7 @@
 
 namespace Http\Controllers;
 
-use App\Console\Command\WinlibsCommand;
+use App\Console\Command\WinlibsDeleteCommand;
 use App\Helpers\Helpers;
 use App\Http\Controllers\WinlibsDeleteController;
 use PHPUnit\Framework\Attributes\DataProvider;
@@ -96,7 +96,7 @@ public function testRequiresExistingWinlibsQueue(): void
         $this->assertSame([], $this->queuedTasks());
     }
 
-    public function testQueuedRequestIsAppliedByExistingWinlibsRunner(): void
+    public function testQueuedRequestIsAppliedByWinlibsDeleteCommand(): void
     {
         $baseDirectory = sys_get_temp_dir() . '/winlibs_delete_api_base_' . uniqid();
         $depsDirectory = $baseDirectory . '/php-sdk/deps';
@@ -110,7 +110,7 @@ public function testQueuedRequestIsAppliedByExistingWinlibsRunner(): void
 
         try {
             $this->request(['type' => 'php', 'filename' =>
$target]);
-            $command = new WinlibsCommand();
+            $command = new WinlibsDeleteCommand();
             $command->options = [
                 'base-directory' => $baseDirectory,
                 'builds-directory' => $this->buildsDirectory,
@@ -124,45 +124,6 @@ public function testQueuedRequestIsAppliedByExistingWinlibsRunner(): void
         }
     }
 
-    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') ?: [];


Thread (1 message)

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