Author: Shivam Mathur (shivammathur)
Date: 2026-10-03T02:33:30+05:30
Commit: Separate Winlibs deletion processing from uploads · php/web-downloads@dd276db · GitHub
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') ?: [];