Skip to content

Commit fcf3a86

Browse files
committed
WIP special case failures when batch changes is disabled
1 parent 0ec60de commit fcf3a86

File tree

4 files changed

+203
-1
lines changed

4 files changed

+203
-1
lines changed

internal/api/errors_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,72 @@ func TestGraphQLError_Code(t *testing.T) {
117117
}
118118

119119
}
120+
121+
func TestGraphQLError_Message(t *testing.T) {
122+
for name, tc := range map[string]struct {
123+
in string
124+
want string
125+
wantErr bool
126+
}{
127+
"invalid message": {
128+
in: `{
129+
"errors": [
130+
{
131+
"message": 42
132+
}
133+
],
134+
"data": null
135+
}`,
136+
wantErr: true,
137+
},
138+
"no message": {
139+
in: `{
140+
"errors": [
141+
{
142+
"extensions": {
143+
"code": "ErrBatchChangesUnlicensed"
144+
}
145+
}
146+
],
147+
"data": null
148+
}`,
149+
want: "",
150+
},
151+
"valid message": {
152+
in: `{
153+
"errors": [
154+
{
155+
"message": "Cannot query field \"batchChanges\" on type \"Query\"."
156+
}
157+
],
158+
"data": null
159+
}`,
160+
want: `Cannot query field "batchChanges" on type "Query".`,
161+
},
162+
} {
163+
t.Run(name, func(t *testing.T) {
164+
var result rawResult
165+
if err := json.Unmarshal([]byte(tc.in), &result); err != nil {
166+
t.Fatal(err)
167+
}
168+
if ne := len(result.Errors); ne != 1 {
169+
t.Fatalf("unexpected number of GraphQL errors (this test can only handle one!): %d", ne)
170+
}
171+
172+
ge := &GraphQlError{result.Errors[0]}
173+
have, err := ge.Message()
174+
if tc.wantErr {
175+
if err == nil {
176+
t.Errorf("unexpected nil error")
177+
}
178+
} else {
179+
if err != nil {
180+
t.Errorf("unexpected error: %+v", err)
181+
}
182+
if have != tc.want {
183+
t.Errorf("unexpected message: have=%q want=%q", have, tc.want)
184+
}
185+
}
186+
})
187+
}
188+
}

