diff --git a/src/Api/Client.php b/src/Api/Client.php index 2fe1571..85276bc 100644 --- a/src/Api/Client.php +++ b/src/Api/Client.php @@ -79,9 +79,25 @@ public function requestRaw(Request $request): \stdClass return $this->request($request); } + /** + * Drupal.org answers unknown node IDs with HTTP 200 and a stub body, and + * serves every node type from the same endpoint, so both cases are checked + * here rather than surfacing as empty issue fields downstream. + * + * @throws \RuntimeException + * When the node does not exist or is not an issue. + */ public function getNode(string $nid): IssueNode { - return IssueNode::fromStdClass($this->request(new Request('node/' . $nid))); + $data = $this->request(new Request('node/' . $nid)); + $type = $data->type ?? null; + if (!is_string($type)) { + throw new \RuntimeException(sprintf('Node %s was not found on Drupal.org.', $nid)); + } + if ($type !== 'project_issue') { + throw new \RuntimeException(sprintf('Node %s is a %s, not an issue.', $nid, $type)); + } + return IssueNode::fromStdClass($data); } public function getFile(string $fid): File diff --git a/tests/src/ClientTest.php b/tests/src/ClientTest.php new file mode 100644 index 0000000..e1acecb --- /dev/null +++ b/tests/src/ClientTest.php @@ -0,0 +1,68 @@ + $body + */ + private static function clientResponding(array $body): Client + { + $handler = HandlerStack::create(new MockHandler([ + new Response(200, ['Content-Type' => 'application/json'], json_encode($body, JSON_THROW_ON_ERROR)), + ])); + return new class ($handler) extends Client { + public function __construct(HandlerStack $handler) + { + parent::__construct(); + $this->client = new \GuzzleHttp\Client(['handler' => $handler]); + } + }; + } + + public function testGetNodeReturnsIssue(): void + { + $client = self::clientResponding([ + 'nid' => '3383637', + 'type' => 'project_issue', + 'title' => 'Fix the thing', + 'field_project' => ['id' => '3060', 'machine_name' => 'drupal'], + ]); + + $issue = $client->getNode('3383637'); + + self::assertSame('3383637', $issue->nid); + self::assertSame('drupal', $issue->fieldProjectMachineName); + } + + public function testGetNodeRejectsNonIssueNode(): void + { + $client = self::clientResponding([ + 'nid' => '3000001', + 'type' => 'project_release', + 'title' => 'queue_throttle 8.x-1.x-dev', + ]); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Node 3000001 is a project_release, not an issue.'); + $client->getNode('3000001'); + } + + public function testGetNodeRejectsMissingNode(): void + { + $client = self::clientResponding(['comments' => [], 'body' => []]); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Node 999999999 was not found on Drupal.org.'); + $client->getNode('999999999'); + } +}