Skip to content

Commit 51ec409

Browse files
Merge pull request #54 from snyk/feat/add-fileupload-filters-client
feat: add fileupload filters client
2 parents f3eb632 + 55dbd01 commit 51ec409

File tree

6 files changed

+226
-0
lines changed

6 files changed

+226
-0
lines changed

internal/fileupload/filters/client.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package filters
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
9+
"github.com/google/uuid"
10+
)
11+
12+
// AllowList represents the response structure from the deeproxy filters API.
13+
type AllowList struct {
14+
ConfigFiles []string `json:"configFiles"`
15+
Extensions []string `json:"extensions"`
16+
}
17+
18+
// Client defines the interface for the filters client.
19+
type Client interface {
20+
GetFilters(ctx context.Context, orgID uuid.UUID) (AllowList, error)
21+
}
22+
23+
// DeeproxyClient is the deeproxy implementation of the Client interface.
24+
type DeeproxyClient struct {
25+
httpClient *http.Client
26+
cfg Config
27+
}
28+
29+
// Config contains the configuration for the filters client.
30+
type Config struct {
31+
BaseURL string
32+
IsFedRamp bool
33+
}
34+
35+
// NewDeeproxyClient creates a new DeeproxyClient with the given configuration and options.
36+
func NewDeeproxyClient(cfg Config, opts ...Opt) *DeeproxyClient {
37+
c := &DeeproxyClient{
38+
cfg: cfg,
39+
httpClient: http.DefaultClient,
40+
}
41+
42+
for _, opt := range opts {
43+
opt(c)
44+
}
45+
46+
return c
47+
}
48+
49+
// GetFilters returns the deeproxy filters in the form of an AllowList.
50+
func (c *DeeproxyClient) GetFilters(ctx context.Context, orgID uuid.UUID) (AllowList, error) {
51+
var allowList AllowList
52+
53+
url := getFilterURL(c.cfg.BaseURL, orgID, c.cfg.IsFedRamp)
54+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
55+
if err != nil {
56+
return allowList, fmt.Errorf("failed to create deeproxy filters request: %w", err)
57+
}
58+
59+
req.Header.Set("snyk-org-name", orgID.String())
60+
61+
resp, err := c.httpClient.Do(req)
62+
if err != nil {
63+
return allowList, fmt.Errorf("error making deeproxy filters request: %w", err)
64+
}
65+
defer resp.Body.Close()
66+
67+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
68+
return allowList, fmt.Errorf("unexpected response code: %s", resp.Status)
69+
}
70+
71+
if err := json.NewDecoder(resp.Body).Decode(&allowList); err != nil {
72+
return allowList, fmt.Errorf("failed to decode deeproxy filters response: %w", err)
73+
}
74+
75+
return allowList, nil
76+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package filters //nolint:testpackage // Testing private utility functions.
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/google/uuid"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestClients(t *testing.T) {
15+
tests := []struct {
16+
getClient func(t *testing.T, orgID uuid.UUID, expectedAllow AllowList) (Client, func())
17+
clientName string
18+
}{
19+
{
20+
clientName: "deeproxyClient",
21+
getClient: func(t *testing.T, orgID uuid.UUID, expectedAllow AllowList) (Client, func()) {
22+
t.Helper()
23+
24+
s := setupServer(t, orgID, expectedAllow)
25+
cleanup := func() {
26+
s.Close()
27+
}
28+
c := NewDeeproxyClient(Config{BaseURL: s.URL, IsFedRamp: true}, WithHTTPClient(s.Client()))
29+
30+
return c, cleanup
31+
},
32+
},
33+
{
34+
clientName: "fakeClient",
35+
getClient: func(t *testing.T, _ uuid.UUID, expectedAllow AllowList) (Client, func()) {
36+
t.Helper()
37+
38+
c := NewFakeClient(expectedAllow, nil)
39+
40+
return c, func() {}
41+
},
42+
},
43+
}
44+
45+
for _, testData := range tests {
46+
t.Run(testData.clientName+": GetFilters", func(t *testing.T) {
47+
orgID := uuid.New()
48+
expectedAllow := AllowList{
49+
ConfigFiles: []string{"package.json"},
50+
Extensions: []string{".ts", ".js"},
51+
}
52+
client, cleanup := testData.getClient(t, orgID, expectedAllow)
53+
defer cleanup()
54+
55+
allow, err := client.GetFilters(t.Context(), orgID)
56+
require.NoError(t, err)
57+
58+
assert.Equal(t, expectedAllow, allow)
59+
})
60+
}
61+
}
62+
63+
func setupServer(t *testing.T, orgID uuid.UUID, expectedAllow AllowList) *httptest.Server {
64+
t.Helper()
65+
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
expectedURL := getFilterURL("", orgID, true)
67+
assert.Equal(t, expectedURL, r.URL.Path)
68+
assert.Equal(t, orgID.String(), r.Header.Get("snyk-org-name"))
69+
w.Header().Set("Content-Type", "application/json")
70+
if err := json.NewEncoder(w).Encode(expectedAllow); err != nil {
71+
http.Error(w, "failed to encode response", http.StatusInternalServerError)
72+
return
73+
}
74+
}))
75+
ts.Start()
76+
return ts
77+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package filters
2+
3+
import (
4+
"context"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
type FakeClient struct {
10+
getFilters func(ctx context.Context, orgID uuid.UUID) (AllowList, error)
11+
}
12+
13+
func NewFakeClient(allowList AllowList, err error) *FakeClient {
14+
return &FakeClient{
15+
getFilters: func(ctx context.Context, orgID uuid.UUID) (AllowList, error) {
16+
return allowList, err
17+
},
18+
}
19+
}
20+
21+
func (f *FakeClient) GetFilters(ctx context.Context, orgID uuid.UUID) (AllowList, error) {
22+
return f.getFilters(ctx, orgID)
23+
}

internal/fileupload/filters/opts.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package filters
2+
3+
import "net/http"
4+
5+
// Opt is a function that configures an deeproxyClient instance.
6+
type Opt func(*DeeproxyClient)
7+
8+
// WithHTTPClient sets a custom HTTP client for the filters client.
9+
func WithHTTPClient(httpClient *http.Client) Opt {
10+
return func(c *DeeproxyClient) {
11+
c.httpClient = httpClient
12+
}
13+
}

internal/fileupload/filters/utils.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package filters
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/google/uuid"
8+
)
9+
10+
func getFilterURL(baseURL string, orgID uuid.UUID, isFedRamp bool) string {
11+
if isFedRamp {
12+
return fmt.Sprintf("%s/hidden/orgs/%s/code/filters", baseURL, orgID)
13+
}
14+
15+
deeproxyURL := strings.ReplaceAll(baseURL, "api", "deeproxy")
16+
return fmt.Sprintf("%s/filters", deeproxyURL)
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package filters //nolint:testpackage // Testing private utility functions.
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/uuid"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
var orgID = uuid.MustParse("738ef92e-21cc-4a11-8c13-388d89272f4b")
11+
12+
func Test_getBaseUrl_notFedramp(t *testing.T) {
13+
actualURL := getFilterURL("https://api.snyk.io", orgID, false)
14+
assert.Equal(t, "https://deeproxy.snyk.io/filters", actualURL)
15+
}
16+
17+
func Test_getBaseUrl_fedramp(t *testing.T) {
18+
actualURL := getFilterURL("https://api.snyk.io", orgID, true)
19+
assert.Equal(t, "https://api.snyk.io/hidden/orgs/738ef92e-21cc-4a11-8c13-388d89272f4b/code/filters", actualURL)
20+
}

0 commit comments

Comments
 (0)