fj/tests/functional/fixtures.go
sid 7c0dcc8696 test: rewrite functional tests for full CLI coverage
Consolidate all tests into functional_test.go — remove duplicate
new_commands_test.go. Replace SDK-only tests with actual CLI binary
invocations. Add missing coverage for: issue list, issue view,
issue comment, issue create, issue edit title, repo view, repo list,
release view, --json flag on issue list/view and pr list.
All tests now use -R flag consistently.

35 pass, 0 fail, 3 expected skips (pr view/diff need PRs, clone needs auth).
2026-03-23 12:42:24 -06:00

334 lines
8.2 KiB
Go

//go:build functional
// +build functional
package functional
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"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)
}
}
// EnsureTestLabels creates test labels if they don't exist
func (env *TestEnv) EnsureTestLabels() {
labelNames := []string{"bug", "enhancement", "help-wanted"}
for _, name := range labelNames {
labels, _, err := env.Client.ListRepoLabels(env.Owner, env.RepoName, gitea.ListLabelsOptions{})
if err != nil {
env.T.Fatalf("failed to list labels: %v", err)
}
exists := false
for _, label := range labels {
if label.Name == name {
exists = true
break
}
}
if !exists {
color := "#00aabb"
if name == "bug" {
color = "#ff0000"
} else if name == "enhancement" {
color = "#00ff00"
}
_, _, err = env.Client.CreateLabel(env.Owner, env.RepoName, gitea.CreateLabelOption{
Name: name,
Color: color,
})
if err != nil {
env.T.Logf("warning: failed to create label '%s': %v", name, err)
} else {
env.T.Logf("Created test label: %s", name)
}
}
}
}
// GetIssueLabels gets the labels on an issue
func (env *TestEnv) GetIssueLabels(issueNumber int64) ([]*gitea.Label, error) {
labels, _, err := env.Client.GetIssueLabels(env.Owner, env.RepoName, issueNumber, gitea.ListLabelsOptions{})
return labels, err
}
// GetLabelIDs converts label names to label IDs
func (env *TestEnv) GetLabelIDs(labelNames []string) []int64 {
labels, _, err := env.Client.ListRepoLabels(env.Owner, env.RepoName, gitea.ListLabelsOptions{})
if err != nil {
env.T.Fatalf("failed to list labels: %v", err)
}
nameToID := make(map[string]int64)
for _, label := range labels {
nameToID[label.Name] = label.ID
}
var ids []int64
for _, name := range labelNames {
id, exists := nameToID[name]
if !exists {
env.T.Fatalf("label '%s' not found", name)
}
ids = append(ids, id)
}
return ids
}
// CleanupRepo deletes a repository created during testing.
func (env *TestEnv) CleanupRepo(owner, repoName string) {
_, err := env.Client.DeleteRepo(owner, repoName)
if err != nil {
env.T.Logf("warning: failed to delete repo %s/%s: %v", owner, repoName, 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
}
// runCLIWithStdin executes the CLI binary with the given args and pipes input to stdin.
func (env *TestEnv) runCLIWithStdin(input string, args ...string) *CLIResult {
cmd := exec.Command(env.GetBinaryPath(), args...)
cmd.Env = os.Environ()
cmd.Stdin = strings.NewReader(input)
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
}