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:
Dev
2025-09-11 17:02:12 +03:00
commit 15bbfdcda2
27 changed files with 5727 additions and 0 deletions

518
internal/cmd/pr_test.go Normal file
View 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)
}
}