Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions internal/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
type ClientInterface interface {
CreateIssue(issue *models.Issue, repo string) (*models.IssueResponse, error)
GetCurrentRepository() (string, error)
GetRateLimit() (*models.RateLimitResponse, error)
}

// Client provides GitHub API functionality
Expand Down Expand Up @@ -94,3 +95,16 @@ func (c *Client) GetCurrentRepository() (string, error) {

return fmt.Sprintf("%s/%s", info.Owner.Login, info.Name), nil
}

// GetRateLimit gets the current GitHub API rate limit information
func (c *Client) GetRateLimit() (*models.RateLimitResponse, error) {
response := &models.RateLimitResponse{}

// Send GET request to rate_limit endpoint
err := c.client.Get("rate_limit", response)
if err != nil {
return nil, fmt.Errorf("failed to get rate limit: %v", err)
}

return response, nil
}
73 changes: 73 additions & 0 deletions internal/github/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type MockClient struct {
CreateIssueFunc func(issue *models.Issue, repo string) (*models.IssueResponse, error)
GetCurrentRepoFunc func() (string, error)
GetRateLimitFunc func() (*models.RateLimitResponse, error)
CreatedIssues []*models.Issue
GetCurrentRepoCounter int
}
Expand All @@ -32,6 +33,20 @@ func (m *MockClient) GetCurrentRepository() (string, error) {
return "mock/repo", nil
}

// GetRateLimit implements the ClientInterface for testing
func (m *MockClient) GetRateLimit() (*models.RateLimitResponse, error) {
if m.GetRateLimitFunc != nil {
return m.GetRateLimitFunc()
}
return &models.RateLimitResponse{
Rate: models.RateLimit{
Limit: 5000,
Remaining: 4999,
Reset: 1234567890,
},
}, nil
}

func TestMockClient(t *testing.T) {
// Create mock client
mockClient := &MockClient{}
Expand Down Expand Up @@ -125,4 +140,62 @@ func TestClientInterface(t *testing.T) {
if repo != "mock/repo" {
t.Errorf("Expected repo 'mock/repo', got '%s'", repo)
}

// Test GetRateLimit interface method
rateLimit, err := client.GetRateLimit()
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}

if rateLimit.Rate.Limit != 5000 {
t.Errorf("Expected rate limit 5000, got %d", rateLimit.Rate.Limit)
}
}

// TestRateLimit tests the GetRateLimit functionality
func TestRateLimit(t *testing.T) {
// Create mock client
mockClient := &MockClient{}

// Test GetRateLimit with default values
rateLimit, err := mockClient.GetRateLimit()
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

// Check default values
if rateLimit.Rate.Limit != 5000 {
t.Errorf("Expected rate limit 5000, got %d", rateLimit.Rate.Limit)
}
if rateLimit.Rate.Remaining != 4999 {
t.Errorf("Expected remaining 4999, got %d", rateLimit.Rate.Remaining)
}
if rateLimit.Rate.Reset != 1234567890 {
t.Errorf("Expected reset 1234567890, got %d", rateLimit.Rate.Reset)
}

// Test with custom function
mockClient = &MockClient{
GetRateLimitFunc: func() (*models.RateLimitResponse, error) {
return &models.RateLimitResponse{
Rate: models.RateLimit{
Limit: 60,
Remaining: 30,
Reset: 1234567900,
},
}, nil
},
}

rateLimit, err = mockClient.GetRateLimit()
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

if rateLimit.Rate.Limit != 60 {
t.Errorf("Expected custom limit 60, got %d", rateLimit.Rate.Limit)
}
if rateLimit.Rate.Remaining != 30 {
t.Errorf("Expected custom remaining 30, got %d", rateLimit.Rate.Remaining)
}
}
34 changes: 34 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/ntsk/gh-issue-bulk-create/internal/csv"
"github.com/ntsk/gh-issue-bulk-create/internal/github"
Expand Down Expand Up @@ -165,6 +166,39 @@ func main() {

fmt.Printf("Target repository: %s\n", targetRepo)

// Check rate limit before creating issues
if !opts.dryRun {
rateLimit, err := githubClient.GetRateLimit()
if err != nil {
fmt.Printf("Warning: Failed to check rate limit: %v\n", err)
} else {
fmt.Printf("Current rate limit: %d remaining out of %d\n",
rateLimit.Rate.Remaining, rateLimit.Rate.Limit)

resetTime := time.Unix(int64(rateLimit.Rate.Reset), 0)
fmt.Printf("Reset time: %s (in %s)\n",
resetTime.Format(time.RFC3339),
time.Until(resetTime).Round(time.Minute))

issueCount := len(dataMaps)
if rateLimit.Rate.Remaining < issueCount {
fmt.Printf("Warning: Not enough rate limit remaining (%d) for %d issues\n",
rateLimit.Rate.Remaining, issueCount)
fmt.Printf("You may hit the rate limit during execution.\n")
fmt.Printf("Do you want to continue? (y/N): ")
var response string
fmt.Scanln(&response)
response = strings.ToLower(strings.TrimSpace(response))
if response != "y" && response != "yes" {
fmt.Println("Aborted.")
os.Exit(0)
}
} else {
fmt.Printf("Rate limit looks sufficient for %d issues\n", issueCount)
}
}
}

// Process template and create issues
for _, data := range dataMaps {
// Render template with data
Expand Down
12 changes: 12 additions & 0 deletions pkg/models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,15 @@ type IssueResponse struct {
Number int `json:"number"`
URL string `json:"html_url"`
}

// RateLimit represents GitHub API rate limit information
type RateLimit struct {
Limit int `json:"limit"`
Remaining int `json:"remaining"`
Reset int `json:"reset"`
}

// RateLimitResponse represents GitHub API rate limit response
type RateLimitResponse struct {
Rate RateLimit `json:"rate"`
}