Skip to content

Commit 34e01e6

Browse files
authored
fix: handle custom test error interpretation (#128)
1 parent d41a2bd commit 34e01e6

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/pkg/errors v0.9.1
1313
github.com/puzpuzpuz/xsync v1.5.2
1414
github.com/rs/zerolog v1.32.0
15+
github.com/snyk/error-catalog-golang-public v0.0.0-20251008132755-b542bb643649
1516
github.com/stretchr/testify v1.10.0
1617
golang.org/x/net v0.35.0
1718
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
165165
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
166166
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
167167
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
168+
github.com/snyk/error-catalog-golang-public v0.0.0-20251008132755-b542bb643649 h1:kS6bSbjvfMTc8vqIZzHXzTHKh4kLKt27m0tsJ8T3WQc=
169+
github.com/snyk/error-catalog-golang-public v0.0.0-20251008132755-b542bb643649/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4=
168170
github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg=
169171
github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc=
170172
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=

internal/analysis/analysis.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
_ "embed"
2323
"encoding/json"
24+
goerrors "errors"
2425
"fmt"
2526
"io"
2627
"net/http"
@@ -29,7 +30,7 @@ import (
2930

3031
"github.com/google/uuid"
3132
openapi_types "github.com/oapi-codegen/runtime/types"
32-
"github.com/pkg/errors"
33+
errors "github.com/pkg/errors"
3334
"github.com/rs/zerolog"
3435

3536
"github.com/snyk/code-client-go/bundle"
@@ -40,6 +41,7 @@ import (
4041
"github.com/snyk/code-client-go/observability"
4142
"github.com/snyk/code-client-go/sarif"
4243
"github.com/snyk/code-client-go/scan"
44+
"github.com/snyk/error-catalog-golang-public/cli"
4345
)
4446

4547
//go:generate go tool github.com/golang/mock/mockgen -destination=mocks/analysis.go -source=analysis.go -package mocks
@@ -352,6 +354,9 @@ func (a *analysisOrchestrator) retrieveTestURL(ctx context.Context, client *test
352354
}
353355

354356
return components, true, nil
357+
case string(testModels.Error):
358+
testError := parseTestError(parsedResponse, method)
359+
return nil, false, testError
355360
default:
356361
return nil, false, fmt.Errorf("unexpected test status \"%s\"", stateDiscriminator)
357362
}
@@ -360,6 +365,40 @@ func (a *analysisOrchestrator) retrieveTestURL(ctx context.Context, client *test
360365
}
361366
}
362367

368+
func parseTestError(parsedResponse *testApi.GetTestResultResponse, method string) error {
369+
errorResponse, stateErrorStateError := parsedResponse.ApplicationvndApiJSON200.Data.Attributes.AsTestErrorState()
370+
371+
if stateErrorStateError != nil {
372+
return stateErrorStateError
373+
}
374+
375+
if errorResponse.Errors == nil {
376+
return fmt.Errorf("%s: test error state has no errors", method)
377+
}
378+
379+
var testError error
380+
for _, error := range *errorResponse.Errors {
381+
// since the error is only partially defined, we to create an existing generic error and fill it with the available information
382+
tmp := cli.NewGeneralCLIFailureError(error.Message)
383+
tmp.Level = "error"
384+
tmp.ErrorCode = error.ErrorCode
385+
tmp.Title = error.Title
386+
tmp.StatusCode = parsedResponse.StatusCode()
387+
tmp.Classification = error.Classification
388+
389+
if error.InfoUrl != nil {
390+
tmp.Type = *error.InfoUrl
391+
tmp.Links = []string{}
392+
}
393+
testError = goerrors.Join(testError, tmp)
394+
}
395+
396+
if testError == nil {
397+
testError = fmt.Errorf("%s: test error state has no errors", method)
398+
}
399+
return testError
400+
}
401+
363402
func (a *analysisOrchestrator) retrieveTestComponents(ctx context.Context, client *testApi.Client, org uuid.UUID, testId openapi_types.UUID) (*scan.ResultMetaData, error) {
364403
method := "analysis.retrieveTestComponents"
365404
logger := a.logger.With().Str("method", method).Logger()
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package analysis
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/golang/mock/gomock"
10+
"github.com/google/uuid"
11+
confMocks "github.com/snyk/code-client-go/config/mocks"
12+
httpmocks "github.com/snyk/code-client-go/http/mocks"
13+
testApi "github.com/snyk/code-client-go/internal/api/test/2025-04-07"
14+
"github.com/snyk/error-catalog-golang-public/snyk_errors"
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
func TestAnalysis_retrieveTestURL_TestResultError(t *testing.T) {
19+
ctrl := gomock.NewController(t)
20+
config := confMocks.NewMockConfig(ctrl)
21+
mockHTTPClient := httpmocks.NewMockHTTPClient(ctrl)
22+
apiClient, err := testApi.NewClient("http://localhost", testApi.WithHTTPClient(mockHTTPClient))
23+
assert.NoError(t, err)
24+
25+
analysisOrchestrator, ok := NewAnalysisOrchestrator(
26+
config,
27+
mockHTTPClient,
28+
).(*analysisOrchestrator)
29+
30+
assert.True(t, ok)
31+
32+
mockHTTPClient.EXPECT().Do(gomock.Any()).Return(&http.Response{
33+
StatusCode: http.StatusOK,
34+
Header: http.Header{
35+
"Content-Type": []string{"application/json"},
36+
},
37+
Body: io.NopCloser(bytes.NewBufferString(`{
38+
"data": {
39+
"id": "8afb6fed-b2a9-48cd-9097-8cebd80935e2",
40+
"type": "test",
41+
"attributes": {
42+
"status": "error",
43+
"created_at": "2025-10-09T08:42:07.462607Z",
44+
"errors": [
45+
{
46+
"title": "Analysis result size limit exceeded",
47+
"classification": "UNSUPPORTED",
48+
"message": "Analysis result sarif size is too large",
49+
"error_code": "SNYK-CODE-0002",
50+
"info_url": "https://docs.snyk.io/scan-with-snyk/error-catalog#snyk-code-0002"
51+
}
52+
]
53+
}
54+
},
55+
"jsonapi": {
56+
"version": "1.0"
57+
},
58+
"links": {
59+
"self": "/hidden/orgs/***/tests/8afb6fed-b2a9-48cd-9097-8cebd80935e2"
60+
}
61+
}`)),
62+
}, nil)
63+
64+
resultMetaData, completed, err := analysisOrchestrator.retrieveTestURL(t.Context(), apiClient, uuid.New(), uuid.New())
65+
assert.Error(t, err)
66+
assert.False(t, completed)
67+
assert.Nil(t, resultMetaData)
68+
69+
expectedError := snyk_errors.Error{}
70+
assert.ErrorAs(t, err, &expectedError)
71+
assert.Equal(t, expectedError.ErrorCode, "SNYK-CODE-0002")
72+
assert.Equal(t, expectedError.Title, "Analysis result size limit exceeded")
73+
assert.Equal(t, expectedError.Detail, "Analysis result sarif size is too large")
74+
assert.Equal(t, expectedError.Classification, "UNSUPPORTED")
75+
assert.Equal(t, expectedError.Type, "https://docs.snyk.io/scan-with-snyk/error-catalog#snyk-code-0002")
76+
}

0 commit comments

Comments
 (0)