//go:build functional // +build functional package functional import ( "bytes" "fmt" "os" "os/exec" "path/filepath" "testing" "code.gitea.io/sdk/gitea" ) // TestEnv holds the functional test environment type TestEnv struct { Token string Hostname string Owner string RepoName string Client *gitea.Client T *testing.T } // NewTestEnv creates a new functional test environment from environment variables func NewTestEnv(t *testing.T) *TestEnv { token := os.Getenv("CODEBERG_TEST_TOKEN") if token == "" { t.Skip("CODEBERG_TEST_TOKEN not set, skipping functional tests") } hostname := os.Getenv("CODEBERG_TEST_HOSTNAME") if hostname == "" { hostname = "codeberg.org" } owner := os.Getenv("CODEBERG_TEST_OWNER") if owner == "" { t.Fatal("CODEBERG_TEST_OWNER environment variable not set") } repoName := os.Getenv("CODEBERG_TEST_REPO") if repoName == "" { t.Fatal("CODEBERG_TEST_REPO environment variable not set") } // Create Gitea client client, err := gitea.NewClient( fmt.Sprintf("https://%s", hostname), gitea.SetToken(token), ) if err != nil { t.Fatalf("failed to create Gitea client: %v", err) } env := &TestEnv{ Token: token, Hostname: hostname, Owner: owner, RepoName: repoName, Client: client, T: t, } // Verify authentication by getting user info user, _, err := client.GetMyUserInfo() if err != nil { t.Fatalf("authentication failed: %v", err) } t.Logf("Authenticated as user: %s on %s", user.UserName, hostname) return env } // CreateTestIssue creates a test issue and returns its number func (env *TestEnv) CreateTestIssue(title, body string) int64 { opts := gitea.CreateIssueOption{ Title: title, Body: body, } issue, _, err := env.Client.CreateIssue(env.Owner, env.RepoName, opts) if err != nil { env.T.Fatalf("failed to create test issue: %v", err) } env.T.Logf("Created test issue #%d", issue.Index) return issue.Index } // CleanupIssue closes and deletes a test issue func (env *TestEnv) CleanupIssue(issueNumber int64) { closeState := gitea.StateClosed _, _, err := env.Client.EditIssue(env.Owner, env.RepoName, issueNumber, gitea.EditIssueOption{ State: &closeState, }) if err != nil { env.T.Logf("warning: failed to close issue #%d: %v", issueNumber, err) } } // CreateTestPullRequest creates a test PR (requires a branch to exist) // Note: Creating PRs requires multiple branches, so this is a placeholder for future use func (env *TestEnv) CreateTestPullRequest(title, body, head, base string) int64 { opts := gitea.CreatePullRequestOption{ Head: head, Base: base, Title: title, Body: body, } pr, _, err := env.Client.CreatePullRequest(env.Owner, env.RepoName, opts) if err != nil { env.T.Fatalf("failed to create test PR: %v", err) } env.T.Logf("Created test PR #%d", pr.Index) return pr.Index } // ListPullRequests gets all PRs in the repository func (env *TestEnv) ListPullRequests() ([]*gitea.PullRequest, error) { prs, _, err := env.Client.ListRepoPullRequests(env.Owner, env.RepoName, gitea.ListPullRequestsOptions{}) return prs, err } // GetPullRequest gets a specific PR func (env *TestEnv) GetPullRequest(prNumber int64) (*gitea.PullRequest, error) { pr, _, err := env.Client.GetPullRequest(env.Owner, env.RepoName, prNumber) return pr, err } // CleanupPullRequest closes a test PR func (env *TestEnv) CleanupPullRequest(prNumber int64) { closed := gitea.StateClosed _, _, err := env.Client.EditPullRequest(env.Owner, env.RepoName, prNumber, gitea.EditPullRequestOption{ State: &closed, }) if err != nil { env.T.Logf("warning: failed to close PR #%d: %v", prNumber, err) } } // VerifyAPIConnection verifies the test environment can connect to the API func (env *TestEnv) VerifyAPIConnection() { _, _, err := env.Client.GetMyUserInfo() if err != nil { env.T.Fatalf("failed to verify API connection: %v", err) } } // GetBinaryPath returns the path to the built fgj binary func (env *TestEnv) GetBinaryPath() string { binaryPath := os.Getenv("FGJ_BINARY_PATH") if binaryPath == "" { // Look for the binary in common locations candidates := []string{ "./bin/fgj", "bin/fgj", "/home/romain/work/fgj/bin/fgj", } for _, candidate := range candidates { if _, err := os.Stat(candidate); err == nil { // Convert to absolute path if abs, err := filepath.Abs(candidate); err == nil { return abs } return candidate } } // If no binary found, return default (will error when executed) binaryPath = "./bin/fgj" } return binaryPath } // CLIResult holds the result of a CLI command execution type CLIResult struct { Stdout string Stderr string ExitCode int } // RunCLI executes a CLI command and returns stdout, stderr, and exit code func (env *TestEnv) RunCLI(args ...string) *CLIResult { cmd := exec.Command(env.GetBinaryPath(), args...) // Set up environment with test credentials cmd.Env = os.Environ() var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() exitCode := 0 if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { exitCode = exitErr.ExitCode() } } result := &CLIResult{ Stdout: stdout.String(), Stderr: stderr.String(), ExitCode: exitCode, } env.T.Logf("Command: %s %v", env.GetBinaryPath(), args) env.T.Logf("Exit code: %d", exitCode) if stdout.Len() > 0 { env.T.Logf("Stdout:\n%s", result.Stdout) } if stderr.Len() > 0 { env.T.Logf("Stderr:\n%s", result.Stderr) } return result }