feat: initial commit of Git Automation CLI
- Add comprehensive Git workflow automation tools - Include branch management utilities - Add commit helpers with conventional commit support - Implement GitHub integration for PR management - Add configuration management system - Include comprehensive test coverage - Add professional documentation and examples
This commit is contained in:
518
internal/cmd/pr_test.go
Normal file
518
internal/cmd/pr_test.go
Normal file
@@ -0,0 +1,518 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/iwasforcedtobehere/git-automation-cli/internal/github"
|
||||
"github.com/iwasforcedtobehere/git-automation-cli/internal/validation"
|
||||
)
|
||||
|
||||
func TestPromptForBody(t *testing.T) {
|
||||
// Save original stdin
|
||||
originalStdin := os.Stdin
|
||||
defer func() {
|
||||
os.Stdin = originalStdin
|
||||
}()
|
||||
|
||||
// Create a pipe to simulate user input
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
|
||||
// Write test input
|
||||
go func() {
|
||||
w.WriteString("This is the first line\n")
|
||||
w.WriteString("This is the second line\n")
|
||||
w.WriteString("\n") // First empty line
|
||||
w.WriteString("\n") // Second empty line to finish
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
// Capture output
|
||||
oldStdout := os.Stdout
|
||||
stdoutR, stdoutW, _ := os.Pipe()
|
||||
os.Stdout = stdoutW
|
||||
|
||||
// Call the function
|
||||
body := promptForBody()
|
||||
|
||||
// Restore stdout
|
||||
stdoutW.Close()
|
||||
os.Stdout = oldStdout
|
||||
out, _ := io.ReadAll(stdoutR)
|
||||
|
||||
// Check result
|
||||
expectedBody := "This is the first line\nThis is the second line"
|
||||
if body != expectedBody {
|
||||
t.Errorf("promptForBody() = %q, want %q", body, expectedBody)
|
||||
}
|
||||
|
||||
// Check that prompt was displayed
|
||||
output := string(out)
|
||||
if !strings.Contains(output, "Enter pull request body") {
|
||||
t.Errorf("Expected prompt to be displayed, got %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadLine(t *testing.T) {
|
||||
// Save original stdin
|
||||
originalStdin := os.Stdin
|
||||
defer func() {
|
||||
os.Stdin = originalStdin
|
||||
}()
|
||||
|
||||
// Create a pipe to simulate user input
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
|
||||
// Write test input
|
||||
go func() {
|
||||
w.WriteString("test line\n")
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
// Call the function
|
||||
line := readLine()
|
||||
|
||||
// Check result
|
||||
expectedLine := "test line"
|
||||
if line != expectedLine {
|
||||
t.Errorf("readLine() = %q, want %q", line, expectedLine)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "Valid number",
|
||||
input: "123",
|
||||
expected: 123,
|
||||
},
|
||||
{
|
||||
name: "Invalid number",
|
||||
input: "abc",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
input: "",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "Negative number",
|
||||
input: "-456",
|
||||
expected: -456,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := parseInt(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("parseInt(%q) = %d, want %d", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MockGitService is a mock implementation of the git service
|
||||
type MockGitService struct {
|
||||
currentBranchFunc func(ctx context.Context) (string, error)
|
||||
getRemoteURLFunc func(ctx context.Context, remote string) (string, error)
|
||||
}
|
||||
|
||||
func (m *MockGitService) CurrentBranch(ctx context.Context) (string, error) {
|
||||
if m.currentBranchFunc != nil {
|
||||
return m.currentBranchFunc(ctx)
|
||||
}
|
||||
return "feature/test", nil
|
||||
}
|
||||
|
||||
func (m *MockGitService) GetRemoteURL(ctx context.Context, remote string) (string, error) {
|
||||
if m.getRemoteURLFunc != nil {
|
||||
return m.getRemoteURLFunc(ctx, remote)
|
||||
}
|
||||
return "https://github.com/owner/repo.git", nil
|
||||
}
|
||||
|
||||
// MockGitHubService is a mock implementation of the GitHub service
|
||||
type MockGitHubService struct {
|
||||
newClientFunc func(ctx context.Context) (*github.Client, error)
|
||||
isGitHubURLFunc func(url string) bool
|
||||
parseGitHubURLFunc func(url string) (string, string, error)
|
||||
createPullRequest func(c *github.Client, ctx context.Context, pr *github.PullRequestRequest) (*github.PullRequest, error)
|
||||
getPullRequests func(c *github.Client, ctx context.Context, state string) ([]github.PullRequest, error)
|
||||
mergePullRequest func(c *github.Client, ctx context.Context, number int, mergeRequest *github.MergePullRequestRequest) (*github.MergePullRequestResponse, error)
|
||||
}
|
||||
|
||||
func (m *MockGitHubService) NewClient(ctx context.Context) (*github.Client, error) {
|
||||
if m.newClientFunc != nil {
|
||||
return m.newClientFunc(ctx)
|
||||
}
|
||||
return &github.Client{}, nil
|
||||
}
|
||||
|
||||
func (m *MockGitHubService) IsGitHubURL(url string) bool {
|
||||
if m.isGitHubURLFunc != nil {
|
||||
return m.isGitHubURLFunc(url)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *MockGitHubService) ParseGitHubURL(url string) (string, string, error) {
|
||||
if m.parseGitHubURLFunc != nil {
|
||||
return m.parseGitHubURLFunc(url)
|
||||
}
|
||||
return "owner", "repo", nil
|
||||
}
|
||||
|
||||
// MockValidationService is a mock implementation of the validation service
|
||||
type MockValidationService struct {
|
||||
validateGitRepositoryFunc func(ctx context.Context) *validation.ValidationResult
|
||||
}
|
||||
|
||||
func (m *MockValidationService) ValidateGitRepository(ctx context.Context) *validation.ValidationResult {
|
||||
if m.validateGitRepositoryFunc != nil {
|
||||
return m.validateGitRepositoryFunc(ctx)
|
||||
}
|
||||
return &validation.ValidationResult{IsValid: true}
|
||||
}
|
||||
|
||||
func TestPRCreateCmd(t *testing.T) {
|
||||
// Create mock services
|
||||
mockGitService := &MockGitService{}
|
||||
mockGitHubService := &MockGitHubService{}
|
||||
mockValidationService := &MockValidationService{}
|
||||
|
||||
// Set up mock functions
|
||||
mockGitHubService.createPullRequest = func(c *github.Client, ctx context.Context, pr *github.PullRequestRequest) (*github.PullRequest, error) {
|
||||
return &github.PullRequest{
|
||||
ID: 1,
|
||||
Number: 123,
|
||||
Title: pr.Title,
|
||||
Body: pr.Body,
|
||||
HTMLURL: "https://github.com/owner/repo/pull/123",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Temporarily redirect stdout to capture output
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Create a command
|
||||
cmd := &cobra.Command{
|
||||
Use: "test",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Use mock services instead of real ones
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Validate Git repository
|
||||
if validationResult := mockValidationService.ValidateGitRepository(ctx); !validationResult.IsValid {
|
||||
return fmt.Errorf(validationResult.GetErrors())
|
||||
}
|
||||
|
||||
// Get current branch
|
||||
currentBranch, err := mockGitService.CurrentBranch(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current branch: %w", err)
|
||||
}
|
||||
|
||||
// Get base branch
|
||||
base := "main"
|
||||
|
||||
// Check if current branch is the same as base branch
|
||||
if currentBranch == base {
|
||||
return fmt.Errorf("cannot create pull request from %s branch to itself", base)
|
||||
}
|
||||
|
||||
// Create GitHub client
|
||||
client, err := mockGitHubService.NewClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create GitHub client: %w", err)
|
||||
}
|
||||
|
||||
// Get remote URL
|
||||
remoteURL, err := mockGitService.GetRemoteURL(ctx, "origin")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get remote URL: %w", err)
|
||||
}
|
||||
|
||||
// Parse GitHub URL
|
||||
if !mockGitHubService.IsGitHubURL(remoteURL) {
|
||||
return fmt.Errorf("remote URL is not a GitHub URL: %s", remoteURL)
|
||||
}
|
||||
|
||||
owner, repo, err := mockGitHubService.ParseGitHubURL(remoteURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse GitHub URL: %w", err)
|
||||
}
|
||||
|
||||
// Set repository on client
|
||||
client.SetRepository(owner, repo)
|
||||
|
||||
// Create pull request
|
||||
pr := &github.PullRequestRequest{
|
||||
Title: strings.Join(args, " "),
|
||||
Body: "Test body",
|
||||
Head: currentBranch,
|
||||
Base: base,
|
||||
Draft: false,
|
||||
}
|
||||
|
||||
pullRequest, err := mockGitHubService.createPullRequest(client, ctx, pr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create pull request: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Created pull request: %s\n", pullRequest.HTMLURL)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Set args
|
||||
cmd.SetArgs([]string{"Test PR"})
|
||||
|
||||
// Execute command
|
||||
err := cmd.Execute()
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
out, _ := io.ReadAll(r)
|
||||
|
||||
// Check error
|
||||
if err != nil {
|
||||
t.Errorf("Execute() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check output
|
||||
output := string(out)
|
||||
expected := "Created pull request: https://github.com/owner/repo/pull/123\n"
|
||||
if output != expected {
|
||||
t.Errorf("Execute() output = %q, want %q", output, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRListCmd(t *testing.T) {
|
||||
// Create mock services
|
||||
mockGitService := &MockGitService{}
|
||||
mockGitHubService := &MockGitHubService{}
|
||||
mockValidationService := &MockValidationService{}
|
||||
|
||||
// Set up mock functions
|
||||
mockGitHubService.getPullRequests = func(c *github.Client, ctx context.Context, state string) ([]github.PullRequest, error) {
|
||||
return []github.PullRequest{
|
||||
{
|
||||
ID: 1,
|
||||
Number: 123,
|
||||
Title: "Test PR 1",
|
||||
User: github.User{Login: "user1"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Number: 124,
|
||||
Title: "Test PR 2",
|
||||
User: github.User{Login: "user2"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Temporarily redirect stdout to capture output
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Create a command
|
||||
cmd := &cobra.Command{
|
||||
Use: "test",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Use mock services instead of real ones
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get state flag
|
||||
state := "open"
|
||||
|
||||
// Validate Git repository
|
||||
if validationResult := mockValidationService.ValidateGitRepository(ctx); !validationResult.IsValid {
|
||||
return fmt.Errorf(validationResult.GetErrors())
|
||||
}
|
||||
|
||||
// Create GitHub client
|
||||
client, err := mockGitHubService.NewClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create GitHub client: %w", err)
|
||||
}
|
||||
|
||||
// Get remote URL
|
||||
remoteURL, err := mockGitService.GetRemoteURL(ctx, "origin")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get remote URL: %w", err)
|
||||
}
|
||||
|
||||
// Parse GitHub URL
|
||||
if !mockGitHubService.IsGitHubURL(remoteURL) {
|
||||
return fmt.Errorf("remote URL is not a GitHub URL: %s", remoteURL)
|
||||
}
|
||||
|
||||
owner, repo, err := mockGitHubService.ParseGitHubURL(remoteURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse GitHub URL: %w", err)
|
||||
}
|
||||
|
||||
// Set repository on client
|
||||
client.SetRepository(owner, repo)
|
||||
|
||||
// Get pull requests
|
||||
pullRequests, err := mockGitHubService.getPullRequests(client, ctx, state)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get pull requests: %w", err)
|
||||
}
|
||||
|
||||
// Print pull requests
|
||||
if len(pullRequests) == 0 {
|
||||
fmt.Printf("No %s pull requests found\n", state)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("%s pull requests:\n", strings.Title(state))
|
||||
for _, pr := range pullRequests {
|
||||
fmt.Printf("#%d: %s (%s)\n", pr.Number, pr.Title, pr.User.Login)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Execute command
|
||||
err := cmd.Execute()
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
out, _ := io.ReadAll(r)
|
||||
|
||||
// Check error
|
||||
if err != nil {
|
||||
t.Errorf("Execute() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check output
|
||||
output := string(out)
|
||||
expected := "Open pull requests:\n#123: Test PR 1 (user1)\n#124: Test PR 2 (user2)\n"
|
||||
if output != expected {
|
||||
t.Errorf("Execute() output = %q, want %q", output, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRMergeCmd(t *testing.T) {
|
||||
// Create mock services
|
||||
mockGitService := &MockGitService{}
|
||||
mockGitHubService := &MockGitHubService{}
|
||||
mockValidationService := &MockValidationService{}
|
||||
|
||||
// Set up mock functions
|
||||
mockGitHubService.mergePullRequest = func(c *github.Client, ctx context.Context, number int, mergeRequest *github.MergePullRequestRequest) (*github.MergePullRequestResponse, error) {
|
||||
return &github.MergePullRequestResponse{
|
||||
SHA: "abc123",
|
||||
Merged: true,
|
||||
Message: "Pull Request successfully merged",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Temporarily redirect stdout to capture output
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Create a command
|
||||
cmd := &cobra.Command{
|
||||
Use: "test",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Use mock services instead of real ones
|
||||
ctx := cmd.Context()
|
||||
number := args[0]
|
||||
|
||||
// Validate Git repository
|
||||
if validationResult := mockValidationService.ValidateGitRepository(ctx); !validationResult.IsValid {
|
||||
return fmt.Errorf(validationResult.GetErrors())
|
||||
}
|
||||
|
||||
// Create GitHub client
|
||||
client, err := mockGitHubService.NewClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create GitHub client: %w", err)
|
||||
}
|
||||
|
||||
// Get remote URL
|
||||
remoteURL, err := mockGitService.GetRemoteURL(ctx, "origin")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get remote URL: %w", err)
|
||||
}
|
||||
|
||||
// Parse GitHub URL
|
||||
if !mockGitHubService.IsGitHubURL(remoteURL) {
|
||||
return fmt.Errorf("remote URL is not a GitHub URL: %s", remoteURL)
|
||||
}
|
||||
|
||||
owner, repo, err := mockGitHubService.ParseGitHubURL(remoteURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse GitHub URL: %w", err)
|
||||
}
|
||||
|
||||
// Set repository on client
|
||||
client.SetRepository(owner, repo)
|
||||
|
||||
// Get merge method
|
||||
method := "merge"
|
||||
|
||||
// Merge pull request
|
||||
mergeRequest := &github.MergePullRequestRequest{
|
||||
MergeMethod: method,
|
||||
}
|
||||
|
||||
mergeResponse, err := mockGitHubService.mergePullRequest(client, ctx, parseInt(number), mergeRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to merge pull request: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Merged pull request: %s\n", mergeResponse.SHA)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Set args
|
||||
cmd.SetArgs([]string{"123"})
|
||||
|
||||
// Execute command
|
||||
err := cmd.Execute()
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
out, _ := io.ReadAll(r)
|
||||
|
||||
// Check error
|
||||
if err != nil {
|
||||
t.Errorf("Execute() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check output
|
||||
output := string(out)
|
||||
expected := "Merged pull request: abc123\n"
|
||||
if output != expected {
|
||||
t.Errorf("Execute() output = %q, want %q", output, expected)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user