Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 111 additions & 10 deletions src/VCS/Adapter/Git/Gitea.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,18 +188,25 @@ public function getRepositoryTree(string $owner, string $repositoryName, string
* @param string $message Commit message
Comment thread
jaysomani marked this conversation as resolved.
* @return array<mixed> Response from API
*/
public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file'): array
public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file', string $branch = ''): array
{
$url = "/repos/{$owner}/{$repositoryName}/contents/{$filepath}";
Comment thread
jaysomani marked this conversation as resolved.

$payload = [
'content' => base64_encode($content),
'message' => $message
];

// Add branch if specified
if (!empty($branch)) {
$payload['branch'] = $branch;
}
Comment thread
Meldiron marked this conversation as resolved.

$response = $this->call(
self::METHOD_POST,
$url,
['Authorization' => "token $this->accessToken"],
[
'content' => base64_encode($content),
'message' => $message
]
$payload
);

$responseHeaders = $response['headers'] ?? [];
Expand Down Expand Up @@ -347,19 +354,88 @@ public function deleteRepository(string $owner, string $repositoryName): bool
return true;
}

/**
* Create a pull request
*
* @param string $owner Owner of the repository
* @param string $repositoryName Name of the repository
* @param string $title PR title
* @param string $head Source branch
* @param string $base Target branch
* @param string $body PR description (optional)
* @return array<mixed> Created PR details
*/
public function createPullRequest(string $owner, string $repositoryName, string $title, string $head, string $base, string $body = ''): array
Comment thread
Meldiron marked this conversation as resolved.
{
$url = "/repos/{$owner}/{$repositoryName}/pulls";

$payload = [
'title' => $title,
'head' => $head,
'base' => $base,
];

if (!empty($body)) {
$payload['body'] = $body;
}

$response = $this->call(
self::METHOD_POST,
$url,
['Authorization' => "token $this->accessToken"],
$payload
);

$responseHeaders = $response['headers'] ?? [];
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
if ($responseHeadersStatusCode >= 400) {
throw new Exception("Failed to create pull request: HTTP {$responseHeadersStatusCode}");
}

$responseBody = $response['body'] ?? [];

return $responseBody;
Comment thread
jaysomani marked this conversation as resolved.
}

public function createComment(string $owner, string $repositoryName, int $pullRequestNumber, string $comment): string
{
throw new Exception("Not implemented yet");
$url = "/repos/{$owner}/{$repositoryName}/issues/{$pullRequestNumber}/comments";

$response = $this->call(self::METHOD_POST, $url, ['Authorization' => "token $this->accessToken"], ['body' => $comment]);

$responseBody = $response['body'] ?? [];

if (!array_key_exists('id', $responseBody)) {
throw new Exception("Comment creation response is missing comment ID.");
}

return (string) ($responseBody['id'] ?? '');
}
Comment thread
jaysomani marked this conversation as resolved.

public function getComment(string $owner, string $repositoryName, string $commentId): string
{
throw new Exception("Not implemented yet");
$url = "/repos/{$owner}/{$repositoryName}/issues/comments/{$commentId}";

$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);

$responseBody = $response['body'] ?? [];

return $responseBody['body'] ?? '';
}

public function updateComment(string $owner, string $repositoryName, int $commentId, string $comment): string
{
throw new Exception("Not implemented yet");
$url = "/repos/{$owner}/{$repositoryName}/issues/comments/{$commentId}";

$response = $this->call(self::METHOD_PATCH, $url, ['Authorization' => "token $this->accessToken"], ['body' => $comment]);

$responseBody = $response['body'] ?? [];

if (!array_key_exists('id', $responseBody)) {
throw new Exception("Comment update response is missing comment ID.");
}

return (string) ($responseBody['id'] ?? '');
}
Comment thread
jaysomani marked this conversation as resolved.

public function getUser(string $username): array
Expand All @@ -374,12 +450,37 @@ public function getOwnerName(string $installationId): string

