Skip to content

Commit

Permalink
Enable filtering service offerings by name
Browse files Browse the repository at this point in the history
Also accept the `fields` parameter but ignore it for now

fixes #3270 #3272

Co-authored-by: Georgi Sabev <[email protected]>
  • Loading branch information
danail-branekov and georgethebeatle committed Jul 30, 2024
1 parent 0547327 commit 0930ef1
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 15 deletions.
18 changes: 10 additions & 8 deletions api/handlers/fake/cfservice_offering_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions api/handlers/service_offering.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"code.cloudfoundry.org/korifi/api/authorization"
apierrors "code.cloudfoundry.org/korifi/api/errors"
"code.cloudfoundry.org/korifi/api/payloads"
"code.cloudfoundry.org/korifi/api/presenter"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/api/routing"
Expand All @@ -20,20 +21,23 @@ const (

//counterfeiter:generate -o fake -fake-name CFServiceOfferingRepository . CFServiceOfferingRepository
type CFServiceOfferingRepository interface {
ListOfferings(context.Context, authorization.Info) ([]repositories.ServiceOfferingRecord, error)
ListOfferings(context.Context, authorization.Info, repositories.ListServiceOfferingMessage) ([]repositories.ServiceOfferingRecord, error)
}

type ServiceOffering struct {
serverURL url.URL
requestValidator RequestValidator
serviceOfferingRepo CFServiceOfferingRepository
}

func NewServiceOffering(
serverURL url.URL,
requestValidator RequestValidator,
serviceOfferingRepo CFServiceOfferingRepository,
) *ServiceOffering {
return &ServiceOffering{
serverURL: serverURL,
requestValidator: requestValidator,
serviceOfferingRepo: serviceOfferingRepo,
}
}
Expand All @@ -42,7 +46,12 @@ func (h *ServiceOffering) list(r *http.Request) (*routing.Response, error) {
authInfo, _ := authorization.InfoFromContext(r.Context())
logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.service-offering.list")

serviceOfferingList, err := h.serviceOfferingRepo.ListOfferings(r.Context(), authInfo)
var payload payloads.ServiceOfferingList
if err := h.requestValidator.DecodeAndValidateURLValues(r, &payload); err != nil {
return nil, apierrors.LogAndReturn(logger, err, "failed to decode json payload")
}

serviceOfferingList, err := h.serviceOfferingRepo.ListOfferings(r.Context(), authInfo, payload.ToMessage())
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "Failed to list service offerings")
}
Expand Down
34 changes: 32 additions & 2 deletions api/handlers/service_offering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

. "code.cloudfoundry.org/korifi/api/handlers"
"code.cloudfoundry.org/korifi/api/handlers/fake"
"code.cloudfoundry.org/korifi/api/payloads"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/model"
"code.cloudfoundry.org/korifi/model/services"
Expand All @@ -16,13 +17,18 @@ import (
)