internal/batches/errors.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,22 @@ func (e IgnoredRepoSet) Append(repo *graphql.Repository) {
7777
func (e IgnoredRepoSet) HasIgnored() bool {
7878
return len(e) > 0
7979
}
80+
81+
// BatchChangesDisabledError indicates that Batch Changes is unavailable on the
82+
// target instance, typically because the GraphQL schema does not expose the
83+
// relevant fields when the feature is disabled.
84+
type BatchChangesDisabledError struct {
85+
cause error
86+
}
87+
88+
func NewBatchChangesDisabledError(cause error) *BatchChangesDisabledError {
89+
return &BatchChangesDisabledError{cause: cause}
90+
}
91+
92+
func (e *BatchChangesDisabledError) Error() string {
93+
return "Batch Changes is disabled on this Sourcegraph instance. Ask your site admin to enable Batch Changes before running 'src batch' commands."
94+
}
95+
96+
func (e *BatchChangesDisabledError) Unwrap() error {
97+
return e.cause
98+
}

internal/batches/service/service.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ func (svc *Service) getSourcegraphVersionAndMaxChangesetsCount(ctx context.Conte
6767
}
6868

6969
ok, err := svc.client.NewQuery(getInstanceInfo).Do(ctx, &result)
70-
if err != nil || !ok {
70+
if err != nil {
71+
return "", 0, translateBatchChangesDisabledError(err)
72+
}
73+
if !ok {
7174
return "", 0, err
7275
}
7376

@@ -79,6 +82,9 @@ func (svc *Service) getSourcegraphVersionAndMaxChangesetsCount(ctx context.Conte
7982
func (svc *Service) DetermineLicenseAndFeatureFlags(ctx context.Context, skipErrors bool) (*batches.LicenseRestrictions, *batches.FeatureFlags, error) {
8083
version, mc, err := svc.getSourcegraphVersionAndMaxChangesetsCount(ctx)
8184
if err != nil {
85+
if _, ok := err.(*batches.BatchChangesDisabledError); ok {
86+
return nil, nil, err
87+
}
8288
return nil, nil, errors.Wrap(err, "failed to query Sourcegraph version and license info for instance")
8389
}
8490

@@ -91,6 +97,55 @@ func (svc *Service) DetermineLicenseAndFeatureFlags(ctx context.Context, skipErr
9197

9298
}
9399

100+
func translateBatchChangesDisabledError(err error) error {
101+
gqlErrs, ok := err.(api.GraphQlErrors)
102+
if !ok || len(gqlErrs) == 0 {
103+
return err
104+
}
105+
106+
sawBatchChangesField := false
107+
108+
for _, gqlErr := range gqlErrs {
109+
message, messageErr := gqlErr.Message()
110+
if messageErr != nil {
111+
return err
112+
}
113+
114+
field, ok := parseMissingQueryField(message)
115+
if !ok {
116+
return err
117+
}
118+
119+
switch field {
120+
case "batchChanges":
121+
sawBatchChangesField = true
122+
case "maxUnlicensedChangesets":
123+
default:
124+
return err
125+
}
126+
}
127+
128+
if !sawBatchChangesField {
129+
return err
130+
}
131+
132+
return batches.NewBatchChangesDisabledError(err)
133+
}
134+
135+
func parseMissingQueryField(message string) (string, bool) {
136+
const (
137+
prefix = `Cannot query field "`
138+
suffix = `" on type "Query".`
139+
)
140+
141+
if !strings.HasPrefix(message, prefix) || !strings.HasSuffix(message, suffix) {
142+
return "", false
143+
}
144+
145+
field := strings.TrimSuffix(strings.TrimPrefix(message, prefix), suffix)
146+
return field, field != ""
147+
}
148+
94149
const applyBatchChangeMutation = `
95150
mutation ApplyBatchChange($batchSpec: ID!) {
96151
applyBatchChange(batchSpec: $batchSpec) {

internal/batches/service/service_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package service
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
7+
"net/http"
8+
"net/http/httptest"
9+
"net/url"
610
"os"
711
"path/filepath"
812
"strconv"
@@ -16,11 +20,66 @@ import (
1620

1721
batcheslib "github.com/sourcegraph/sourcegraph/lib/batches"
1822

23+
"github.com/sourcegraph/src-cli/internal/api"
24+
"github.com/sourcegraph/src-cli/internal/batches"
1925
"github.com/sourcegraph/src-cli/internal/batches/docker"
2026
"github.com/sourcegraph/src-cli/internal/batches/graphql"
2127
"github.com/sourcegraph/src-cli/internal/batches/mock"
2228
)
2329

30+
func TestService_DetermineLicenseAndFeatureFlags_BatchChangesDisabled(t *testing.T) {
31+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32+
require.Equal(t, http.MethodPost, r.Method)
33+
require.Equal(t, "/.api/graphql", r.URL.Path)
34+
w.Header().Set("Content-Type", "application/json")
35+
_, _ = w.Write([]byte(`{
36+
"errors": [
37+
{"message": "Cannot query field \"maxUnlicensedChangesets\" on type \"Query\"."},
38+
{"message": "Cannot query field \"batchChanges\" on type \"Query\"."}
39+
],
40+
"data": {}
41+
}`))
42+
}))
43+
defer ts.Close()
44+
45+
endpointURL, err := url.Parse(ts.URL)
46+
require.NoError(t, err)
47+
48+
var clientOutput bytes.Buffer
49+
svc := New(&Opts{Client: api.NewClient(api.ClientOpts{EndpointURL: endpointURL, Out: &clientOutput})})
50+
51+
_, _, err = svc.DetermineLicenseAndFeatureFlags(context.Background(), false)
52+
require.Error(t, err)
53+
54+
var disabledErr *batches.BatchChangesDisabledError
55+
require.ErrorAs(t, err, &disabledErr)
56+
assert.Equal(t, "Batch Changes is disabled on this Sourcegraph instance. Ask your site admin to enable Batch Changes before running 'src batch' commands.", err.Error())
57+
}
58+
59+
func TestService_DetermineLicenseAndFeatureFlags_DoesNotMisclassifySchemaErrors(t *testing.T) {
60+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61+
w.Header().Set("Content-Type", "application/json")
62+
_, _ = w.Write([]byte(`{
63+
"errors": [
64+
{"message": "Cannot query field \"maxUnlicensedChangesets\" on type \"Query\"."}
65+
],
66+
"data": {}
67+
}`))
68+
}))
69+
defer ts.Close()
70+
71+
endpointURL, err := url.Parse(ts.URL)
72+
require.NoError(t, err)
73+
74+
var clientOutput bytes.Buffer
75+
svc := New(&Opts{Client: api.NewClient(api.ClientOpts{EndpointURL: endpointURL, Out: &clientOutput})})
76+
77+
_, _, err = svc.DetermineLicenseAndFeatureFlags(context.Background(), false)
78+
require.Error(t, err)
79+
assert.Contains(t, err.Error(), "failed to query Sourcegraph version and license info for instance")
80+
assert.NotContains(t, err.Error(), "Batch Changes is disabled on this Sourcegraph instance")
81+
}
82+
2483
func TestService_ValidateChangesetSpecs(t *testing.T) {
2584
repo1 := &graphql.Repository{ID: "repo-graphql-id-1", Name: "github.com/sourcegraph/src-cli"}
2685
repo2 := &graphql.Repository{ID: "repo-graphql-id-2", Name: "github.com/sourcegraph/sourcegraph"}

0 commit comments

Comments
 (0)