public function getPullRequest(string $owner, string $repositoryName, int $pullRequestNumber): array
{
throw new Exception("Not implemented yet");
$url = "/repos/{$owner}/{$repositoryName}/pulls/{$pullRequestNumber}";

$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);

$responseHeaders = $response['headers'] ?? [];
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
if ($responseHeadersStatusCode >= 400) {
throw new Exception("Failed to get pull request: HTTP {$responseHeadersStatusCode}");
}

return $response['body'] ?? [];
}

public function getPullRequestFromBranch(string $owner, string $repositoryName, string $branch): array
{
throw new Exception("Not implemented yet");
$url = "/repos/{$owner}/{$repositoryName}/pulls?state=open&sort=recentupdate";

$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);

Comment thread
jaysomani marked this conversation as resolved.
$responseBody = $response['body'] ?? [];

// Filter by head branch (source branch of the PR)
foreach ($responseBody as $pr) {
$prHead = $pr['head'] ?? [];
$prHeadRef = $prHead['ref'] ?? '';
Comment thread
jaysomani marked this conversation as resolved.
Outdated
if ($prHeadRef === $branch) {
return $pr;
}
}

return [];
Comment thread
jaysomani marked this conversation as resolved.
Outdated
}
Comment thread
jaysomani marked this conversation as resolved.

public function listBranches(string $owner, string $repositoryName): array
Expand Down
197 changes: 194 additions & 3 deletions tests/VCS/Adapter/GiteaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,106 @@ public function testCreatePrivateRepository(): void
$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

public function testCommentWorkflow(): void
{
$repositoryName = 'test-comment-workflow-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);

try {
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');
$this->vcsAdapter->createBranch(self::$owner, $repositoryName, 'comment-test', 'main');
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'test.txt', 'test', 'Add test file', 'comment-test');

$pr = $this->vcsAdapter->createPullRequest(
self::$owner,
$repositoryName,
'Comment Test PR',
'comment-test',
'main'
);

$prNumber = $pr['number'] ?? 0;
$this->assertGreaterThan(0, $prNumber);

$originalComment = 'This is a test comment';
$commentId = $this->vcsAdapter->createComment(self::$owner, $repositoryName, $prNumber, $originalComment);

$this->assertNotEmpty($commentId);
$this->assertIsString($commentId);

$retrievedComment = $this->vcsAdapter->getComment(self::$owner, $repositoryName, $commentId);
$this->assertSame($originalComment, $retrievedComment);

$updatedCommentText = 'This comment has been updated';
$updatedCommentId = $this->vcsAdapter->updateComment(self::$owner, $repositoryName, (int)$commentId, $updatedCommentText);

$this->assertSame($commentId, $updatedCommentId);

$finalComment = $this->vcsAdapter->getComment(self::$owner, $repositoryName, $commentId);
$this->assertSame($updatedCommentText, $finalComment);
} finally {
$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}
}

public function testGetComment(): void
{
$this->markTestSkipped('Will be implemented in follow-up PR');
$repositoryName = 'test-get-comment-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);

$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');
$this->vcsAdapter->createBranch(self::$owner, $repositoryName, 'test-branch', 'main');
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'test.txt', 'test', 'Add test', 'test-branch');

// Create PR
$pr = $this->vcsAdapter->createPullRequest(
self::$owner,
$repositoryName,
'Test PR',
'test-branch',
'main'
);

$prNumber = $pr['number'] ?? 0;

// Create a comment
$commentId = $this->vcsAdapter->createComment(self::$owner, $repositoryName, $prNumber, 'Test comment');

Comment thread
jaysomani marked this conversation as resolved.
// Test getComment
$result = $this->vcsAdapter->getComment(self::$owner, $repositoryName, $commentId);

$this->assertIsString($result);
$this->assertSame('Test comment', $result);

$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

public function testCreateCommentInvalidPR(): void
{
$repositoryName = 'test-comment-invalid-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');

try {
$this->expectException(\Exception::class);
$this->vcsAdapter->createComment(self::$owner, $repositoryName, 99999, 'Test comment');
} finally {
$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}
}

