Skip to content

Commit d1c368f

Browse files
Add test coverage and fix unused receiver for DeepSource issues
- Convert startPATLoginFlow from method to standalone function (unused receiver) - Add monorepo hint and repo-not-found error path tests for issues command - Add PR issues query test covering node-to-Issue mapping and nil analyzer - Add corrupt state file test for update ApplyUpdate error path
1 parent 04178d8 commit d1c368f

5 files changed

Lines changed: 209 additions & 2 deletions

File tree

command/auth/login/login.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (opts *LoginOptions) Run(cmd *cobra.Command) (err error) {
114114
}
115115

116116
if opts.PAT != "" {
117-
return opts.startPATLoginFlow(svc, cfg, opts.PAT)
117+
return startPATLoginFlow(svc, cfg, opts.PAT)
118118
}
119119

120120
if !opts.TokenExpired && cfg.Token != "" {

command/auth/login/pat_login_flow.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/fatih/color"
1010
)
1111

12-
func (lo *LoginOptions) startPATLoginFlow(svc *authsvc.Service, cfg *config.CLIConfig, token string) error {
12+
func startPATLoginFlow(svc *authsvc.Service, cfg *config.CLIConfig, token string) error {
1313
cfg.Token = token
1414

1515
viewer, err := svc.GetViewer(context.Background(), cfg)

command/issues/tests/issues_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"fmt"
78
"os"
89
"path/filepath"
910
"runtime"
@@ -1041,4 +1042,58 @@ func TestIssuesLimitCapAfterPagination(t *testing.T) {
10411042
}
10421043
}
10431044

1045+
func TestIssuesMonorepoHintError(t *testing.T) {
1046+
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
1047+
1048+
mock := graphqlclient.NewMockClient()
1049+
mock.QueryFunc = func(_ context.Context, _ string, _ map[string]any, _ any) error {
1050+
return fmt.Errorf("This repository is a monorepo. Please specify a sub-project")
1051+
}
1052+
client := deepsource.NewWithGraphQLClient(mock)
1053+
1054+
var buf bytes.Buffer
1055+
deps := &cmddeps.Deps{
1056+
Client: client,
1057+
ConfigMgr: cfgMgr,
1058+
Stdout: &buf,
1059+
}
1060+
1061+
cmd := issuesCmd.NewCmdIssuesWithDeps(deps)
1062+
cmd.SetArgs([]string{"--repo", "gh/testowner/testrepo", "--commit", "abc123f", "--output", "json"})
1063+
1064+
err := cmd.Execute()
1065+
if err == nil {
1066+
t.Fatal("expected error for monorepo hint")
1067+
}
1068+
if !strings.Contains(err.Error(), "Use --repo to specify a sub-project") {
1069+
t.Errorf("expected monorepo hint in error, got: %s", err.Error())
1070+
}
1071+
}
10441072

1073+
func TestIssuesRepoNotFoundError(t *testing.T) {
1074+
cfgMgr := testutil.CreateTestConfigManager(t, "test-token", "deepsource.com", "test@example.com")
1075+
1076+
mock := graphqlclient.NewMockClient()
1077+
mock.QueryFunc = func(_ context.Context, _ string, _ map[string]any, _ any) error {
1078+
return fmt.Errorf("Repository does not exist on DeepSource")
1079+
}
1080+
client := deepsource.NewWithGraphQLClient(mock)
1081+
1082+
var buf bytes.Buffer
1083+
deps := &cmddeps.Deps{
1084+
Client: client,
1085+
ConfigMgr: cfgMgr,
1086+
Stdout: &buf,
1087+
}
1088+
1089+
cmd := issuesCmd.NewCmdIssuesWithDeps(deps)
1090+
cmd.SetArgs([]string{"--repo", "gh/testowner/testrepo", "--commit", "abc123f", "--output", "json"})
1091+
1092+
err := cmd.Execute()
1093+
if err == nil {
1094+
t.Fatal("expected error for repository not found")
1095+
}
1096+
if !strings.Contains(err.Error(), "Repository does not exist") {
1097+
t.Errorf("expected repo-not-found error, got: %s", err.Error())
1098+
}
1099+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package issues
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"testing"
8+
9+
"github.com/deepsourcelabs/cli/deepsource/graphqlclient"
10+
)
11+
12+
func TestPRIssuesList_Do(t *testing.T) {
13+
respJSON := `{
14+
"repository": {
15+
"pullRequest": {
16+
"issues": {
17+
"edges": [
18+
{
19+
"node": {
20+
"source": "static",
21+
"path": "cmd/deepsource/main.go",
22+
"beginLine": 42,
23+
"endLine": 42,
24+
"title": "Unchecked error return value",
25+
"shortcode": "GO-W1007",
26+
"category": "BUG_RISK",
27+
"severity": "MAJOR",
28+
"explanation": "Return value not checked",
29+
"issue": {
30+
"analyzer": {
31+
"name": "Go",
32+
"shortcode": "go"
33+
}
34+
}
35+
}
36+
},
37+
{
38+
"node": {
39+
"source": "ai",
40+
"path": "internal/vcs/remotes.go",
41+
"beginLine": 87,
42+
"endLine": 91,
43+
"title": "HTTP request with user-controlled URL",
44+
"shortcode": "GO-S1010",
45+
"category": "SECURITY",
46+
"severity": "MAJOR",
47+
"explanation": "SSRF risk",
48+
"issue": null
49+
}
50+
}
51+
],
52+
"pageInfo": {
53+
"hasNextPage": false,
54+
"endCursor": null
55+
}
56+
}
57+
}
58+
}
59+
}`
60+
61+
mock := graphqlclient.NewMockClient()
62+
mock.QueryFunc = func(_ context.Context, _ string, _ map[string]any, result any) error {
63+
return json.Unmarshal([]byte(respJSON), result)
64+
}
65+
66+
req := NewPRIssuesListRequest(mock, PRIssuesListParams{
67+
Owner: "testowner",
68+
RepoName: "testrepo",
69+
Provider: "GITHUB",
70+
PRNumber: 42,
71+
})
72+
73+
issues, err := req.Do(context.Background())
74+
if err != nil {
75+
t.Fatalf("unexpected error: %v", err)
76+
}
77+
78+
if len(issues) != 2 {
79+
t.Fatalf("expected 2 issues, got %d", len(issues))
80+
}
81+
82+
// First issue: has analyzer
83+
if issues[0].IssueCode != "GO-W1007" {
84+
t.Errorf("expected issue code GO-W1007, got %s", issues[0].IssueCode)
85+
}
86+
if issues[0].IssueText != "Unchecked error return value" {
87+
t.Errorf("expected title 'Unchecked error return value', got %s", issues[0].IssueText)
88+
}
89+
if issues[0].IssueCategory != "BUG_RISK" {
90+
t.Errorf("expected category BUG_RISK, got %s", issues[0].IssueCategory)
91+
}
92+
if issues[0].Analyzer.Name != "Go" {
93+
t.Errorf("expected analyzer name 'Go', got %s", issues[0].Analyzer.Name)
94+
}
95+
if issues[0].Location.Path != "cmd/deepsource/main.go" {
96+
t.Errorf("expected path 'cmd/deepsource/main.go', got %s", issues[0].Location.Path)
97+
}
98+
99+
// Second issue: nil analyzer (AI-detected)
100+
if issues[1].IssueCode != "GO-S1010" {
101+
t.Errorf("expected issue code GO-S1010, got %s", issues[1].IssueCode)
102+
}
103+
if issues[1].Analyzer.Name != "" {
104+
t.Errorf("expected empty analyzer name for nil issue, got %s", issues[1].Analyzer.Name)
105+
}
106+
if issues[1].IssueSource != "ai" {
107+
t.Errorf("expected source 'ai', got %s", issues[1].IssueSource)
108+
}
109+
}
110+
111+
func TestPRIssuesList_QueryError(t *testing.T) {
112+
mock := graphqlclient.NewMockClient()
113+
mock.QueryFunc = func(_ context.Context, _ string, _ map[string]any, _ any) error {
114+
return fmt.Errorf("network error")
115+
}
116+
117+
req := NewPRIssuesListRequest(mock, PRIssuesListParams{
118+
Owner: "testowner",
119+
RepoName: "testrepo",
120+
Provider: "GITHUB",
121+
PRNumber: 42,
122+
})
123+
124+
_, err := req.Do(context.Background())
125+
if err == nil {
126+
t.Fatal("expected error from query")
127+
}
128+
}

internal/update/updater_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os"
1414
"path/filepath"
1515
"runtime"
16+
"strings"
1617
"testing"
1718
"time"
1819

@@ -620,6 +621,29 @@ func TestPlatformKey(t *testing.T) {
620621
}
621622
}
622623

624+
func TestApplyUpdate_CorruptStateFile(t *testing.T) {
625+
tmpHome := t.TempDir()
626+
t.Setenv("HOME", tmpHome)
627+
628+
// Write corrupt JSON to the state file path
629+
stateDir := filepath.Join(tmpHome, buildinfo.ConfigDirName)
630+
if err := os.MkdirAll(stateDir, 0o750); err != nil {
631+
t.Fatal(err)
632+
}
633+
if err := os.WriteFile(filepath.Join(stateDir, "update.json"), []byte("not valid json{{{"), 0o644); err != nil {
634+
t.Fatal(err)
635+
}
636+
637+
client := &http.Client{Timeout: 1 * time.Second}
638+
_, err := ApplyUpdate(client)
639+
if err == nil {
640+
t.Fatal("expected error for corrupt state file")
641+
}
642+
if !strings.Contains(err.Error(), "parsing update state") {
643+
t.Errorf("expected parsing error, got: %v", err)
644+
}
645+
}
646+
623647
// helpers
624648

625649
func createTarGz(t *testing.T, name string, content []byte) []byte {

0 commit comments

Comments
 (0)