From 82d2e7a4e2951f1766f84d44f539ddd5a645d9a7 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 6 May 2026 02:06:36 +0100 Subject: [PATCH 1/2] Add Droplet list filters --- CHANGELOG.md | 1 + src/Api/Droplet.php | 20 ++++++- tests/Api/DropletTest.php | 108 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 tests/Api/DropletTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f892cf..ac48c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGE LOG * Fixed hydration of object-backed App Platform and project resource fields * Fixed `LoadBalancer` hydration and update serialization * Fixed `Volume` hydration when `droplet_ids` is null +* Add support for listing Droplets by name or type ## 5.0.5 (03/05/2025) diff --git a/src/Api/Droplet.php b/src/Api/Droplet.php index a9053b3..1d3d5a2 100644 --- a/src/Api/Droplet.php +++ b/src/Api/Droplet.php @@ -27,13 +27,29 @@ class Droplet extends AbstractApi { /** + * @param 'droplets'|'gpus'|null $type + * * @throws ExceptionInterface * * @return DropletEntity[] */ - public function getAll(?string $tag = null): array + public function getAll(?string $tag = null, ?string $name = null, ?string $type = null): array { - $droplets = $this->get('droplets', null === $tag ? [] : ['tag_name' => $tag]); + $query = []; + + if (null !== $tag) { + $query['tag_name'] = $tag; + } + + if (null !== $name) { + $query['name'] = $name; + } + + if (null !== $type && \in_array($type, ['droplets', 'gpus'], true)) { + $query['type'] = $type; + } + + $droplets = $this->get('droplets', $query); return \array_map(function ($droplet) { return new DropletEntity($droplet); diff --git a/tests/Api/DropletTest.php b/tests/Api/DropletTest.php new file mode 100644 index 0000000..36d0579 --- /dev/null +++ b/tests/Api/DropletTest.php @@ -0,0 +1,108 @@ + + * (c) Graham Campbell + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace DigitalOceanV2\Tests\Api; + +use DigitalOceanV2\Api\Droplet; +use DigitalOceanV2\Client; +use DigitalOceanV2\Entity\Droplet as DropletEntity; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; +use Http\Client\Common\HttpMethodsClientInterface; +use PHPUnit\Framework\TestCase; + +/** + * @author Graham Campbell + */ +class DropletTest extends TestCase +{ + public function testItCreatesAnArrayOfDropletEntities(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets')->getAll(); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + + public function testItFiltersDropletsByTagName(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets?tag_name=awesome')->getAll('awesome'); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + + public function testItFiltersDropletsByName(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets?name=example.com')->getAll(name: 'example.com'); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + + public function testItFiltersDropletsByType(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets?type=gpus')->getAll(type: 'gpus'); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + + public function testItFiltersDropletsByStandardType(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets?type=droplets')->getAll(type: 'droplets'); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + + public function testItFiltersDropletsByNameAndType(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets?name=example.com&type=gpus') + ->getAll(name: 'example.com', type: 'gpus'); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + + private function createApiExpectingGet(string $uri): Droplet + { + $client = $this->createMock(Client::class); + $client->expects(self::once()) + ->method('getHttpClient') + ->willReturn($httpClient = $this->createMock(HttpMethodsClientInterface::class)); + + $httpClient->expects(self::once()) + ->method('get') + ->with($uri) + ->willReturn(self::createResponse()); + + return new Droplet($client); + } + + private static function createResponse(): Response + { + return new Response( + 200, + ['Content-Type' => ['application/json']], + Utils::streamFor(\json_encode(['droplets' => [ + [ + 'id' => 3164444, + 'name' => 'example.com', + 'features' => [], + ], + ]])) + ); + } +} From 5383feb808b84b8a6e655faa8b4b5fe18879a695 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 6 May 2026 02:16:08 +0100 Subject: [PATCH 2/2] Use explicit Droplet list filter methods --- src/Api/Droplet.php | 56 ++++++++++++++++++++++++++++++++------- tests/Api/DropletTest.php | 16 ++++++++--- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/Api/Droplet.php b/src/Api/Droplet.php index 1d3d5a2..a3a9f43 100644 --- a/src/Api/Droplet.php +++ b/src/Api/Droplet.php @@ -27,28 +27,64 @@ class Droplet extends AbstractApi { /** - * @param 'droplets'|'gpus'|null $type - * * @throws ExceptionInterface * * @return DropletEntity[] */ - public function getAll(?string $tag = null, ?string $name = null, ?string $type = null): array + public function getAll(?string $tag = null): array { - $query = []; + return $this->getAllWithQuery(null === $tag ? [] : ['tag_name' => $tag]); + } - if (null !== $tag) { - $query['tag_name'] = $tag; - } + /** + * @throws ExceptionInterface + * + * @return DropletEntity[] + */ + public function getAllByTag(string $tag): array + { + return $this->getAllWithQuery(['tag_name' => $tag]); + } - if (null !== $name) { - $query['name'] = $name; - } + /** + * @param 'droplets'|'gpus'|null $type + * + * @throws ExceptionInterface + * + * @return DropletEntity[] + */ + public function getAllByName(string $name, ?string $type = null): array + { + $query = ['name' => $name]; if (null !== $type && \in_array($type, ['droplets', 'gpus'], true)) { $query['type'] = $type; } + return $this->getAllWithQuery($query); + } + + /** + * @param 'droplets'|'gpus' $type + * + * @throws ExceptionInterface + * + * @return DropletEntity[] + */ + public function getAllByType(string $type): array + { + return $this->getAllWithQuery(\in_array($type, ['droplets', 'gpus'], true) ? ['type' => $type] : []); + } + + /** + * @param array $query + * + * @throws ExceptionInterface + * + * @return DropletEntity[] + */ + private function getAllWithQuery(array $query): array + { $droplets = $this->get('droplets', $query); return \array_map(function ($droplet) { diff --git a/tests/Api/DropletTest.php b/tests/Api/DropletTest.php index 36d0579..a0f6458 100644 --- a/tests/Api/DropletTest.php +++ b/tests/Api/DropletTest.php @@ -43,9 +43,17 @@ public function testItFiltersDropletsByTagName(): void self::assertSame('example.com', $droplets[0]->name); } + public function testItFiltersDropletsByTagNameMethod(): void + { + $droplets = $this->createApiExpectingGet('/v2/droplets?tag_name=awesome')->getAllByTag('awesome'); + + self::assertInstanceOf(DropletEntity::class, $droplets[0]); + self::assertSame('example.com', $droplets[0]->name); + } + public function testItFiltersDropletsByName(): void { - $droplets = $this->createApiExpectingGet('/v2/droplets?name=example.com')->getAll(name: 'example.com'); + $droplets = $this->createApiExpectingGet('/v2/droplets?name=example.com')->getAllByName('example.com'); self::assertInstanceOf(DropletEntity::class, $droplets[0]); self::assertSame('example.com', $droplets[0]->name); @@ -53,7 +61,7 @@ public function testItFiltersDropletsByName(): void public function testItFiltersDropletsByType(): void { - $droplets = $this->createApiExpectingGet('/v2/droplets?type=gpus')->getAll(type: 'gpus'); + $droplets = $this->createApiExpectingGet('/v2/droplets?type=gpus')->getAllByType('gpus'); self::assertInstanceOf(DropletEntity::class, $droplets[0]); self::assertSame('example.com', $droplets[0]->name); @@ -61,7 +69,7 @@ public function testItFiltersDropletsByType(): void public function testItFiltersDropletsByStandardType(): void { - $droplets = $this->createApiExpectingGet('/v2/droplets?type=droplets')->getAll(type: 'droplets'); + $droplets = $this->createApiExpectingGet('/v2/droplets?type=droplets')->getAllByType('droplets'); self::assertInstanceOf(DropletEntity::class, $droplets[0]); self::assertSame('example.com', $droplets[0]->name); @@ -70,7 +78,7 @@ public function testItFiltersDropletsByStandardType(): void public function testItFiltersDropletsByNameAndType(): void { $droplets = $this->createApiExpectingGet('/v2/droplets?name=example.com&type=gpus') - ->getAll(name: 'example.com', type: 'gpus'); + ->getAllByName('example.com', 'gpus'); self::assertInstanceOf(DropletEntity::class, $droplets[0]); self::assertSame('example.com', $droplets[0]->name);