public function testGetCommentInvalidId(): void
{
$repositoryName = 'test-get-comment-invalid-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');

$result = $this->vcsAdapter->getComment(self::$owner, $repositoryName, '99999999');

$this->assertIsString($result);
$this->assertSame('', $result);

$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

public function testGetRepositoryTreeWithSlashInBranchName(): void
Expand Down Expand Up @@ -340,7 +437,56 @@ public function testListRepositoryContentsNonExistingPath(): void

public function testGetPullRequest(): void
{
$this->markTestSkipped('Will be implemented in follow-up PR');
$repositoryName = 'test-get-pull-request-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);

$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');
$this->vcsAdapter->createBranch(self::$owner, $repositoryName, 'feature-branch', 'main');
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'feature.txt', 'feature content', 'Add feature', 'feature-branch');

$pr = $this->vcsAdapter->createPullRequest(
self::$owner,
$repositoryName,
'Test PR',
'feature-branch',
'main',
'Test PR description'
);

$prNumber = $pr['number'] ?? 0;
$this->assertGreaterThan(0, $prNumber);

// Now test getPullRequest
$result = $this->vcsAdapter->getPullRequest(self::$owner, $repositoryName, $prNumber);

$this->assertIsArray($result);
$this->assertArrayHasKey('number', $result);
$this->assertArrayHasKey('title', $result);
$this->assertArrayHasKey('state', $result);
$this->assertArrayHasKey('head', $result);
$this->assertArrayHasKey('base', $result);

$this->assertSame($prNumber, $result['number']);
$this->assertSame('Test PR', $result['title']);
$this->assertSame('open', $result['state']);

$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

public function testGetPullRequestWithInvalidNumber(): void
{
$repositoryName = 'test-get-pull-request-invalid-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');

try {
$this->expectException(\Exception::class);
$this->vcsAdapter->getPullRequest(self::$owner, $repositoryName, 99999);
} finally {
$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
Comment thread
jaysomani marked this conversation as resolved.
Outdated
Comment thread
jaysomani marked this conversation as resolved.
Outdated
}

public function testGenerateCloneCommand(): void
Expand Down Expand Up @@ -505,7 +651,52 @@ public function testGetOwnerName(): void

public function testGetPullRequestFromBranch(): void
{
$this->markTestSkipped('Will be implemented in follow-up PR');
$repositoryName = 'test-get-pr-from-branch-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);

$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');
$this->vcsAdapter->createBranch(self::$owner, $repositoryName, 'my-feature', 'main');
$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'feature.txt', 'content', 'Add feature', 'my-feature');

// Create PR
$pr = $this->vcsAdapter->createPullRequest(
self::$owner,
$repositoryName,
'Feature PR',
'my-feature',
'main'
);

$this->assertArrayHasKey('number', $pr);

// Test getPullRequestFromBranch
$result = $this->vcsAdapter->getPullRequestFromBranch(self::$owner, $repositoryName, 'my-feature');

$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertArrayHasKey('head', $result);

$resultHead = $result['head'] ?? [];
$this->assertSame('my-feature', $resultHead['ref'] ?? '');

$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

public function testGetPullRequestFromBranchNoPR(): void
{
$repositoryName = 'test-get-pr-no-pr-' . \uniqid();
$this->vcsAdapter->createRepository(self::$owner, $repositoryName, false);

$this->vcsAdapter->createFile(self::$owner, $repositoryName, 'README.md', '# Test');
$this->vcsAdapter->createBranch(self::$owner, $repositoryName, 'lonely-branch', 'main');

// Don't create a PR - just test the method
$result = $this->vcsAdapter->getPullRequestFromBranch(self::$owner, $repositoryName, 'lonely-branch');

$this->assertIsArray($result);
$this->assertEmpty($result);

$this->vcsAdapter->deleteRepository(self::$owner, $repositoryName);
}

public function testCreateComment(): void
Expand Down
Loading