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
30 changes: 19 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func main() {
verbose = flag.Bool("verbose", false, "Show verbose logging from libraries")
excludeOrgs = flag.String("exclude-orgs", "", "Comma-separated list of orgs to exclude")
includeStale = flag.Bool("include-stale", false, "Include PRs that haven't been modified in 90 days")
user = flag.String("user", "", "View PRs for specified user instead of authenticated user")
)
flag.Parse()

Expand Down Expand Up @@ -229,14 +230,21 @@ func main() {
}
logger.Print("INFO: Successfully retrieved GitHub token")

username, err := currentUser(ctx, token, logger, httpClient)
if err != nil {
logger.Printf("ERROR: Failed to get current user: %v", err)
fmt.Fprint(os.Stderr, "error: failed to identify github user\n")
cancel()
return
// Determine the username to use - either specified via --user or the authenticated user
var username string
if *user != "" {
username = *user
logger.Printf("INFO: Using specified user: %s", username)
} else {
username, err = currentUser(ctx, token, logger, httpClient)
if err != nil {
logger.Printf("ERROR: Failed to get current user: %v", err)
fmt.Fprint(os.Stderr, "error: failed to identify github user\n")
cancel()
return
}
logger.Printf("INFO: Authenticated as user: %s", username)
}
logger.Printf("INFO: Authenticated as user: %s", username)

// Set up turn client
var turnClient *turn.Client
Expand Down Expand Up @@ -809,9 +817,9 @@ type displayConfig struct {
httpClient *http.Client
turnClient *turn.Client
lastDisplayHash *string
excludedOrgs []string
token string
username string
excludedOrgs []string
blockingOnly bool
verbose bool
includeStale bool
Expand All @@ -820,13 +828,13 @@ type displayConfig struct {

// watchConfig holds configuration for watch mode.
type watchConfig struct {
logger *log.Logger
httpClient *http.Client
turnClient *turn.Client
excludedOrgs []string
logger *log.Logger
org string
token string
username string
org string
excludedOrgs []string
interval time.Duration
blockingOnly bool
notifyMode bool
Expand Down
166 changes: 166 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,169 @@ func TestCategorizePRs(t *testing.T) {
}
}
}

func TestGeneratePRDisplayBlockedFlag(t *testing.T) {
username := "alice"

// Create test PRs with blocking/non-blocking scenarios
incomingBlocked := PR{
Number: 1,
Title: "Incoming blocked PR",
HTMLURL: "https://github.com/org/repo/pull/1",
User: User{Login: "bob"},
RequestedReviewers: []User{{Login: "alice"}}, // alice is requested reviewer
}

incomingNotBlocked := PR{
Number: 2,
Title: "Incoming non-blocked PR",
HTMLURL: "https://github.com/org/repo/pull/2",
User: User{Login: "charlie"},
RequestedReviewers: []User{{Login: "dave"}}, // alice is not requested reviewer
}

outgoingBlocked := PR{
Number: 3,
Title: "Outgoing blocked PR",
HTMLURL: "https://github.com/org/repo/pull/3",
User: User{Login: "alice"}, // alice is author
RequestedReviewers: []User{{Login: "alice"}}, // alice is requested reviewer (perhaps due to turn server logic)
}

outgoingNotBlocked := PR{
Number: 4,
Title: "Outgoing non-blocked PR",
HTMLURL: "https://github.com/org/repo/pull/4",
User: User{Login: "alice"}, // alice is author
RequestedReviewers: []User{{Login: "eve"}}, // alice is not requested reviewer
}

allPRs := []PR{incomingBlocked, incomingNotBlocked, outgoingBlocked, outgoingNotBlocked}

tests := []struct {
name string
prs []PR
blockingOnly bool
expectIncoming bool
expectOutgoing bool
expectIncomingCount int
expectOutgoingCount int
}{
{
name: "normal mode shows all PRs",
prs: allPRs,
blockingOnly: false,
expectIncoming: true,
expectOutgoing: true,
expectIncomingCount: 2, // both incoming PRs
expectOutgoingCount: 2, // both outgoing PRs
},
{
name: "blocked mode shows only blocked PRs",
prs: allPRs,
blockingOnly: true,
expectIncoming: true, // has blocked incoming
expectOutgoing: true, // has blocked outgoing
expectIncomingCount: 1, // only blocked incoming
expectOutgoingCount: 1, // only blocked outgoing
},
{
name: "blocked mode with only incoming blocked",
prs: []PR{incomingBlocked, incomingNotBlocked, outgoingNotBlocked},
blockingOnly: true,
expectIncoming: true, // has blocked incoming
expectOutgoing: false, // no blocked outgoing
expectIncomingCount: 1, // only blocked incoming
expectOutgoingCount: 0, // no outgoing shown
},
{
name: "blocked mode with only outgoing blocked",
prs: []PR{incomingNotBlocked, outgoingBlocked, outgoingNotBlocked},
blockingOnly: true,
expectIncoming: false, // no blocked incoming
expectOutgoing: true, // has blocked outgoing
expectIncomingCount: 0, // no incoming shown
expectOutgoingCount: 1, // only blocked outgoing
},
{
name: "blocked mode with no blocked PRs",
prs: []PR{incomingNotBlocked, outgoingNotBlocked},
blockingOnly: true,
expectIncoming: false, // no blocked incoming
expectOutgoing: false, // no blocked outgoing
expectIncomingCount: 0, // no incoming shown
expectOutgoingCount: 0, // no outgoing shown
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := generatePRDisplay(tt.prs, username, tt.blockingOnly, false, true, nil)

// Debug output for failing test
if tt.name == "blocked mode with only outgoing blocked" {
t.Logf("Debug output: %q", output)
}

// For the case with no blocked PRs, expect empty output
if !tt.expectIncoming && !tt.expectOutgoing {
if strings.TrimSpace(output) != "" {
t.Errorf("Expected empty output for no blocked PRs, got: %q", output)
}
return
}

// Check for incoming section presence
hasIncomingSection := strings.Contains(output, "incoming -")
if hasIncomingSection != tt.expectIncoming {
t.Errorf("Expected incoming section: %v, got: %v", tt.expectIncoming, hasIncomingSection)
}

// Check for outgoing section presence
hasOutgoingSection := strings.Contains(output, "outgoing -")
if hasOutgoingSection != tt.expectOutgoing {
t.Errorf("Expected outgoing section: %v, got: %v", tt.expectOutgoing, hasOutgoingSection)
}

// Count actual PRs shown in each section
if tt.expectIncoming {
incomingLines := 0
lines := strings.Split(output, "\n")
incomingStarted := false
for _, line := range lines {
if strings.Contains(line, "incoming -") {
incomingStarted = true
continue
}
if strings.Contains(line, "outgoing -") {
break
}
if incomingStarted && (strings.HasPrefix(line, "• ") || strings.HasPrefix(line, " ")) {
incomingLines++
}
}
if incomingLines != tt.expectIncomingCount {
t.Errorf("Expected %d incoming PRs shown, got %d", tt.expectIncomingCount, incomingLines)
}
}

if tt.expectOutgoing {
outgoingLines := 0
lines := strings.Split(output, "\n")
outgoingStarted := false
for _, line := range lines {
if strings.Contains(line, "outgoing -") {
outgoingStarted = true
continue
}
if outgoingStarted && (strings.HasPrefix(line, "• ") || strings.HasPrefix(line, " ")) {
outgoingLines++
}
}
if outgoingLines != tt.expectOutgoingCount {
t.Errorf("Expected %d outgoing PRs shown, got %d", tt.expectOutgoingCount, outgoingLines)
}
}
})
}
}
Loading