Skip to content

Commit

Permalink
Implement GET /v3/service_plans
Browse files Browse the repository at this point in the history
fixes #3265

Co-authored-by: Danail Branekov <danailster@gmail.com>
  • Loading branch information
georgethebeatle and danail-branekov committed Jul 9, 2024
1 parent e9a2f23 commit 6c60159
Show file tree
Hide file tree
Showing 31 changed files with 1,394 additions and 36 deletions.
121 changes: 121 additions & 0 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.

1 change: 1 addition & 0 deletions api/handlers/service_offering.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// nolint:dupl
package handlers

import (
Expand Down
11 changes: 11 additions & 0 deletions api/handlers/service_offering_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers_test

import (
"errors"
"net/http"

. "code.cloudfoundry.org/korifi/api/handlers"
Expand Down Expand Up @@ -67,5 +68,15 @@ var _ = Describe("ServiceOffering", func() {
MatchJSONPath("$.resources[0].links.service_broker.href", "https://api.example.org/v3/service_brokers/broker-guid"),
)))
})

When("listing the offerings fails", func() {
BeforeEach(func() {
serviceOfferingRepo.ListOfferingsReturns(nil, errors.New("list-err"))
})

It("returns an error", func() {
expectUnknownError()
})
})
})
})
61 changes: 61 additions & 0 deletions api/handlers/service_plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// nolint:dupl
package handlers

import (
"context"
"net/http"
"net/url"

"code.cloudfoundry.org/korifi/api/authorization"
apierrors "code.cloudfoundry.org/korifi/api/errors"
"code.cloudfoundry.org/korifi/api/presenter"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/api/routing"
"github.com/go-logr/logr"
)

const (
ServicePlansPath = "/v3/service_plans"
)

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

type ServicePlan struct {
serverURL url.URL
servicePlanRepo CFServicePlanRepository
}

func NewServicePlan(
serverURL url.URL,
servicePlanRepo CFServicePlanRepository,
) *ServicePlan {
return &ServicePlan{
serverURL: serverURL,
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)
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "Failed to list service plans")
}

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForList(presenter.ForServicePlan, servicePlanList, h.serverURL, *r.URL)), nil
}

func (h *ServicePlan) UnauthenticatedRoutes() []routing.Route {
return nil
}

func (h *ServicePlan) AuthenticatedRoutes() []routing.Route {
return []routing.Route{
{Method: "GET", Pattern: ServicePlansPath, Handler: h.list},
}
}
79 changes: 79 additions & 0 deletions api/handlers/service_plan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package handlers_test

import (
"errors"
"net/http"

. "code.cloudfoundry.org/korifi/api/handlers"
"code.cloudfoundry.org/korifi/api/handlers/fake"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/model"
. "code.cloudfoundry.org/korifi/tests/matchers"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

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

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

apiHandler := NewServicePlan(
*serverURL,
servicePlanRepo,
)
routerBuilder.LoadRoutes(apiHandler)
})

Describe("GET /v3/service_plans", func() {
BeforeEach(func() {
servicePlanRepo.ListPlansReturns([]repositories.ServicePlanResource{{
CFResource: model.CFResource{
GUID: "plan-guid",
},
Relationships: repositories.ServicePlanRelationships{
ServiceOffering: model.ToOneRelationship{
Data: model.Relationship{
GUID: "service-offering-guid",
},
},
},
}}, nil)
})

JustBeforeEach(func() {
req, err := http.NewRequestWithContext(ctx, "GET", "/v3/service_plans", nil)
Expect(err).NotTo(HaveOccurred())

routerBuilder.Build().ServeHTTP(rr, req)
})

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

Expect(rr).Should(HaveHTTPStatus(http.StatusOK))
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))
Expect(rr).To(HaveHTTPBody(SatisfyAll(
MatchJSONPath("$.pagination.total_results", BeEquivalentTo(1)),
MatchJSONPath("$.pagination.first.href", "https://api.example.org/v3/service_plans"),
MatchJSONPath("$.resources[0].guid", "plan-guid"),
MatchJSONPath("$.resources[0].links.self.href", "https://api.example.org/v3/service_plans/plan-guid"),
MatchJSONPath("$.resources[0].links.service_offering.href", "https://api.example.org/v3/service_offerings/service-offering-guid"),
)))
})

When("listing the plans fails", func() {
BeforeEach(func() {
servicePlanRepo.ListPlansReturns(nil, errors.New("list-err"))
})

It("returns an error", func() {
expectUnknownError()
})
})
})
})
5 changes: 5 additions & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func main() {
metricsRepo := repositories.NewMetricsRepo(userClientFactory)
serviceBrokerRepo := repositories.NewServiceBrokerRepo(userClientFactory, cfg.RootNamespace)
serviceOfferingRepo := repositories.NewServiceOfferingRepo(userClientFactory, cfg.RootNamespace)
servicePlanRepo := repositories.NewServicePlanRepo(userClientFactory, cfg.RootNamespace)

processStats := actions.NewProcessStats(processRepo, appRepo, metricsRepo)
manifest := actions.NewManifest(
Expand Down Expand Up @@ -420,6 +421,10 @@ func main() {
*serverURL,
serviceOfferingRepo,
),
handlers.NewServicePlan(
*serverURL,
servicePlanRepo,
),
}
for _, handler := range apiHandlers {
routerBuilder.LoadRoutes(handler)
Expand Down
16 changes: 16 additions & 0 deletions api/presenter/service_offering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ var _ = Describe("Service Offering", func() {
GUID: "resource-guid",
CreatedAt: time.UnixMilli(1000),
UpdatedAt: tools.PtrTo(time.UnixMilli(2000)),
Metadata: model.Metadata{
Labels: map[string]string{
"label": "label-foo",
},
Annotations: map[string]string{
"annotation": "annotation-bar",
},
},
},
Relationships: repositories.ServiceOfferingRelationships{
ServiceBroker: model.ToOneRelationship{
Expand Down Expand Up @@ -100,6 +108,14 @@ var _ = Describe("Service Offering", func() {
"Status": 0,
"Details": ""
},
"metadata": {
"labels": {
"label": "label-foo"
},
"annotations": {
"annotation": "annotation-bar"
}
},
"relationships": {
"service_broker": {
"data": {
Expand Down
37 changes: 37 additions & 0 deletions api/presenter/service_plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package presenter

import (
"net/url"

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

type ServicePlanLinks struct {
Self Link `json:"self"`
ServiceOffering Link `json:"service_offering"`
}

type ServicePlanResponse struct {
repositories.ServicePlanResource
Links ServicePlanLinks `json:"links"`
}

func ForServicePlan(servicePlanResource repositories.ServicePlanResource, baseURL url.URL) ServicePlanResponse {
return ServicePlanResponse{
ServicePlanResource: servicePlanResource,
Links: ServicePlanLinks{
Self: Link{
HRef: buildURL(baseURL).appendPath(servicePlansBase, servicePlanResource.GUID).build(),
},
ServiceOffering: Link{
HRef: buildURL(baseURL).appendPath(serviceOfferingsBase, servicePlanResource.Relationships.ServiceOffering.Data.GUID).build(),
},
},
}
}

func ForServicePlanList(servicePlanResourceList []repositories.ServicePlanResource, baseURL, requestURL url.URL) ListResponse[ServicePlanResponse] {
return ForList(func(servicePlanResource repositories.ServicePlanResource, baseURL url.URL) ServicePlanResponse {
return ForServicePlan(servicePlanResource, baseURL)
}, servicePlanResourceList, baseURL, requestURL)
}
Loading

0 comments on commit 6c60159

Please sign in to comment.