hub/internal/handlers/pkg/handlers_test.go

2127 lines
56 KiB
Go

package pkg
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/artifacthub/hub/internal/handlers/helpers"
"github.com/artifacthub/hub/internal/hub"
"github.com/artifacthub/hub/internal/oci"
"github.com/artifacthub/hub/internal/pkg"
"github.com/artifacthub/hub/internal/repo"
"github.com/artifacthub/hub/internal/tests"
"github.com/artifacthub/hub/internal/tracker/source/helm"
"github.com/go-chi/chi/v5"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rs/zerolog"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"helm.sh/helm/v3/pkg/chart/loader"
)
func TestMain(m *testing.M) {
zerolog.SetGlobalLevel(zerolog.Disabled)
os.Exit(m.Run())
}
func TestAddProductionUsage(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"repoName", "packageName", "orgName"},
Values: []string{"repo1", "pkg1", "org1"},
},
}
t.Run("error adding production usage entry", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("AddProductionUsage", r.Context(), "repo1", "pkg1", "org1").Return(tests.ErrFakeDB)
hw.h.AddProductionUsage(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("add production usage entry succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("AddProductionUsage", r.Context(), "repo1", "pkg1", "org1").Return(nil)
hw.h.AddProductionUsage(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusCreated, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestDeleteProductionUsage(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"repoName", "packageName", "orgName"},
Values: []string{"repo1", "pkg1", "org1"},
},
}
t.Run("error deleting production usage entry", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("DeleteProductionUsage", r.Context(), "repo1", "pkg1", "org1").Return(tests.ErrFakeDB)
hw.h.DeleteProductionUsage(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("delete production usage entry succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("DeleteProductionUsage", r.Context(), "repo1", "pkg1", "org1").Return(nil)
hw.h.DeleteProductionUsage(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGenerateChangelogMD(t *testing.T) {
packageID := "packageID"
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"repoName", "packageName"},
Values: []string{"repo1", "pkg1"},
},
}
getPkgInput := &hub.GetPackageInput{
RepositoryName: "repo1",
PackageName: "pkg1",
}
t.Run("error getting package", func(t *testing.T) {
testCases := []struct {
pmErr error
expectedStatusCode int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
hub.ErrNotFound,
http.StatusNotFound,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.pmErr.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), getPkgInput).Return(nil, tc.pmErr)
hw.h.GenerateChangelogMD(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
t.Run("error getting package changelog", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), mock.Anything).Return(&hub.Package{PackageID: packageID}, nil)
hw.pm.On("GetChangelog", r.Context(), packageID).Return(nil, tests.ErrFake)
hw.h.GenerateChangelogMD(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("changelog not found", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), mock.Anything).Return(&hub.Package{PackageID: packageID}, nil)
hw.pm.On("GetChangelog", r.Context(), packageID).Return(&hub.Changelog{}, nil)
hw.h.GenerateChangelogMD(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("changelog returned formatted as markdown", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), mock.Anything).Return(&hub.Package{PackageID: packageID}, nil)
hw.pm.On("GetChangelog", r.Context(), packageID).Return(&hub.Changelog{
{
Version: "1.0.0",
TS: 1592299234,
Changes: []*hub.Change{
{
Kind: "added",
Description: "feature 3",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
{
Kind: "fixed",
Description: "fix 3",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
},
ContainsSecurityUpdates: true,
Prerelease: true,
},
{
Version: "0.0.9",
TS: 1592299233,
Changes: []*hub.Change{
{
Kind: "added",
Description: "feature 2",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
{
Kind: "fixed",
Description: "fix 2",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
},
ContainsSecurityUpdates: false,
Prerelease: false,
},
}, nil)
hw.h.GenerateChangelogMD(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
expectedData := []byte(`
# Changelog
## 1.0.0 - 2020-06-16
### Added
- feature 3
### Fixed
- fix 3
## 0.0.9 - 2020-06-16
### Added
- feature 2
### Fixed
- fix 2
`)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "text/markdown", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, expectedData, data)
hw.assertExpectations(t)
})
}
func TestGet(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"repoName", "packageName", "version"},
Values: []string{"repo1", "pkg1", "1.0.0"},
},
}
getPkgInput := &hub.GetPackageInput{
RepositoryName: "repo1",
PackageName: "pkg1",
Version: "1.0.0",
}
t.Run("get package failed", func(t *testing.T) {
testCases := []struct {
pmErr error
expectedStatusCode int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
hub.ErrNotFound,
http.StatusNotFound,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.pmErr.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetJSON", r.Context(), getPkgInput).Return(nil, tc.pmErr)
hw.h.Get(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
t.Run("get package succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetJSON", r.Context(), getPkgInput).Return([]byte("dataJSON"), nil)
hw.h.Get(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
}
func TestGetChangelog(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID"},
Values: []string{"pkg1"},
},
}
t.Run("get changelog succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
changelog := &hub.Changelog{
{
Version: "1.0.0",
TS: 1592299234,
Changes: []*hub.Change{
{
Kind: "added",
Description: "feature 3",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
{
Kind: "fixed",
Description: "fix 3",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
},
ContainsSecurityUpdates: true,
Prerelease: true,
},
{
Version: "0.0.9",
TS: 1592299233,
Changes: []*hub.Change{
{
Kind: "added",
Description: "feature 2",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
{
Kind: "fixed",
Description: "fix 2",
Links: []*hub.Link{
{
Name: "github issue",
URL: "https://issue.url",
},
},
},
},
ContainsSecurityUpdates: false,
Prerelease: false,
},
}
hw.pm.On("GetChangelog", r.Context(), "pkg1").Return(changelog, nil)
hw.h.GetChangelog(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
expectedData, _ := json.Marshal(changelog)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, expectedData, data)
hw.assertExpectations(t)
})
t.Run("error getting changelog", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetChangelog", r.Context(), "pkg1").Return(nil, tests.ErrFakeDB)
hw.h.GetChangelog(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetChartValues(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID", "version"},
Values: []string{"pkg", "1.0.0"},
},
}
getPkgInput := &hub.GetPackageInput{
PackageID: "pkg",
Version: "1.0.0",
}
p1ContentURL := "https://content.url/p1.tgz"
p1 := &hub.Package{
ContentURL: p1ContentURL,
Repository: &hub.Repository{
Kind: hub.Helm,
URL: "https://repo.url",
},
}
t.Run("get chart archive failed", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), getPkgInput).Return(nil, tests.ErrFakeDB)
hw.h.GetChartValues(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("get chart archive succeeded, but values not found", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), getPkgInput).Return(p1, nil)
tgzReq, _ := http.NewRequest("GET", p1ContentURL, nil)
tgzReq = tgzReq.WithContext(r.Context())
tgzReq.Header.Set("Accept-Encoding", "*")
f, _ := os.Open("testdata/pkg2-1.0.0.tgz")
hw.hc.On("Do", tgzReq).Return(&http.Response{
Body: f,
StatusCode: http.StatusOK,
}, nil)
hw.h.GetChartValues(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("get chart archive succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), getPkgInput).Return(p1, nil)
tgzReq, _ := http.NewRequest("GET", p1ContentURL, nil)
tgzReq = tgzReq.WithContext(r.Context())
tgzReq.Header.Set("Accept-Encoding", "*")
f, _ := os.Open("testdata/pkg1-1.0.0.tgz")
hw.hc.On("Do", tgzReq).Return(&http.Response{
Body: f,
StatusCode: http.StatusOK,
}, nil)
hw.h.GetChartValues(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/yaml", h.Get("Content-Type"))
expectedData := []byte("key: value\n")
assert.Equal(t, expectedData, data)
hw.assertExpectations(t)
})
}
func TestGetChartTemplates(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID", "version"},
Values: []string{"pkg", "1.0.0"},
},
}
getPkgInput := &hub.GetPackageInput{
PackageID: "pkg",
Version: "1.0.0",
}
p1ContentURL := "https://content.url/p1.tgz"
p1 := &hub.Package{
ContentURL: p1ContentURL,
Repository: &hub.Repository{
Kind: hub.Helm,
URL: "https://repo.url",
},
}
t.Run("get chart archive failed", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), getPkgInput).Return(nil, tests.ErrFakeDB)
hw.h.GetChartTemplates(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("get chart archive succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), getPkgInput).Return(p1, nil)
tgzReq, _ := http.NewRequest("GET", p1ContentURL, nil)
tgzReq = tgzReq.WithContext(r.Context())
tgzReq.Header.Set("Accept-Encoding", "*")
f, _ := os.Open("testdata/pkg1-1.0.0.tgz")
hw.hc.On("Do", tgzReq).Return(&http.Response{
Body: f,
StatusCode: http.StatusOK,
}, nil)
hw.h.GetChartTemplates(w, r)
resp := w.Result()
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
expectedData := []byte(`{"templates":[{"name":"templates/template.yaml","data":"a2V5OiB7eyAuVmFsdWVzLmtleSB9fQo="}],"values":{"key":"value"}}`)
assert.Equal(t, expectedData, data)
hw.assertExpectations(t)
})
}
func TestGetHarborReplicationDump(t *testing.T) {
t.Run("get harbor replication dump succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetHarborReplicationDumpJSON", r.Context()).Return([]byte("dataJSON"), nil)
hw.h.GetHarborReplicationDump(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(1*time.Hour), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting harbor replication dump", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetHarborReplicationDumpJSON", r.Context()).Return(nil, tests.ErrFakeDB)
hw.h.GetHarborReplicationDump(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetHelmExporterDump(t *testing.T) {
t.Run("get helm exporter dump succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetHelmExporterDumpJSON", r.Context()).Return([]byte("dataJSON"), nil)
hw.h.GetHelmExporterDump(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(1*time.Hour), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting helm exporter dump", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetHelmExporterDumpJSON", r.Context()).Return(nil, tests.ErrFakeDB)
hw.h.GetHelmExporterDump(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetProductionUsage(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"repoName", "packageName"},
Values: []string{"repo1", "pkg1"},
},
}
t.Run("get production usage succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetProductionUsageJSON", r.Context(), "repo1", "pkg1").Return([]byte("dataJSON"), nil)
hw.h.GetProductionUsage(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting production usage", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetProductionUsageJSON", r.Context(), "repo1", "pkg1").Return(nil, tests.ErrFakeDB)
hw.h.GetProductionUsage(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetRandom(t *testing.T) {
t.Run("get random packages succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetRandomJSON", r.Context()).Return([]byte("dataJSON"), nil)
hw.h.GetRandom(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting random packages", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetRandomJSON", r.Context()).Return(nil, tests.ErrFakeDB)
hw.h.GetRandom(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetSnapshotSecurityReport(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID", "version"},
Values: []string{"pkg1", "1.0.0"},
},
}
t.Run("get snapshot security report succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetSnapshotSecurityReportJSON", r.Context(), "pkg1", "1.0.0").Return([]byte("dataJSON"), nil)
hw.h.GetSnapshotSecurityReport(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting snapshot security report", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetSnapshotSecurityReportJSON", r.Context(), "pkg1", "1.0.0").Return(nil, tests.ErrFakeDB)
hw.h.GetSnapshotSecurityReport(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetStarredByUser(t *testing.T) {
t.Run("get packages starred by user succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/?limit=10&offset=1", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
hw := newHandlersWrapper()
hw.pm.On("GetStarredByUserJSON", r.Context(), &hub.Pagination{
Limit: 10,
Offset: 1,
}).Return(&hub.JSONQueryResult{
Data: []byte("dataJSON"),
TotalCount: 1,
}, nil)
hw.h.GetStarredByUser(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, h.Get(helpers.PaginationTotalCount), "1")
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting packages starred by user", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/?limit=10&offset=1", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
hw := newHandlersWrapper()
hw.pm.On("GetStarredByUserJSON", r.Context(), &hub.Pagination{
Limit: 10,
Offset: 1,
}).Return(nil, tests.ErrFakeDB)
hw.h.GetStarredByUser(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetStars(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID"},
Values: []string{"packageID"},
},
}
t.Run("get stars failed", func(t *testing.T) {
testCases := []struct {
err error
expectedStatus int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.err.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetStarsJSON", r.Context(), "packageID").Return(nil, tc.err)
hw.h.GetStars(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatus, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
t.Run("get stars succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetStarsJSON", r.Context(), "packageID").Return([]byte("dataJSON"), nil)
hw.h.GetStars(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(0), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
}
func TestGetStats(t *testing.T) {
t.Run("get stats succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetStatsJSON", r.Context()).Return([]byte("dataJSON"), nil)
hw.h.GetStats(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting stats", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("GetStatsJSON", r.Context()).Return(nil, tests.ErrFakeDB)
hw.h.GetStats(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetSummary(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"repoName", "packageName"},
Values: []string{"repo1", "pkg1"},
},
}
input := &hub.GetPackageInput{
RepositoryName: "repo1",
PackageName: "pkg1",
}
t.Run("get package summary failed", func(t *testing.T) {
testCases := []struct {
pmErr error
expectedStatusCode int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
hub.ErrNotFound,
http.StatusNotFound,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.pmErr.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetSummaryJSON", r.Context(), input).Return(nil, tc.pmErr)
hw.h.GetSummary(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
t.Run("get package summary succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetSummaryJSON", r.Context(), input).Return([]byte("dataJSON"), nil)
hw.h.GetSummary(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
}
func TestGetValuesSchema(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID", "version"},
Values: []string{"pkg1", "1.0.0"},
},
}
t.Run("get values schema succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetValuesSchemaJSON", r.Context(), "pkg1", "1.0.0").Return([]byte("dataJSON"), nil)
hw.h.GetValuesSchema(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting values schema", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetValuesSchemaJSON", r.Context(), "pkg1", "1.0.0").Return(nil, tests.ErrFakeDB)
hw.h.GetValuesSchema(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetViews(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID"},
Values: []string{"pkg1"},
},
}
t.Run("get views succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetViewsJSON", r.Context(), "pkg1").Return([]byte("dataJSON"), nil)
hw.h.GetViews(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(1*time.Hour), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error getting views", func(t *testing.T) {
testCases := []struct {
pmErr error
expectedStatusCode int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
hub.ErrNotFound,
http.StatusNotFound,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.pmErr.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("GetViewsJSON", r.Context(), "pkg1").Return(nil, tc.pmErr)
hw.h.GetViews(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
}
func TestInjectIndexMeta(t *testing.T) {
checkIndexMeta := func(expectedTitle, expectedDescription interface{}) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
title, _ := r.Context().Value(hub.IndexMetaTitleKey).(string)
description, _ := r.Context().Value(hub.IndexMetaDescriptionKey).(string)
assert.Equal(t, expectedTitle, title)
assert.Equal(t, expectedDescription, description)
}
}
testCases := []struct {
p *hub.Package
err error
expectedTitle string
expectedDescription string
}{
{
&hub.Package{
NormalizedName: "pkg1",
Version: "1.0.0",
Description: "description",
Repository: &hub.Repository{
Name: "repo1",
OrganizationName: "org1",
},
},
nil,
"pkg1 1.0.0 · org1/repo1",
"description",
},
{
&hub.Package{
NormalizedName: "pkg1",
Version: "1.0.0",
Repository: &hub.Repository{
Name: "repo1",
UserAlias: "user1",
},
},
nil,
"pkg1 1.0.0 · user1/repo1",
"",
},
{
nil,
tests.ErrFake,
"",
"",
},
}
for i, tc := range testCases {
tc := tc
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
if tc.err == nil {
hw.pm.On("Get", r.Context(), mock.Anything).Return(tc.p, nil)
} else {
hw.pm.On("Get", r.Context(), mock.Anything).Return(nil, tc.err)
}
hw.h.InjectIndexMeta(checkIndexMeta(tc.expectedTitle, tc.expectedDescription)).ServeHTTP(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
hw.assertExpectations(t)
})
}
}
func TestRssFeed(t *testing.T) {
t.Run("error getting rss feed package", func(t *testing.T) {
testCases := []struct {
pmErr error
expectedStatusCode int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
hub.ErrNotFound,
http.StatusNotFound,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.pmErr.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), mock.Anything).Return(nil, tc.pmErr)
hw.h.RssFeed(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
t.Run("rss feed built successfully", func(t *testing.T) {
testCases := []struct {
p *hub.Package
expectedRssFeedData []byte
}{
{
&hub.Package{
PackageID: "0001",
NormalizedName: "pkg1",
Description: "description",
Version: "1.0.0",
LogoImageID: "0001",
TS: 1592299234,
AvailableVersions: []*hub.Version{
{
Version: "0.0.9",
TS: 1592299233,
},
{
Version: "1.0.0",
TS: 1592299234,
},
},
Maintainers: []*hub.Maintainer{
{
Name: "name1",
Email: "email1",
},
},
Repository: &hub.Repository{
Name: "repo1",
OrganizationName: "org1",
},
},
[]byte(`<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>org1/pkg1 (Artifact Hub)</title>
<link>baseURL</link>
<description>description</description>
<managingEditor>email1 (name1)</managingEditor>
<image>
<url>baseURL/image/0001@4x</url>
<title>logo</title>
<link>baseURL</link>
</image>
<item>
<title>1.0.0</title>
<link>baseURL/packages/helm/repo1/pkg1/1.0.0</link>
<description>pkg1 1.0.0</description>
<guid>0001#1.0.0</guid>
<pubDate>Tue, 16 Jun 2020 09:20:34 +0000</pubDate>
</item>
<item>
<title>0.0.9</title>
<link>baseURL/packages/helm/repo1/pkg1/0.0.9</link>
<description>pkg1 0.0.9</description>
<guid>0001#0.0.9</guid>
<pubDate>Tue, 16 Jun 2020 09:20:33 +0000</pubDate>
</item>
</channel>
</rss>`),
},
}
for i, tc := range testCases {
tc := tc
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("Get", r.Context(), mock.Anything).Return(tc.p, nil)
hw.h.RssFeed(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "text/xml; charset=utf-8", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, tc.expectedRssFeedData, data)
hw.assertExpectations(t)
})
}
})
}
func TestSearch(t *testing.T) {
t.Run("invalid request params", func(t *testing.T) {
testCases := []struct {
desc string
params string
}{
{"invalid limit", "limit=z"},
{"invalid offset", "offset=z"},
{"invalid facets", "facets=z"},
{"invalid kind", "kind=z"},
{"invalid kind (one of them)", "kind=0&kind=z"},
{"invalid verified publisher", "verified_publisher=z"},
{"invalid official", "official=z"},
{"invalid operators", "operators=z"},
{"invalid deprecated", "deprecated=z"},
}
for _, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("%s: %s", tc.desc, tc.params), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/?"+tc.params, nil)
hw := newHandlersWrapper()
hw.h.Search(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
}
})
t.Run("invalid search input", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("SearchJSON", r.Context(), mock.Anything).Return(nil, hub.ErrInvalidInput)
hw.h.Search(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
t.Run("valid request, search succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
v := url.Values{}
v.Set("limit", "10")
v.Set("offset", "10")
v.Set("facets", "true")
v.Set("ts_query_web", "q1")
v.Set("ts_query", "q2")
v.Add("user", "u1")
v.Add("user", "u2")
v.Add("org", "o1")
v.Add("org", "o2")
v.Add("repo", "r1")
v.Add("repo", "r2")
v.Add("kind", "0")
v.Add("kind", "3")
v.Set("verified_publisher", "true")
v.Set("official", "true")
v.Set("operators", "true")
v.Set("deprecated", "true")
v.Add("license", "l1")
v.Add("license", "l2")
v.Add("capabilities", "c1")
v.Add("capabilities", "c2")
v.Set("sort", "stars")
r, _ := http.NewRequest("GET", "/?"+v.Encode(), nil)
hw := newHandlersWrapper()
hw.pm.On("SearchJSON", r.Context(), &hub.SearchPackageInput{
Limit: 10,
Offset: 10,
Facets: true,
TSQueryWeb: "q1",
TSQuery: "q2",
Users: []string{"u1", "u2"},
Orgs: []string{"o1", "o2"},
Repositories: []string{"r1", "r2"},
RepositoryKinds: []hub.RepositoryKind{hub.Helm, hub.OLM},
VerifiedPublisher: true,
Official: true,
Operators: true,
Deprecated: true,
Licenses: []string{"l1", "l2"},
Capabilities: []string{"c1", "c2"},
Sort: "stars",
}).Return(&hub.JSONQueryResult{
Data: []byte("dataJSON"),
TotalCount: 1,
}, nil)
hw.h.Search(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, h.Get(helpers.PaginationTotalCount), "1")
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("error searching packages", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
hw := newHandlersWrapper()
hw.pm.On("SearchJSON", r.Context(), mock.Anything).Return(nil, tests.ErrFakeDB)
hw.h.Search(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestSearchMonocular(t *testing.T) {
t.Run("search succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/?q=text", nil)
hw := newHandlersWrapper()
hw.pm.On("SearchMonocularJSON", r.Context(), "baseURL", "text").Return([]byte("dataJSON"), nil)
hw.h.SearchMonocular(w, r)
resp := w.Result()
defer resp.Body.Close()
h := resp.Header
data, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", h.Get("Content-Type"))
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
assert.Equal(t, []byte("dataJSON"), data)
hw.assertExpectations(t)
})
t.Run("search failed", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/?q=text", nil)
hw := newHandlersWrapper()
hw.pm.On("SearchMonocularJSON", r.Context(), "baseURL", "text").Return(nil, tests.ErrFakeDB)
hw.h.SearchMonocular(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestToggleStar(t *testing.T) {
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID"},
Values: []string{"packageID"},
},
}
t.Run("error toggling star", func(t *testing.T) {
testCases := []struct {
err error
expectedStatus int
}{
{
hub.ErrInvalidInput,
http.StatusBadRequest,
},
{
tests.ErrFakeDB,
http.StatusInternalServerError,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.err.Error(), func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("PUT", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("ToggleStar", r.Context(), "packageID").Return(tc.err)
hw.h.ToggleStar(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, tc.expectedStatus, resp.StatusCode)
hw.assertExpectations(t)
})
}
})
t.Run("toggle star succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("PUT", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), hub.UserIDKey, "userID"))
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.pm.On("ToggleStar", r.Context(), "packageID").Return(nil)
hw.h.ToggleStar(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestTrackView(t *testing.T) {
packageID := "00000000-0000-0000-0000-000000000001"
version := "1.0.0"
rctx := &chi.Context{
URLParams: chi.RouteParams{
Keys: []string{"packageID", "version"},
Values: []string{packageID, version},
},
}
t.Run("track view succeeded", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.vt.On("TrackView", packageID, version).Return(nil)
hw.h.TrackView(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
hw.assertExpectations(t)
})
t.Run("error tracking view", func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", nil)
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
hw := newHandlersWrapper()
hw.vt.On("TrackView", packageID, version).Return(hub.ErrInvalidInput)
hw.h.TrackView(w, r)
resp := w.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
hw.assertExpectations(t)
})
}
func TestGetChartArchive(t *testing.T) {
ctx := context.Background()
packageID := "packageID"
version := "1.0.0"
getPkgInput := &hub.GetPackageInput{
PackageID: packageID,
Version: version,
}
p1ContentURL := "https://content.url/p1.tgz"
p1 := &hub.Package{
ContentURL: p1ContentURL,
Repository: &hub.Repository{
Kind: hub.Helm,
URL: "https://repo.url",
},
}
p2ContentURL := "https://content.url/p2.tgz"
p2 := &hub.Package{
ContentURL: p2ContentURL,
Repository: &hub.Repository{
RepositoryID: "repo2",
Kind: hub.Helm,
URL: "https://repo2.url",
Private: true,
},
}
p3ContentURL := "oci://registry/namespace/chart:1.0.0"
p3 := &hub.Package{
ContentURL: p3ContentURL,
Repository: &hub.Repository{
Kind: hub.Helm,
URL: "oci://registry/namespace/chart",
},
}
f, _ := os.Open("testdata/pkg1-1.0.0.tgz")
expectedChrt, _ := loader.LoadArchive(f)
t.Run("error getting package", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(nil, tests.ErrFake)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Equal(t, tests.ErrFake, err)
assert.Nil(t, chrt)
hw.assertExpectations(t)
})
t.Run("repository kind not supported", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(&hub.Package{
Repository: &hub.Repository{
Kind: hub.OLM,
},
}, nil)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Error(t, err)
assert.Contains(t, err.Error(), "operation not supported for this repository kind")
assert.Nil(t, chrt)
hw.assertExpectations(t)
})
t.Run("error downloading repository", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p2, nil)
hw.rm.On("GetByID", ctx, p2.Repository.RepositoryID, true).Return(nil, tests.ErrFake)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Equal(t, tests.ErrFake, err)
assert.Nil(t, chrt)
hw.assertExpectations(t)
})
t.Run("error downloading chart archive from http server", func(t *testing.T) {
testCases := []struct {
description string
resp *http.Response
err error
}{
{
"server returned an error",
nil,
tests.ErrFake,
},
{
"server returned an unexpected status code",
&http.Response{
Body: io.NopCloser(strings.NewReader("")),
StatusCode: http.StatusNotFound,
},
nil,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p1, nil)
tgzReq, _ := http.NewRequest("GET", p1ContentURL, nil)
tgzReq = tgzReq.WithContext(ctx)
tgzReq.Header.Set("Accept-Encoding", "*")
hw.hc.On("Do", tgzReq).Return(tc.resp, tc.err)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Error(t, err)
assert.Nil(t, chrt)
hw.assertExpectations(t)
})
}
})
t.Run("error pulling chart archive from oci registry", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p3, nil)
ref := strings.TrimPrefix(p3ContentURL, hub.RepositoryOCIPrefix)
hw.op.On("PullLayer", ctx, ref, helm.ChartContentLayerMediaType, "", "").
Return(ocispec.Descriptor{}, nil, tests.ErrFake)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Equal(t, tests.ErrFake, err)
assert.Nil(t, chrt)
hw.assertExpectations(t)
})
t.Run("error loading chart archive", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p1, nil)
tgzReq, _ := http.NewRequest("GET", p1ContentURL, nil)
tgzReq = tgzReq.WithContext(ctx)
tgzReq.Header.Set("Accept-Encoding", "*")
hw.hc.On("Do", tgzReq).Return(&http.Response{
Body: io.NopCloser(strings.NewReader("")),
StatusCode: http.StatusOK,
}, nil)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Error(t, err)
assert.Nil(t, chrt)
hw.assertExpectations(t)
})
t.Run("chart archive returned successfully (http server)", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p1, nil)
tgzReq, _ := http.NewRequest("GET", p1ContentURL, nil)
tgzReq = tgzReq.WithContext(ctx)
tgzReq.Header.Set("Accept-Encoding", "*")
f, _ := os.Open("testdata/pkg1-1.0.0.tgz")
hw.hc.On("Do", tgzReq).Return(&http.Response{
Body: f,
StatusCode: http.StatusOK,
}, nil)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Equal(t, expectedChrt, chrt)
assert.NoError(t, err)
hw.assertExpectations(t)
})
t.Run("chart archive returned successfully (http server, private repository)", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p2, nil)
hw.rm.On("GetByID", ctx, p2.Repository.RepositoryID, true).Return(&hub.Repository{
AuthUser: "user",
AuthPass: "pass",
}, nil)
tgzReq, _ := http.NewRequest("GET", p2ContentURL, nil)
tgzReq = tgzReq.WithContext(ctx)
tgzReq.SetBasicAuth("user", "pass")
tgzReq.Header.Set("Accept-Encoding", "*")
f, _ := os.Open("testdata/pkg1-1.0.0.tgz")
hw.hc.On("Do", tgzReq).Return(&http.Response{
Body: f,
StatusCode: http.StatusOK,
}, nil)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Equal(t, expectedChrt, chrt)
assert.NoError(t, err)
hw.assertExpectations(t)
})
t.Run("chart archive returned successfully (oci registry)", func(t *testing.T) {
t.Parallel()
hw := newHandlersWrapper()
hw.pm.On("Get", ctx, getPkgInput).Return(p3, nil)
layerData, _ := os.ReadFile("testdata/pkg1-1.0.0.tgz")
ref := strings.TrimPrefix(p3ContentURL, hub.RepositoryOCIPrefix)
hw.op.On("PullLayer", ctx, ref, helm.ChartContentLayerMediaType, "", "").
Return(ocispec.Descriptor{}, layerData, nil)
chrt, err := hw.h.getChartArchive(ctx, packageID, version)
assert.Equal(t, expectedChrt, chrt)
assert.NoError(t, err)
hw.assertExpectations(t)
})
}
func TestBuildURL(t *testing.T) {
baseURL := "http://localhost:8000"
testCases := []struct {
p *hub.Package
version string
expectedPkgURL string
}{
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Helm,
Name: "repo1",
},
},
"1.0.0",
baseURL + "/packages/helm/repo1/pkg1/1.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Helm,
Name: "repo1",
},
},
"",
baseURL + "/packages/helm/repo1/pkg1",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Falco,
Name: "repo1",
},
},
"",
baseURL + "/packages/falco/repo1/pkg1",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.OPA,
Name: "repo1",
},
},
"",
baseURL + "/packages/opa/repo1/pkg1",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.OLM,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/olm/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.TBAction,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/tbaction/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Krew,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/krew/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.HelmPlugin,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/helm-plugin/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.TektonTask,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/tekton-task/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.KedaScaler,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/keda-scaler/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.CoreDNS,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/coredns/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Keptn,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/keptn/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.TektonPipeline,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/tekton-pipeline/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Container,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/container/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Kubewarden,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/kubewarden/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Gatekeeper,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/gatekeeper/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Kyverno,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/kyverno/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.KnativeClientPlugin,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/knative-client-plugin/repo1/pkg1/2.0.0",
},
{
&hub.Package{
NormalizedName: "pkg1",
Repository: &hub.Repository{
Kind: hub.Backstage,
Name: "repo1",
},
},
"2.0.0",
baseURL + "/packages/backstage/repo1/pkg1/2.0.0",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.expectedPkgURL, func(t *testing.T) {
pkgURL := BuildURL(baseURL, tc.p, tc.version)
assert.Equal(t, tc.expectedPkgURL, pkgURL)
})
}
}
type handlersWrapper struct {
pm *pkg.ManagerMock
rm *repo.ManagerMock
hc *tests.HTTPClientMock
op *oci.PullerMock
vt *pkg.ViewsTrackerMock
h *Handlers
}
func newHandlersWrapper() *handlersWrapper {
cfg := viper.New()
cfg.Set("server.baseURL", "baseURL")
pm := &pkg.ManagerMock{}
rm := &repo.ManagerMock{}
hc := &tests.HTTPClientMock{}
op := &oci.PullerMock{}
vt := &pkg.ViewsTrackerMock{}
return &handlersWrapper{
pm: pm,
rm: rm,
hc: hc,
op: op,
vt: vt,
h: NewHandlers(pm, rm, cfg, hc, op, vt),
}
}
func (hw *handlersWrapper) assertExpectations(t *testing.T) {
hw.pm.AssertExpectations(t)
hw.rm.AssertExpectations(t)
hw.hc.AssertExpectations(t)
hw.op.AssertExpectations(t)
}