diff --git a/src/Contracts/IssuesServiceInterface.php b/src/Contracts/IssuesServiceInterface.php index 74f6e1ff..fae70328 100644 --- a/src/Contracts/IssuesServiceInterface.php +++ b/src/Contracts/IssuesServiceInterface.php @@ -4,4 +4,4 @@ namespace ConduitUI\Issue\Contracts; -interface IssuesServiceInterface extends ManagesIssueAssigneesInterface, ManagesIssueLabelsInterface, ManagesIssuesInterface {} +interface IssuesServiceInterface extends ManagesIssueAssigneesInterface, ManagesIssueCommentsInterface, ManagesIssueLabelsInterface, ManagesIssuesInterface {} diff --git a/src/Contracts/ManagesIssueCommentsInterface.php b/src/Contracts/ManagesIssueCommentsInterface.php new file mode 100644 index 00000000..08ae670d --- /dev/null +++ b/src/Contracts/ManagesIssueCommentsInterface.php @@ -0,0 +1,34 @@ + + */ + public function listComments(string $owner, string $repo, int $issueNumber): Collection; + + public function getComment(string $owner, string $repo, int $commentId): Comment; + + public function createComment(string $owner, string $repo, int $issueNumber, string $body): Comment; + + public function updateComment(string $owner, string $repo, int $commentId, string $body): Comment; + + public function deleteComment(string $owner, string $repo, int $commentId): bool; + + /** + * @return \Illuminate\Support\Collection + */ + public function listCommentReactions(string $owner, string $repo, int $commentId): Collection; + + public function addCommentReaction(string $owner, string $repo, int $commentId, string $content): Reaction; + + public function removeCommentReaction(string $owner, string $repo, int $commentId, int $reactionId): bool; +} diff --git a/src/Data/Comment.php b/src/Data/Comment.php new file mode 100644 index 00000000..9c4b9d3a --- /dev/null +++ b/src/Data/Comment.php @@ -0,0 +1,46 @@ + $this->id, + 'body' => $this->body, + 'user' => $this->user->toArray(), + 'created_at' => $this->createdAt->format('c'), + 'updated_at' => $this->updatedAt->format('c'), + 'html_url' => $this->htmlUrl, + 'issue_url' => $this->issueUrl, + ]; + } +} diff --git a/src/Data/Reaction.php b/src/Data/Reaction.php new file mode 100644 index 00000000..b6f5dc3f --- /dev/null +++ b/src/Data/Reaction.php @@ -0,0 +1,37 @@ + $this->id, + 'content' => $this->content, + 'user' => $this->user->toArray(), + 'created_at' => $this->createdAt->format('c'), + ]; + } +} diff --git a/src/Services/IssuesService.php b/src/Services/IssuesService.php index 2d380d59..f493c9cc 100644 --- a/src/Services/IssuesService.php +++ b/src/Services/IssuesService.php @@ -7,12 +7,14 @@ use ConduitUi\GitHubConnector\Connector; use ConduitUI\Issue\Contracts\IssuesServiceInterface; use ConduitUI\Issue\Traits\ManagesIssueAssignees; +use ConduitUI\Issue\Traits\ManagesIssueComments; use ConduitUI\Issue\Traits\ManagesIssueLabels; use ConduitUI\Issue\Traits\ManagesIssues; class IssuesService implements IssuesServiceInterface { use ManagesIssueAssignees; + use ManagesIssueComments; use ManagesIssueLabels; use ManagesIssues; diff --git a/src/Traits/ManagesIssueComments.php b/src/Traits/ManagesIssueComments.php new file mode 100644 index 00000000..01097bfe --- /dev/null +++ b/src/Traits/ManagesIssueComments.php @@ -0,0 +1,98 @@ + + */ + public function listComments(string $owner, string $repo, int $issueNumber): Collection + { + $response = $this->connector->send( + $this->connector->get("/repos/{$owner}/{$repo}/issues/{$issueNumber}/comments") + ); + + return collect($response->json()) + ->map(fn (array $data) => Comment::fromArray($data)); + } + + public function getComment(string $owner, string $repo, int $commentId): Comment + { + $response = $this->connector->send( + $this->connector->get("/repos/{$owner}/{$repo}/issues/comments/{$commentId}") + ); + + return Comment::fromArray($response->json()); + } + + public function createComment(string $owner, string $repo, int $issueNumber, string $body): Comment + { + $response = $this->connector->send( + $this->connector->post("/repos/{$owner}/{$repo}/issues/{$issueNumber}/comments", [ + 'body' => $body, + ]) + ); + + return Comment::fromArray($response->json()); + } + + public function updateComment(string $owner, string $repo, int $commentId, string $body): Comment + { + $response = $this->connector->send( + $this->connector->patch("/repos/{$owner}/{$repo}/issues/comments/{$commentId}", [ + 'body' => $body, + ]) + ); + + return Comment::fromArray($response->json()); + } + + public function deleteComment(string $owner, string $repo, int $commentId): bool + { + $response = $this->connector->send( + $this->connector->delete("/repos/{$owner}/{$repo}/issues/comments/{$commentId}") + ); + + return $response->successful(); + } + + /** + * @return \Illuminate\Support\Collection + */ + public function listCommentReactions(string $owner, string $repo, int $commentId): Collection + { + $response = $this->connector->send( + $this->connector->get("/repos/{$owner}/{$repo}/issues/comments/{$commentId}/reactions") + ); + + return collect($response->json()) + ->map(fn (array $data) => Reaction::fromArray($data)); + } + + public function addCommentReaction(string $owner, string $repo, int $commentId, string $content): Reaction + { + $response = $this->connector->send( + $this->connector->post("/repos/{$owner}/{$repo}/issues/comments/{$commentId}/reactions", [ + 'content' => $content, + ]) + ); + + return Reaction::fromArray($response->json()); + } + + public function removeCommentReaction(string $owner, string $repo, int $commentId, int $reactionId): bool + { + $response = $this->connector->send( + $this->connector->delete("/repos/{$owner}/{$repo}/issues/comments/{$commentId}/reactions/{$reactionId}") + ); + + return $response->successful(); + } +} diff --git a/tests/Unit/Data/CommentTest.php b/tests/Unit/Data/CommentTest.php new file mode 100644 index 00000000..49dbb29a --- /dev/null +++ b/tests/Unit/Data/CommentTest.php @@ -0,0 +1,56 @@ + 123, + 'body' => 'This is a test comment', + 'user' => [ + 'id' => 456, + 'login' => 'testuser', + 'avatar_url' => 'https://github.com/testuser.png', + 'html_url' => 'https://github.com/testuser', + 'type' => 'User', + ], + 'created_at' => '2023-01-01T12:00:00Z', + 'updated_at' => '2023-01-02T12:00:00Z', + 'html_url' => 'https://github.com/owner/repo/issues/comments/123', + 'issue_url' => 'https://api.github.com/repos/owner/repo/issues/1', + ]; + + $comment = Comment::fromArray($data); + + expect($comment->id)->toBe(123); + expect($comment->body)->toBe('This is a test comment'); + expect($comment->user)->toBeInstanceOf(User::class); + expect($comment->user->login)->toBe('testuser'); + expect($comment->htmlUrl)->toBe('https://github.com/owner/repo/issues/comments/123'); + expect($comment->issueUrl)->toBe('https://api.github.com/repos/owner/repo/issues/1'); +}); + +test('can convert comment to array', function () { + $user = new User(456, 'testuser', 'https://github.com/testuser.png', 'https://github.com/testuser', 'User'); + + $comment = new Comment( + id: 123, + body: 'This is a test comment', + user: $user, + createdAt: new DateTime('2023-01-01T12:00:00Z'), + updatedAt: new DateTime('2023-01-02T12:00:00Z'), + htmlUrl: 'https://github.com/owner/repo/issues/comments/123', + issueUrl: 'https://api.github.com/repos/owner/repo/issues/1', + ); + + $array = $comment->toArray(); + + expect($array['id'])->toBe(123); + expect($array['body'])->toBe('This is a test comment'); + expect($array['user'])->toBeArray(); + expect($array['user']['login'])->toBe('testuser'); + expect($array['html_url'])->toBe('https://github.com/owner/repo/issues/comments/123'); + expect($array['issue_url'])->toBe('https://api.github.com/repos/owner/repo/issues/1'); +}); diff --git a/tests/Unit/Data/ReactionTest.php b/tests/Unit/Data/ReactionTest.php new file mode 100644 index 00000000..ca68c5d8 --- /dev/null +++ b/tests/Unit/Data/ReactionTest.php @@ -0,0 +1,68 @@ + 123, + 'content' => '+1', + 'user' => [ + 'id' => 456, + 'login' => 'testuser', + 'avatar_url' => 'https://github.com/testuser.png', + 'html_url' => 'https://github.com/testuser', + 'type' => 'User', + ], + 'created_at' => '2023-01-01T12:00:00Z', + ]; + + $reaction = Reaction::fromArray($data); + + expect($reaction->id)->toBe(123); + expect($reaction->content)->toBe('+1'); + expect($reaction->user)->toBeInstanceOf(User::class); + expect($reaction->user->login)->toBe('testuser'); +}); + +test('can convert reaction to array', function () { + $user = new User(456, 'testuser', 'https://github.com/testuser.png', 'https://github.com/testuser', 'User'); + + $reaction = new Reaction( + id: 123, + content: '+1', + user: $user, + createdAt: new DateTime('2023-01-01T12:00:00Z'), + ); + + $array = $reaction->toArray(); + + expect($array['id'])->toBe(123); + expect($array['content'])->toBe('+1'); + expect($array['user'])->toBeArray(); + expect($array['user']['login'])->toBe('testuser'); +}); + +test('supports various reaction types', function (string $content) { + $user = new User(456, 'testuser', 'https://github.com/testuser.png', 'https://github.com/testuser', 'User'); + + $reaction = new Reaction( + id: 123, + content: $content, + user: $user, + createdAt: new DateTime('2023-01-01T12:00:00Z'), + ); + + expect($reaction->content)->toBe($content); +})->with([ + '+1', + '-1', + 'laugh', + 'confused', + 'heart', + 'hooray', + 'rocket', + 'eyes', +]);