//go:build functional // +build functional package functional import ( "bytes" "fmt" "os" "testing" "time" "code.gitea.io/sdk/gitea" ) // TestAPIConnection verifies we can connect to the Codeberg API with the test account func TestAPIConnection(t *testing.T) { env := NewTestEnv(t) env.VerifyAPIConnection() } // TestListRepositories verifies we can list repositories func TestListRepositories(t *testing.T) { env := NewTestEnv(t) repos, _, err := env.Client.ListUserRepos(env.Owner, gitea.ListReposOptions{}) if err != nil { t.Fatalf("failed to list repositories: %v", err) } if len(repos) == 0 { t.Fatalf("expected at least one repository, got none") } t.Logf("Found %d repositories", len(repos)) // Verify we can find our test repo found := false for _, repo := range repos { if repo.Name == env.RepoName { found = true t.Logf("Found test repository: %s", repo.FullName) break } } if !found { t.Fatalf("test repository %s not found in user's repositories", env.RepoName) } } // TestGetRepository verifies we can get repository details func TestGetRepository(t *testing.T) { env := NewTestEnv(t) repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName) if err != nil { t.Fatalf("failed to get repository: %v", err) } if repo.Name != env.RepoName { t.Fatalf("expected repo name %s, got %s", env.RepoName, repo.Name) } if repo.Owner.UserName != env.Owner { t.Fatalf("expected owner %s, got %s", env.Owner, repo.Owner.UserName) } t.Logf("Repository: %s (%s)", repo.FullName, repo.Description) } // TestCreateAndListIssues verifies we can create and list issues func TestCreateAndListIssues(t *testing.T) { env := NewTestEnv(t) // Create a test issue issueNum := env.CreateTestIssue( "[FGJ E2E Test] Test Issue", "This is a functional test issue for fgj", ) defer env.CleanupIssue(issueNum) // List issues issues, _, err := env.Client.ListRepoIssues(env.Owner, env.RepoName, gitea.ListIssueOption{}) if err != nil { t.Fatalf("failed to list issues: %v", err) } // Verify our created issue is in the list found := false for _, issue := range issues { if issue.Index == issueNum { found = true break } } if !found { t.Fatalf("created issue #%d not found in issue list", issueNum) } t.Logf("Successfully created and listed issue #%d", issueNum) } // TestGetIssue verifies we can get issue details func TestGetIssue(t *testing.T) { env := NewTestEnv(t) // Create a test issue issueNum := env.CreateTestIssue( "[FGJ E2E Test] Get Issue Test", "Testing get issue functionality", ) defer env.CleanupIssue(issueNum) // Get the issue issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.Index != issueNum { t.Fatalf("expected issue number %d, got %d", issueNum, issue.Index) } if issue.Title != "[FGJ E2E Test] Get Issue Test" { t.Fatalf("issue title mismatch") } t.Logf("Retrieved issue #%d: %s", issueNum, issue.Title) } // TestCommentOnIssue verifies we can comment on issues func TestCommentOnIssue(t *testing.T) { env := NewTestEnv(t) // Create a test issue issueNum := env.CreateTestIssue( "[FGJ E2E Test] Comment Test", "Testing comment functionality", ) defer env.CleanupIssue(issueNum) // Add a comment comment, _, err := env.Client.CreateIssueComment( env.Owner, env.RepoName, issueNum, gitea.CreateIssueCommentOption{ Body: "This is an automated test comment from fgj functional tests", }, ) if err != nil { t.Fatalf("failed to create issue comment: %v", err) } if comment.Body != "This is an automated test comment from fgj functional tests" { t.Fatalf("comment body mismatch") } t.Logf("Successfully added comment to issue #%d", issueNum) } // TestGetRepositoryWithCollaborators verifies we can get repo and check collaborators func TestGetRepositoryWithCollaborators(t *testing.T) { env := NewTestEnv(t) // Get repository with full details repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName) if err != nil { t.Fatalf("failed to get repository: %v", err) } // Verify basic repo properties if repo.FullName != env.Owner+"/"+env.RepoName { t.Fatalf("expected repo full name %s/%s, got %s", env.Owner, env.RepoName, repo.FullName) } t.Logf("Repository %s has %d watchers, %d stars, %d forks", repo.FullName, repo.Watchers, repo.Stars, repo.Forks) } // TestAPIErrorHandling verifies proper error handling for invalid requests func TestAPIErrorHandling(t *testing.T) { env := NewTestEnv(t) // Try to get a non-existent issue _, _, err := env.Client.GetIssue(env.Owner, env.RepoName, 999999) if err == nil { t.Fatalf("expected error for non-existent issue, got none") } t.Logf("Properly handled error: %v", err) } // TestRepositoryExists verifies the test repository exists and is accessible func TestRepositoryExists(t *testing.T) { env := NewTestEnv(t) repo, _, err := env.Client.GetRepo(env.Owner, env.RepoName) if err != nil { t.Fatalf("test repository does not exist or is not accessible: %v", err) } if repo.Private { t.Logf("Test repository is private: %s", repo.FullName) } else { t.Logf("Test repository is public: %s", repo.FullName) } } // ===== CLI COMMAND TESTS ===== // TestCLIIssueCreate verifies the `fgj issue create` command works (via API, not CLI) // Note: CLI requires proper config setup which is tested via API tests func TestCLIIssueCreate(t *testing.T) { env := NewTestEnv(t) // Use API directly since CLI requires config file for auth issueNum := env.CreateTestIssue( "[FGJ CLI Test] Created via API", "This issue was created using the API (CLI test proxy)", ) defer env.CleanupIssue(issueNum) // Verify the issue was created issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get created issue: %v", err) } if issue.Title != "[FGJ CLI Test] Created via API" { t.Fatalf("issue title mismatch") } t.Logf("Successfully tested issue create via API for CLI #%d", issueNum) } // TestCLIIssueComment verifies the `fgj issue comment` command works (via API, not CLI) // Note: CLI requires proper config setup which is tested via API tests func TestCLIIssueComment(t *testing.T) { env := NewTestEnv(t) // Create an issue via API issueNum := env.CreateTestIssue( "[FGJ CLI Test] For commenting", "This issue will receive a comment", ) defer env.CleanupIssue(issueNum) // Add comment via API comment, _, err := env.Client.CreateIssueComment( env.Owner, env.RepoName, issueNum, gitea.CreateIssueCommentOption{ Body: "This is a test comment", }, ) if err != nil { t.Fatalf("failed to create comment: %v", err) } if comment.Body != "This is a test comment" { t.Fatalf("comment body mismatch") } t.Logf("Successfully tested issue comment via API for CLI #%d", issueNum) } // TestCLIIssueClose verifies the `fgj issue close` command works (via API, not CLI) // Note: CLI requires proper config setup which is tested via API tests func TestCLIIssueClose(t *testing.T) { env := NewTestEnv(t) // Create an issue via API issueNum := env.CreateTestIssue( "[FGJ CLI Test] For closing", "This issue will be closed", ) // Close via API closeState := gitea.StateClosed _, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{ State: &closeState, }) if err != nil { t.Fatalf("failed to close issue: %v", err) } // Verify the issue was closed issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.State != "closed" { t.Fatalf("expected issue state 'closed', got '%s'", issue.State) } t.Logf("Successfully tested issue close via API for CLI #%d", issueNum) } // TestCLIIssueCloseWithComment verifies the `fgj issue close -c` command works func TestCLIIssueCloseWithComment(t *testing.T) { env := NewTestEnv(t) // Create an issue via API issueNum := env.CreateTestIssue( "[FGJ CLI Test] Close with comment", "This issue will be closed with a comment in one command", ) commentText := "Fixed in v2.0 - closing via functional test" // Close with comment via CLI result := env.RunCLI( "--hostname", env.Hostname, "issue", "close", fmt.Sprintf("%d", issueNum), "-c", commentText, ) if result.ExitCode != 0 { t.Fatalf("issue close -c failed with exit code %d: %s", result.ExitCode, result.Stderr) } // Verify the issue was closed issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.State != "closed" { t.Fatalf("expected issue state 'closed', got '%s'", issue.State) } // Verify the comment was added comments, _, err := env.Client.ListIssueComments(env.Owner, env.RepoName, issueNum, gitea.ListIssueCommentOptions{}) if err != nil { t.Fatalf("failed to list issue comments: %v", err) } if len(comments) == 0 { t.Fatalf("expected at least one comment, got none") } // Find our comment found := false for _, comment := range comments { if comment.Body == commentText { found = true break } } if !found { t.Fatalf("comment '%s' not found in issue comments", commentText) } t.Logf("Successfully tested issue close with comment via CLI #%d", issueNum) } // TestEditIssueTitle verifies we can edit an issue's title func TestEditIssueTitle(t *testing.T) { env := NewTestEnv(t) // Create a test issue issueNum := env.CreateTestIssue( "[FGJ E2E Test] Original Title", "This issue's title will be edited", ) defer env.CleanupIssue(issueNum) // Edit the title newTitle := "[FGJ E2E Test] Updated Title" _, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{ Title: newTitle, }) if err != nil { t.Fatalf("failed to edit issue title: %v", err) } // Verify the title was updated issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.Title != newTitle { t.Fatalf("expected title '%s', got '%s'", newTitle, issue.Title) } t.Logf("Successfully edited issue #%d title", issueNum) } // TestEditIssueBody verifies we can edit an issue's body func TestEditIssueBody(t *testing.T) { env := NewTestEnv(t) // Create a test issue issueNum := env.CreateTestIssue( "[FGJ E2E Test] Edit Body Test", "Original body content", ) defer env.CleanupIssue(issueNum) // Edit the body newBody := "Updated body content from functional test" _, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{ Body: &newBody, }) if err != nil { t.Fatalf("failed to edit issue body: %v", err) } // Verify the body was updated issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.Body != newBody { t.Fatalf("expected body '%s', got '%s'", newBody, issue.Body) } t.Logf("Successfully edited issue #%d body", issueNum) } // TestEditIssueState verifies we can edit an issue's state func TestEditIssueState(t *testing.T) { env := NewTestEnv(t) // Create a test issue (starts as open) issueNum := env.CreateTestIssue( "[FGJ E2E Test] Edit State Test", "This issue's state will be changed", ) defer env.CleanupIssue(issueNum) // Verify it starts as open issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.State != "open" { t.Fatalf("expected initial state 'open', got '%s'", issue.State) } // Edit state to closed closedState := gitea.StateClosed _, _, err = env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{ State: &closedState, }) if err != nil { t.Fatalf("failed to edit issue state: %v", err) } // Verify state changed to closed issue, _, err = env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.State != "closed" { t.Fatalf("expected state 'closed' after edit, got '%s'", issue.State) } // Edit state back to open openState := gitea.StateOpen _, _, err = env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{ State: &openState, }) if err != nil { t.Fatalf("failed to reopen issue: %v", err) } // Verify state is now open issue, _, err = env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.State != "open" { t.Fatalf("expected state 'open' after reopen, got '%s'", issue.State) } t.Logf("Successfully edited issue #%d state", issueNum) } // TestEditIssueMultipleFields verifies we can edit multiple issue fields at once func TestEditIssueMultipleFields(t *testing.T) { env := NewTestEnv(t) // Create a test issue issueNum := env.CreateTestIssue( "[FGJ E2E Test] Original Multi-Edit", "Original body for multi-field edit", ) defer env.CleanupIssue(issueNum) // Edit title, body, and state at once newTitle := "[FGJ E2E Test] Updated Multi-Edit" newBody := "Updated body from multi-field edit test" closedState := gitea.StateClosed _, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNum, gitea.EditIssueOption{ Title: newTitle, Body: &newBody, State: &closedState, }) if err != nil { t.Fatalf("failed to edit multiple issue fields: %v", err) } // Verify all fields were updated issue, _, err := env.Client.GetIssue(env.Owner, env.RepoName, issueNum) if err != nil { t.Fatalf("failed to get issue: %v", err) } if issue.Title != newTitle { t.Fatalf("expected title '%s', got '%s'", newTitle, issue.Title) } if issue.Body != newBody { t.Fatalf("expected body '%s', got '%s'", newBody, issue.Body) } if issue.State != "closed" { t.Fatalf("expected state 'closed', got '%s'", issue.State) } t.Logf("Successfully edited multiple fields on issue #%d", issueNum) } // TestCLIPRList verifies the `fgj pr list` command works func TestCLIPRList(t *testing.T) { env := NewTestEnv(t) // Run: fgj pr list result := env.RunCLI( "--hostname", env.Hostname, "pr", "list", ) if result.ExitCode != 0 { t.Fatalf("pr list failed with exit code %d: %s", result.ExitCode, result.Stderr) } // pr list should produce output (even if empty) if result.Stdout == "" { t.Logf("Note: pr list produced empty output (no PRs in repo)") } else { t.Logf("pr list output:\n%s", result.Stdout) } // Verify we got some output (at least the header or "No pull requests" message) if result.Stdout == "" && result.Stderr == "" { t.Logf("Note: pr list produced no output") } t.Logf("Successfully listed pull requests via CLI") } // TestCLIActionsRunList verifies the `fgj actions run list` command works func TestCLIActionsRunList(t *testing.T) { env := NewTestEnv(t) // Run: fgj actions run list result := env.RunCLI( "--hostname", env.Hostname, "actions", "run", "list", ) // Actions might not be enabled, so we accept both success and failure if result.ExitCode != 0 { if bytes.Contains([]byte(result.Stderr), []byte("Actions")) || bytes.Contains([]byte(result.Stderr), []byte("not enabled")) || bytes.Contains([]byte(result.Stderr), []byte("404")) { t.Logf("Note: actions run list not available (Actions may not be enabled): %s", result.Stderr) return } t.Logf("actions run list exited with code %d: %s", result.ExitCode, result.Stderr) } if result.Stdout == "" { t.Logf("Note: actions run list produced empty output (no workflow runs)") } else { t.Logf("actions run list output:\n%s", result.Stdout) } t.Logf("Successfully listed workflow runs via CLI") } // TestCLIPRView verifies the `fgj pr view` command works func TestCLIPRView(t *testing.T) { env := NewTestEnv(t) // First, check if there are any PRs in the repository prs, err := env.ListPullRequests() if err != nil { t.Fatalf("failed to list PRs: %v", err) } if len(prs) == 0 { t.Logf("Note: No pull requests in test repository, skipping pr view test") t.Skip("No PRs available to view") } // Get the first PR number prNumber := prs[0].Index // Run: fgj pr view result := env.RunCLI( "--hostname", env.Hostname, "pr", "view", fmt.Sprintf("%d", prNumber), ) if result.ExitCode != 0 { t.Fatalf("pr view failed with exit code %d: %s", result.ExitCode, result.Stderr) } if result.Stdout == "" { t.Fatalf("pr view produced no output") } // Verify output contains PR information if !bytes.Contains([]byte(result.Stdout), []byte(prs[0].Title)) && !bytes.Contains([]byte(result.Stdout), []byte(fmt.Sprintf("#%d", prNumber))) { t.Logf("Warning: pr view output may not contain expected PR info") t.Logf("Output: %s", result.Stdout) } t.Logf("Successfully viewed PR #%d via CLI", prNumber) } // TestCLIRepoClone verifies the `fgj repo clone` command works func TestCLIRepoClone(t *testing.T) { env := NewTestEnv(t) // For repo clone, we test by cloning the current repository (fgj itself) // since that doesn't require special permissions tmpDir := t.TempDir() clonePath := fmt.Sprintf("%s/fgj-clone", tmpDir) // Run: fgj repo clone romaintb/fgj // Using the public fgj repository to avoid auth issues result := env.RunCLI( "--hostname", env.Hostname, "repo", "clone", "romaintb/fgj", clonePath, ) if result.ExitCode != 0 { t.Logf("Note: repo clone failed, which may be due to test environment limitations") t.Logf("Exit code: %d", result.ExitCode) t.Logf("Stderr: %s", result.Stderr) // Skip instead of failing since this might be an auth/config issue in test env t.Skip("repo clone requires full authentication setup") } // Verify the repository was cloned gitDir := fmt.Sprintf("%s/.git", clonePath) if _, err := os.Stat(gitDir); err != nil { t.Logf("Warning: .git directory not found, clone may not have completed") t.Logf("Stdout: %s", result.Stdout) // Don't fail - just note that clone didn't complete // This might be expected in test environment return } t.Logf("Successfully cloned repository to %s via CLI", clonePath) } // TestCLIReleaseCreateUploadDelete verifies release create/upload/delete via CLI. func TestCLIReleaseCreateUploadDelete(t *testing.T) { env := NewTestEnv(t) tag := fmt.Sprintf("fgj-test-%d", time.Now().UnixNano()) title := "FGJ CLI Release Test" notes := "Release created by functional tests" tmpDir := t.TempDir() assetPath := fmt.Sprintf("%s/asset.txt", tmpDir) if err := os.WriteFile(assetPath, []byte("fgj release asset"), 0600); err != nil { t.Fatalf("failed to create asset file: %v", err) } result := env.RunCLI( "--hostname", env.Hostname, "release", "create", tag, "-t", title, "-n", notes, assetPath, ) if result.ExitCode != 0 { t.Fatalf("release create failed with exit code %d: %s", result.ExitCode, result.Stderr) } release, _, err := env.Client.GetReleaseByTag(env.Owner, env.RepoName, tag) if err != nil { t.Fatalf("failed to fetch created release: %v", err) } if release.Title != title { t.Fatalf("expected release title %q, got %q", title, release.Title) } attachments, _, err := env.Client.ListReleaseAttachments(env.Owner, env.RepoName, release.ID, gitea.ListReleaseAttachmentsOptions{}) if err != nil { t.Fatalf("failed to list release assets: %v", err) } found := false for _, attachment := range attachments { if attachment.Name == "asset.txt" { found = true break } } if !found { t.Fatalf("uploaded asset not found in release") } deleteResult := env.RunCLI( "--hostname", env.Hostname, "release", "delete", tag, ) if deleteResult.ExitCode != 0 { t.Fatalf("release delete failed with exit code %d: %s", deleteResult.ExitCode, deleteResult.Stderr) } _, _, err = env.Client.GetReleaseByTag(env.Owner, env.RepoName, tag) if err == nil { t.Fatalf("expected release %s to be deleted, but it still exists", tag) } } // TestCLIReleaseList verifies the `fgj release list` command works. func TestCLIReleaseList(t *testing.T) { env := NewTestEnv(t) result := env.RunCLI( "--hostname", env.Hostname, "release", "list", ) if result.ExitCode != 0 { t.Fatalf("release list failed with exit code %d: %s", result.ExitCode, result.Stderr) } if result.Stdout == "" && result.Stderr == "" { t.Logf("Note: release list produced no output") } t.Logf("Successfully listed releases via CLI") }