Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions api/v1/marketplace/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"

"reverse-watch/domain/dto"
"reverse-watch/domain/models"
"reverse-watch/domain/repository"
"reverse-watch/errors"
Expand Down Expand Up @@ -52,3 +53,31 @@ func createKey(w http.ResponseWriter, r *http.Request) {

render.JSON(w, r, rawKey)
}

func listKeys(w http.ResponseWriter, r *http.Request) {
factory, ok := r.Context().Value(middleware.FactoryContextKey).(repository.Factory)
if !ok {
render.Errorf(w, r, errors.InternalServerError, "missing factory from context")
return
}

key, ok := r.Context().Value(middleware.KeyContextKey).(*models.Key)
if !ok {
render.Errorf(w, r, errors.InternalServerError, "missing key from context")
return
}

keysList, err := factory.Key().List(&dto.KeyListOptions{
MarketplaceSlug: &key.MarketplaceSlug,
})
if err != nil {
render.Errorf(w, r, errors.InternalServerError, "failed to list keys")
return
}

render.JSON(w, r, struct {
Data []*models.Key `json:"data"`
}{
Data: keysList,
})
}
177 changes: 177 additions & 0 deletions api/v1/marketplace/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"reverse-watch/repository/private"
"reverse-watch/secret"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gorm.io/gorm"
)

Expand Down Expand Up @@ -348,3 +350,178 @@ func TestCreateKey_ContextErrors(t *testing.T) {
})
}
}

func TestListKeys(t *testing.T) {
t.Parallel()

db := testutil.NewTestDB(t)
keygen := secret.NewKeyGenerator(constants.EnvironmentDevelopment)
keyRepo := private.NewKeyRepository(db, keygen)
factory := testutil.NewTestFactory(t).WithKey(keyRepo)

testMarketplace1 := &models.Marketplace{
Slug: "test-marketplace-1",
Name: "Test Marketplace 1",
IsActive: true,
}
testMarketplace2 := &models.Marketplace{
Slug: "test-marketplace-2",
Name: "Test Marketplace 2",
IsActive: true,
}
testutil.Insert(t, db, testMarketplace1, testMarketplace2)

// Create auth key
secretKey, err := keygen.GenerateSecretKey()
if err != nil {
t.Fatalf("GenerateSecretKey(): %v", err)
}

id, err := secretKey.ID()
if err != nil {
t.Fatalf("ID(): %v", err)
}

authKey := &models.Key{
ID: id,
Environment: keygen.Environment(),
MarketplaceSlug: testMarketplace1.Slug,
Permissions: models.PermissionManage,
}
testutil.Insert(t, db, authKey)

// Create additional keys for the same marketplace
key1 := &models.Key{
ID: "key-1",
Environment: keygen.Environment(),
MarketplaceSlug: testMarketplace1.Slug,
Permissions: models.PermissionRead,
}
key2 := &models.Key{
ID: "key-2",
Environment: keygen.Environment(),
MarketplaceSlug: testMarketplace1.Slug,
Permissions: models.PermissionWrite,
}
testutil.Insert(t, db, key1, key2)

// Create additional keys for different marketplace
key3 := &models.Key{
ID: "key-3",
Environment: keygen.Environment(),
MarketplaceSlug: testMarketplace2.Slug,
Permissions: models.PermissionExport,
}
testutil.Insert(t, db, key3)

wantKeys := []*models.Key{key2, key1, authKey}
wantStatusCode := http.StatusOK

r := httptest.NewRequest(http.MethodGet, "/", nil)
formattedKey, err := secretKey.Format()
if err != nil {
t.Fatalf("Format(): %v", err)
}
r.Header.Set("Authorization", "Bearer "+formattedKey)

factoryMiddleware := middleware.FactoryMiddleware(factory)
permissionsMiddleware := middleware.RequirePermissions(models.PermissionManage)
handler := http.HandlerFunc(listKeys)

finalHandler := factoryMiddleware(
middleware.AuthMiddleware(
permissionsMiddleware(handler),
),
)

w := httptest.NewRecorder()
finalHandler.ServeHTTP(w, r)

if w.Code != wantStatusCode {
t.Errorf("got status code %d, wanted %d", w.Code, wantStatusCode)
}

resp := w.Result()
defer resp.Body.Close()

var data struct {
Data []*models.Key `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(wantKeys, data.Data, cmpopts.IgnoreFields(models.Key{}, "CreatedAt", "UpdatedAt")); diff != "" {
t.Fatal(diff)
}
}

func TestListKeys_ContextErrors(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
setup func(db *gorm.DB, factory repository.Factory) (*http.Request, error)
wantStatusCode int
}{
{
name: "missingFactoryFromContext",
setup: func(db *gorm.DB, factory repository.Factory) (*http.Request, error) {
return httptest.NewRequest(http.MethodGet, "/", nil), nil
},
wantStatusCode: http.StatusInternalServerError,
},
{
name: "missingKeyFromContext",
setup: func(db *gorm.DB, factory repository.Factory) (*http.Request, error) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
ctx := context.WithValue(r.Context(), middleware.FactoryContextKey, factory)
return r.WithContext(ctx), nil
},
wantStatusCode: http.StatusInternalServerError,
},
{
name: "failedToListKeys",
setup: func(db *gorm.DB, factory repository.Factory) (*http.Request, error) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
ctx := context.WithValue(r.Context(), middleware.FactoryContextKey, factory)
ctx = context.WithValue(ctx, middleware.KeyContextKey, &models.Key{
MarketplaceSlug: "test-marketplace",
})

// Close db connection to simulate database error
sqlDb, err := db.DB()
if err != nil {
return nil, err
}
if err := sqlDb.Close(); err != nil {
return nil, err
}
return r.WithContext(ctx), nil
},
wantStatusCode: http.StatusInternalServerError,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := testutil.NewTestDB(t)
keygen := secret.NewKeyGenerator(constants.EnvironmentDevelopment)
keyRepo := private.NewKeyRepository(db, keygen)
factory := testutil.NewTestFactory(t).WithKey(keyRepo)

w := httptest.NewRecorder()
r, err := tc.setup(db, factory)
if err != nil {
t.Fatal(err)
}

handler := http.HandlerFunc(listKeys)
handler.ServeHTTP(w, r)

if w.Code != tc.wantStatusCode {
t.Errorf("got status code %d, wanted %d", w.Code, tc.wantStatusCode)
}
})
}
}
1 change: 1 addition & 0 deletions api/v1/marketplace/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func Router() chi.Router {
r.Use(rwmiddleware.RequirePermissions(models.PermissionManage))

r.Route("/keys", func(r chi.Router) {
r.Get("/", listKeys)
r.Post("/", createKey)
})
return r
Expand Down