Skip to content

Commit

Permalink
Enable service plan filtering by offering guid
Browse files Browse the repository at this point in the history
Co-authored-by: Georgi Sabev <[email protected]>
  • Loading branch information
danail-branekov and georgethebeatle committed Jul 30, 2024
1 parent 0930ef1 commit 0f2b71e
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 19 deletions.
18 changes: 10 additions & 8 deletions api/handlers/fake/cfservice_plan_repository.go

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

21 changes: 15 additions & 6 deletions api/handlers/service_plan.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,29 +21,37 @@ const (

//counterfeiter:generate -o fake -fake-name CFServicePlanRepository . CFServicePlanRepository
type CFServicePlanRepository interface {
ListPlans(context.Context, authorization.Info) ([]repositories.ServicePlanRecord, error)
ListPlans(context.Context, authorization.Info, repositories.ListServicePlanMessage) ([]repositories.ServicePlanRecord, error)
}

type ServicePlan struct {
serverURL url.URL
servicePlanRepo CFServicePlanRepository
serverURL url.URL
requestValidator RequestValidator
servicePlanRepo CFServicePlanRepository
}

func NewServicePlan(
serverURL url.URL,
requestValidator RequestValidator,
servicePlanRepo CFServicePlanRepository,
) *ServicePlan {
return &ServicePlan{
serverURL: serverURL,
servicePlanRepo: servicePlanRepo,
serverURL: serverURL,
requestValidator: requestValidator,
servicePlanRepo: servicePlanRepo,
}
}

func (h *ServicePlan) list(r *http.Request) (*routing.Response, error) {
authInfo, _ := authorization.InfoFromContext(r.Context())
logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.service-plan.list")

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

servicePlanList, err := h.servicePlanRepo.ListPlans(r.Context(), authInfo, payload.ToMessage())
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "Failed to list service plans")
}
Expand Down
34 changes: 32 additions & 2 deletions api/handlers/service_plan_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/tests/matchers"
Expand All @@ -15,13 +16,18 @@ import (
)

