Skip to content
Open
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
6 changes: 5 additions & 1 deletion command/issues/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,11 @@ func (opts *IssuesOptions) resolveIssues(ctx context.Context, client *deepsource
case ab.PRNumber > 0:
opts.PRNumber = ab.PRNumber
opts.CommitOid = ab.CommitOid
issuesList, err = client.GetPRIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, ab.PRNumber, prFilters)
if ab.Fallback {
issuesList, err = client.GetRunIssuesFlat(ctx, ab.CommitOid, serverFilters)
} else {
issuesList, err = client.GetPRIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider, ab.PRNumber, prFilters)
}
case ab.UseRepo:
issuesList, err = client.GetIssues(ctx, remote.Owner, remote.RepoName, remote.VCSProvider)
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"repository": {
"analysisRuns": {
"edges": [
{
"node": {
"runUid": "run-uid-running-01",
"commitOid": "831701c7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"branchName": "feature/new-auth",
"status": "RUNNING",
"createdAt": "2025-03-12T14:00:00Z",
"finishedAt": null,
"updatedAt": "2025-03-12T14:01:00Z",
"summary": {
"occurrencesIntroduced": 0,
"occurrencesResolved": 0,
"occurrencesSuppressed": 0
},
"reportCard": null
}
},
{
"node": {
"runUid": "run-uid-completed-01",
"commitOid": "862df9f2e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8",
"branchName": "feature/new-auth",
"status": "SUCCESS",
"createdAt": "2025-03-12T12:00:00Z",
"finishedAt": "2025-03-12T12:05:00Z",
"updatedAt": "2025-03-12T12:05:00Z",
"summary": {
"occurrencesIntroduced": 2,
"occurrencesResolved": 0,
"occurrencesSuppressed": 0
},
"reportCard": null
}
}
],
"pageInfo": {
"hasNextPage": false,
"endCursor": null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"repository": {
"analysisRuns": {
"edges": [
{
"node": {
"runUid": "run-uid-running-01",
"commitOid": "831701c7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"branchName": "feature/new-auth",
"status": "RUNNING",
"createdAt": "2025-03-12T14:00:00Z",
"finishedAt": null,
"updatedAt": "2025-03-12T14:01:00Z",
"summary": {
"occurrencesIntroduced": 0,
"occurrencesResolved": 0,
"occurrencesSuppressed": 0
},
"reportCard": null
}
}
],
"pageInfo": {
"hasNextPage": false,
"endCursor": null
}
}
}
}
98 changes: 98 additions & 0 deletions command/issues/tests/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,104 @@ func TestIssuesMultipleFilters(t *testing.T) {
}
}

// TestIssuesPRFallbackUsesRunIssues verifies that when auto-branch resolution
// detects a PR AND the current run is in-progress (fallback), the code fetches
// issues via GetRunIssuesFlat (commit-scoped) instead of GetPRIssues.
// Regression test for ticket #6884175.
func TestIssuesPRFallbackUsesRunIssues(t *testing.T) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function exceeds recommended cyclomatic complexity, reduces maintainability


The function TestIssuesPRFallbackUsesRunIssues has cyclomatic complexity exceeding the recommended threshold, which makes it harder to understand, maintain, and test. High complexity leads to more independent execution paths increasing potential bugs.

Break down TestIssuesPRFallbackUsesRunIssues into smaller functions or refactor complex logic into helper functions to simplify the control flow and improve maintainability and testability.

cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")

// Load golden files for the multi-step mock.
prFoundData, err := os.ReadFile(goldenPath("get_pr_by_branch_found_response.json"))
if err != nil {
t.Fatalf("failed to read PR golden file: %v", err)
}
runsFirstData, err := os.ReadFile(goldenPath("get_analysis_runs_pr_fallback_first_response.json"))
if err != nil {
t.Fatalf("failed to read first runs golden file: %v", err)
}
runsCompletedData, err := os.ReadFile(goldenPath("get_analysis_runs_pr_fallback_completed_response.json"))
if err != nil {
t.Fatalf("failed to read completed runs golden file: %v", err)
}
commitScopeData, err := os.ReadFile(goldenPath("commit_scope_response.json"))
if err != nil {
t.Fatalf("failed to read commit scope golden file: %v", err)
}

mock := graphqlclient.NewMockClient()
analysisRunsCalls := 0
mock.QueryFunc = func(_ context.Context, query string, _ map[string]any, result any) error {
switch {
case strings.Contains(query, "pullRequests("):
return json.Unmarshal(prFoundData, result)
case strings.Contains(query, "query GetAnalysisRuns("):
analysisRunsCalls++
if analysisRunsCalls == 1 {
// First call (ResolveLatestRunForBranch, limit=1): RUNNING run
return json.Unmarshal(runsFirstData, result)
}
// Second call (ResolveLatestCompletedRun, limit=10): RUNNING + SUCCESS
return json.Unmarshal(runsCompletedData, result)
case strings.Contains(query, "checks {"):
// GetRunIssuesFlat for the fallback commit
return json.Unmarshal(commitScopeData, result)
default:
t.Fatalf("unexpected query: %s", query)
return nil
}
}
client := deepsource.NewWithGraphQLClient(mock)

var buf bytes.Buffer
deps := &cmddeps.Deps{
Client: client,
ConfigMgr: cfgMgr,
Stdout: &buf,
BranchNameFunc: func() (string, error) {
return "feature/new-auth", nil
},
HasUnpushedCommitsFunc: func() bool { return false },
HasUncommittedChangesFunc: func() bool { return false },
}

cmd := issuesCmd.NewCmdIssuesWithDeps(deps)
cmd.SetArgs([]string{"--repo", "gh/testowner/testrepo", "--output", "json"})

if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error: %v", err)
}

got := buf.String()

// Verify the fallback info message is present.
if !strings.Contains(got, "Analysis is running on commit") {
t.Errorf("expected fallback info message, got: %q", got)
}

// Extract the JSON array from the output (after the info message line).
// The JSON starts at the first '[' character.
jsonStart := strings.Index(got, "[")
if jsonStart < 0 {
t.Fatalf("no JSON array found in output: %s", got)
}

var issues []map[string]any
if err := json.Unmarshal([]byte(got[jsonStart:]), &issues); err != nil {
t.Fatalf("failed to parse JSON output: %v\nraw output: %s", err, got)
}

// Must NOT be empty — this was the bug.
if len(issues) == 0 {
t.Fatal("expected non-empty issues from fallback commit, got empty array")
}

// Verify we got the expected issues from commit_scope_response.json.
if issues[0]["issue_code"] != "GO-W1007" {
t.Errorf("expected first issue GO-W1007, got %v", issues[0]["issue_code"])
}
}

func TestIssuesRunInProgress(t *testing.T) {
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
mock := testutil.MockQueryFunc(t, map[string]string{
Expand Down
Loading