var _ = Describe("ServiceOffering", func() {
var serviceOfferingRepo *fake.CFServiceOfferingRepository
var (
requestValidator *fake.RequestValidator
serviceOfferingRepo *fake.CFServiceOfferingRepository
)

BeforeEach(func() {
requestValidator = new(fake.RequestValidator)
serviceOfferingRepo = new(fake.CFServiceOfferingRepository)

apiHandler := NewServiceOffering(
*serverURL,
requestValidator,
serviceOfferingRepo,
)
routerBuilder.LoadRoutes(apiHandler)
Expand Down Expand Up @@ -54,7 +60,7 @@ var _ = Describe("ServiceOffering", func() {

It("lists the service offerings", func() {
Expect(serviceOfferingRepo.ListOfferingsCallCount()).To(Equal(1))
_, actualAuthInfo := serviceOfferingRepo.ListOfferingsArgsForCall(0)
_, actualAuthInfo, _ := serviceOfferingRepo.ListOfferingsArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))

Expect(rr).Should(HaveHTTPStatus(http.StatusOK))
Expand All @@ -69,6 +75,30 @@ var _ = Describe("ServiceOffering", func() {
)))
})

When("filtering query params are provided", func() {
BeforeEach(func() {
requestValidator.DecodeAndValidateURLValuesStub = decodeAndValidateURLValuesStub(&payloads.ServiceOfferingList{
Names: "a1,a2",
})
})

It("passes them to the repository", func() {
Expect(serviceOfferingRepo.ListOfferingsCallCount()).To(Equal(1))
_, _, message := serviceOfferingRepo.ListOfferingsArgsForCall(0)
Expect(message.Names).To(ConsistOf("a1", "a2"))
})
})

When("the request is invalid", func() {
BeforeEach(func() {
requestValidator.DecodeAndValidateURLValuesReturns(errors.New("invalid-request"))
})

It("returns an error", func() {
expectUnknownError()
})
})

When("listing the offerings fails", func() {
BeforeEach(func() {
serviceOfferingRepo.ListOfferingsReturns(nil, errors.New("list-err"))
Expand Down
1 change: 1 addition & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ func main() {
),
handlers.NewServiceOffering(
*serverURL,
requestValidator,
serviceOfferingRepo,
),
handlers.NewServicePlan(
Expand Down
32 changes: 32 additions & 0 deletions api/payloads/service_offering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package payloads

import (
"net/url"
"regexp"

"code.cloudfoundry.org/korifi/api/payloads/parse"
"code.cloudfoundry.org/korifi/api/repositories"
)

type ServiceOfferingList struct {
Names string
}

func (l *ServiceOfferingList) ToMessage() repositories.ListServiceOfferingMessage {
return repositories.ListServiceOfferingMessage{
Names: parse.ArrayParam(l.Names),
}
}

func (l *ServiceOfferingList) SupportedKeys() []string {
return []string{"names", "page", "per_page"}
}

func (l *ServiceOfferingList) IgnoredKeys() []*regexp.Regexp {
return []*regexp.Regexp{regexp.MustCompile(`fields\[.+\]`)}
}

func (l *ServiceOfferingList) DecodeFromURLValues(values url.Values) error {
l.Names = values.Get("names")
return nil
}
30 changes: 30 additions & 0 deletions api/payloads/service_offering_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package payloads_test

import (
"code.cloudfoundry.org/korifi/api/payloads"
"code.cloudfoundry.org/korifi/api/repositories"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("ServiceOffering", func() {
DescribeTable("valid query",
func(query string, expectedServiceOfferingList payloads.ServiceOfferingList) {
actualServiceOfferingList, decodeErr := decodeQuery[payloads.ServiceOfferingList](query)

Expect(decodeErr).NotTo(HaveOccurred())
Expect(*actualServiceOfferingList).To(Equal(expectedServiceOfferingList))
},
Entry("names", "names=b1,b2", payloads.ServiceOfferingList{Names: "b1,b2"}),
)

Describe("ToMessage", func() {
It("converts payload to repository message", func() {
payload := &payloads.ServiceOfferingList{Names: "b1,b2"}

Expect(payload.ToMessage()).To(Equal(repositories.ListServiceOfferingMessage{
Names: []string{"b1", "b2"},
}))
})
})
})
12 changes: 10 additions & 2 deletions api/repositories/service_offering_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ type ServiceOfferingRepo struct {
rootNamespace string
}

type ListServiceOfferingMessage struct {
Names []string
}

func (m *ListServiceOfferingMessage) matches(cfServiceOffering korifiv1alpha1.CFServiceOffering) bool {
return emptyOrContains(m.Names, cfServiceOffering.Spec.Name)
}

func NewServiceOfferingRepo(
userClientFactory authorization.UserK8sClientFactory,
rootNamespace string,
Expand All @@ -41,7 +49,7 @@ func NewServiceOfferingRepo(
}
}

func (r *ServiceOfferingRepo) ListOfferings(ctx context.Context, authInfo authorization.Info) ([]ServiceOfferingRecord, error) {
func (r *ServiceOfferingRepo) ListOfferings(ctx context.Context, authInfo authorization.Info, message ListServiceOfferingMessage) ([]ServiceOfferingRecord, error) {
userClient, err := r.userClientFactory.BuildClient(authInfo)
if err != nil {
return []ServiceOfferingRecord{}, fmt.Errorf("failed to build user client: %w", err)
Expand All @@ -59,7 +67,7 @@ func (r *ServiceOfferingRepo) ListOfferings(ctx context.Context, authInfo author
)
}

return iter.Map(iter.Lift(offeringsList.Items), offeringToRecord).Collect(), nil
return iter.Map(iter.Lift(offeringsList.Items).Filter(message.matches), offeringToRecord).Collect(), nil
}

func offeringToRecord(offering korifiv1alpha1.CFServiceOffering) ServiceOfferingRecord {
Expand Down
31 changes: 30 additions & 1 deletion api/repositories/service_offering_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var _ = Describe("ServiceOfferingRepo", func() {
var (
offeringGUID string
listedOfferings []repositories.ServiceOfferingRecord
message repositories.ListServiceOfferingMessage
listErr error
)

Expand Down Expand Up @@ -65,10 +66,12 @@ var _ = Describe("ServiceOfferingRepo", func() {
},
},
})).To(Succeed())

message = repositories.ListServiceOfferingMessage{}
})

JustBeforeEach(func() {
listedOfferings, listErr = repo.ListOfferings(ctx, authInfo)
listedOfferings, listErr = repo.ListOfferings(ctx, authInfo, message)
})

It("lists service offerings", func() {
Expand Down Expand Up @@ -113,5 +116,31 @@ var _ = Describe("ServiceOfferingRepo", func() {
}),
})))
})

When("filtering by name", func() {
BeforeEach(func() {
Expect(k8sClient.Create(ctx, &korifiv1alpha1.CFServiceOffering{
ObjectMeta: metav1.ObjectMeta{
Namespace: rootNamespace,
Name: uuid.NewString(),
},
Spec: korifiv1alpha1.CFServiceOfferingSpec{
ServiceOffering: services.ServiceOffering{
Name: "my-other-offering",
},
},
})).To(Succeed())

message.Names = []string{"my-offering"}
})

It("returns the matching offerings", func() {
Expect(listedOfferings).To(ConsistOf(MatchFields(IgnoreExtras, Fields{
"ServiceOffering": MatchFields(IgnoreExtras, Fields{
"Name": Equal("my-offering"),
}),
})))
})
})
})
})

0 comments on commit 0930ef1

Please sign in to comment.