var _ = Describe("ServicePlan", func() {
var servicePlanRepo *fake.CFServicePlanRepository
var (
servicePlanRepo *fake.CFServicePlanRepository
requestValidator *fake.RequestValidator
)

BeforeEach(func() {
requestValidator = new(fake.RequestValidator)
servicePlanRepo = new(fake.CFServicePlanRepository)

apiHandler := NewServicePlan(
*serverURL,
requestValidator,
servicePlanRepo,
)
routerBuilder.LoadRoutes(apiHandler)
Expand Down Expand Up @@ -52,7 +58,7 @@ var _ = Describe("ServicePlan", func() {

It("lists the service plans", func() {
Expect(servicePlanRepo.ListPlansCallCount()).To(Equal(1))
_, actualAuthInfo := servicePlanRepo.ListPlansArgsForCall(0)
_, actualAuthInfo, _ := servicePlanRepo.ListPlansArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))

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

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

It("passes them to the repository", func() {
Expect(servicePlanRepo.ListPlansCallCount()).To(Equal(1))
_, _, message := servicePlanRepo.ListPlansArgsForCall(0)
Expect(message.ServiceOfferingGUIDs).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 plans fails", func() {
BeforeEach(func() {
servicePlanRepo.ListPlansReturns(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 @@ -428,6 +428,7 @@ func main() {
),
handlers.NewServicePlan(
*serverURL,
requestValidator,
servicePlanRepo,
),
}
Expand Down
32 changes: 32 additions & 0 deletions api/payloads/service_plan.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 ServicePlanList struct {
ServiceOfferingGUIDs string
}

func (l *ServicePlanList) ToMessage() repositories.ListServicePlanMessage {
return repositories.ListServicePlanMessage{
ServiceOfferingGUIDs: parse.ArrayParam(l.ServiceOfferingGUIDs),
}
}

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

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

func (l *ServicePlanList) DecodeFromURLValues(values url.Values) error {
l.ServiceOfferingGUIDs = values.Get("service_offering_guids")
return nil
}
30 changes: 30 additions & 0 deletions api/payloads/service_plan_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("ServicePlan", func() {
DescribeTable("valid query",
func(query string, expectedServicePlanList payloads.ServicePlanList) {
actualServicePlanList, decodeErr := decodeQuery[payloads.ServicePlanList](query)

Expect(decodeErr).NotTo(HaveOccurred())
Expect(*actualServicePlanList).To(Equal(expectedServicePlanList))
},
Entry("names", "names=b1,b2", payloads.ServicePlanList{ServiceOfferingGUIDs: "b1,b2"}),
)

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

Expect(payload.ToMessage()).To(Equal(repositories.ListServicePlanMessage{
ServiceOfferingGUIDs: []string{"b1", "b2"},
}))
})
})
})
1 change: 1 addition & 0 deletions api/repositories/service_offering_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ var _ = Describe("ServiceOfferingRepo", func() {
})

It("returns the matching offerings", func() {
Expect(listErr).NotTo(HaveOccurred())
Expect(listedOfferings).To(ConsistOf(MatchFields(IgnoreExtras, Fields{
"ServiceOffering": MatchFields(IgnoreExtras, Fields{
"Name": Equal("my-offering"),
Expand Down
12 changes: 10 additions & 2 deletions api/repositories/service_plan_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ func NewServicePlanRepo(
}
}

func (r *ServicePlanRepo) ListPlans(ctx context.Context, authInfo authorization.Info) ([]ServicePlanRecord, error) {
type ListServicePlanMessage struct {
ServiceOfferingGUIDs []string
}

func (m *ListServicePlanMessage) matches(cfServicePlan korifiv1alpha1.CFServicePlan) bool {
return emptyOrContains(m.ServiceOfferingGUIDs, cfServicePlan.Labels[korifiv1alpha1.RelServiceOfferingLabel])
}

func (r *ServicePlanRepo) ListPlans(ctx context.Context, authInfo authorization.Info, message ListServicePlanMessage) ([]ServicePlanRecord, error) {
userClient, err := r.userClientFactory.BuildClient(authInfo)
if err != nil {
return nil, fmt.Errorf("failed to build user client: %w", err)
Expand All @@ -51,7 +59,7 @@ func (r *ServicePlanRepo) ListPlans(ctx context.Context, authInfo authorization.
return nil, apierrors.FromK8sError(err, ServicePlanResourceType)
}

return iter.Map(iter.Lift(cfServicePlans.Items), planToRecord).Collect(), nil
return iter.Map(iter.Lift(cfServicePlans.Items).Filter(message.matches), planToRecord).Collect(), nil
}

func planToRecord(plan korifiv1alpha1.CFServicePlan) ServicePlanRecord {
Expand Down
34 changes: 33 additions & 1 deletion api/repositories/service_plan_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var _ = Describe("ServicePlanRepo", func() {
var (
planGUID string
listedPlans []repositories.ServicePlanRecord
message repositories.ListServicePlanMessage
listErr error
)

Expand Down Expand Up @@ -82,10 +83,12 @@ var _ = Describe("ServicePlanRepo", func() {
},
},
})).To(Succeed())

message = repositories.ListServicePlanMessage{}
})

JustBeforeEach(func() {
listedPlans, listErr = repo.ListPlans(ctx, authInfo)
listedPlans, listErr = repo.ListPlans(ctx, authInfo, message)
})

It("lists service offerings", func() {
Expand Down Expand Up @@ -148,5 +151,34 @@ var _ = Describe("ServicePlanRepo", func() {
}),
})))
})

When("filtering by service_offering_guid", func() {
BeforeEach(func() {
Expect(k8sClient.Create(ctx, &korifiv1alpha1.CFServicePlan{
ObjectMeta: metav1.ObjectMeta{
Namespace: rootNamespace,
Name: uuid.NewString(),
Labels: map[string]string{
korifiv1alpha1.RelServiceOfferingLabel: "other-offering-guid",
},
},
})).To(Succeed())

message.ServiceOfferingGUIDs = []string{"other-offering-guid"}
})

It("returns matching service plans", func() {
Expect(listErr).NotTo(HaveOccurred())
Expect(listedPlans).To(ConsistOf(MatchFields(IgnoreExtras, Fields{
"Relationships": Equal(repositories.ServicePlanRelationships{
ServiceOffering: model.ToOneRelationship{
Data: model.Relationship{
GUID: "other-offering-guid",
},
},
}),
})))
})
})
})
})

0 comments on commit 0f2b71e

Please sign in to comment.