Skip to content

Commit

Permalink
Implement POST /v3/service_plans/{guid}/visibility
Browse files Browse the repository at this point in the history
- For now `public` is the only allowd value other than `admin`, which is
  the default
- Bonus: Introduce the `visibility_type` field in the service plan
  resource. This makes `cf service-access` display the plan visibility
  correctly

fixes #3275

Co-authored-by: Georgi Sabev <georgethebeatle@gmail.com>
  • Loading branch information
danail-branekov and georgethebeatle committed Aug 5, 2024
1 parent b6bd5dc commit fc29672
Show file tree
Hide file tree
Showing 18 changed files with 585 additions and 208 deletions.
171 changes: 127 additions & 44 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.

26 changes: 24 additions & 2 deletions api/handlers/service_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ const (

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

type ServicePlan struct {
Expand Down Expand Up @@ -68,11 +69,31 @@ func (h *ServicePlan) getPlanVisibility(r *http.Request) (*routing.Response, err
planGUID := routing.URLParam(r, "guid")
logger = logger.WithValues("guid", planGUID)

visibility, err := h.servicePlanRepo.GetPlanVisibility(r.Context(), authInfo, planGUID)
plan, err := h.servicePlanRepo.GetPlan(r.Context(), authInfo, planGUID)
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "failed to get plan visibility")
}

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServicePlanVisibility(plan, h.serverURL)), nil
}

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

planGUID := routing.URLParam(r, "guid")
logger = logger.WithValues("guid", planGUID)

payload := payloads.ServicePlanVisibility{}
if err := h.requestValidator.DecodeAndValidateJSONPayload(r, &payload); err != nil {
return nil, apierrors.LogAndReturn(logger, err, "failed to decode json payload")
}

visibility, err := h.servicePlanRepo.ApplyPlanVisibility(r.Context(), authInfo, payload.ToMessage(planGUID))
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "failed to apply plan visibility")
}

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServicePlanVisibility(visibility, h.serverURL)), nil
}

Expand All @@ -84,5 +105,6 @@ func (h *ServicePlan) AuthenticatedRoutes() []routing.Route {
return []routing.Route{
{Method: "GET", Pattern: ServicePlansPath, Handler: h.list},
{Method: "GET", Pattern: ServicePlanVisivilityPath, Handler: h.getPlanVisibility},
{Method: "POST", Pattern: ServicePlanVisivilityPath, Handler: h.applyPlanVisibility},
}
}
73 changes: 68 additions & 5 deletions api/handlers/service_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers_test
import (
"errors"
"net/http"
"strings"

. "code.cloudfoundry.org/korifi/api/handlers"
"code.cloudfoundry.org/korifi/api/handlers/fake"
Expand Down Expand Up @@ -110,8 +111,8 @@ var _ = Describe("ServicePlan", func() {

Describe("GET /v3/service_plans/{guid}/visibility", func() {
BeforeEach(func() {
servicePlanRepo.GetPlanVisibilityReturns(repositories.ServicePlanVisibilityRecord{
Type: korifiv1alpha1.AdminServicePlanVisibilityType,
servicePlanRepo.GetPlanReturns(repositories.ServicePlanRecord{
VisibilityType: korifiv1alpha1.AdminServicePlanVisibilityType,
}, nil)
})

Expand All @@ -123,8 +124,8 @@ var _ = Describe("ServicePlan", func() {
})

It("returns the plan visibility", func() {
Expect(servicePlanRepo.GetPlanVisibilityCallCount()).To(Equal(1))
_, actualAuthInfo, actualPlanID := servicePlanRepo.GetPlanVisibilityArgsForCall(0)
Expect(servicePlanRepo.GetPlanCallCount()).To(Equal(1))
_, actualAuthInfo, actualPlanID := servicePlanRepo.GetPlanArgsForCall(0)
Expect(actualPlanID).To(Equal("my-service-plan"))
Expect(actualAuthInfo).To(Equal(authInfo))

Expand All @@ -138,7 +139,69 @@ var _ = Describe("ServicePlan", func() {

When("getting the visibility fails", func() {
BeforeEach(func() {
servicePlanRepo.GetPlanVisibilityReturns(repositories.ServicePlanVisibilityRecord{}, errors.New("visibility-err"))
servicePlanRepo.GetPlanReturns(repositories.ServicePlanRecord{}, errors.New("visibility-err"))
})

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

Describe("POST /v3/service_plans/{guid}/visibility", func() {
BeforeEach(func() {
requestValidator.DecodeAndValidateJSONPayloadStub = decodeAndValidatePayloadStub(&payloads.ServicePlanVisibility{
Type: korifiv1alpha1.PublicServicePlanVisibilityType,
})

servicePlanRepo.ApplyPlanVisibilityReturns(repositories.ServicePlanRecord{
VisibilityType: korifiv1alpha1.PublicServicePlanVisibilityType,
}, nil)
})

JustBeforeEach(func() {
req, err := http.NewRequestWithContext(ctx, "POST", "/v3/service_plans/my-service-plan/visibility", strings.NewReader("the-payload"))
Expect(err).NotTo(HaveOccurred())

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

It("validates the payload", func() {
Expect(requestValidator.DecodeAndValidateJSONPayloadCallCount()).To(Equal(1))
actualReq, _ := requestValidator.DecodeAndValidateJSONPayloadArgsForCall(0)
Expect(bodyString(actualReq)).To(Equal("the-payload"))
})

It("updates the plan visibility", func() {
Expect(servicePlanRepo.ApplyPlanVisibilityCallCount()).To(Equal(1))
_, actualAuthInfo, actualMessage := servicePlanRepo.ApplyPlanVisibilityArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))
Expect(actualMessage).To(Equal(repositories.ApplyServicePlanVisibilityMessage{
PlanGUID: "my-service-plan",
Type: korifiv1alpha1.PublicServicePlanVisibilityType,
}))

Expect(rr).To(HaveHTTPStatus(http.StatusOK))
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))

Expect(rr).To(HaveHTTPBody(SatisfyAll(
MatchJSONPath("$.type", korifiv1alpha1.PublicServicePlanVisibilityType),
)))
})

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

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

When("updating the visibility fails", func() {
BeforeEach(func() {
servicePlanRepo.ApplyPlanVisibilityReturns(repositories.ServicePlanRecord{}, errors.New("visibility-err"))
})

It("returns an error", func() {
Expand Down
11 changes: 11 additions & 0 deletions api/payloads/service_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ func (l *ServicePlanList) DecodeFromURLValues(values url.Values) error {
l.ServiceOfferingGUIDs = values.Get("service_offering_guids")
return nil
}

type ServicePlanVisibility struct {
Type string `json:"type"`
}

func (p *ServicePlanVisibility) ToMessage(planGUID string) repositories.ApplyServicePlanVisibilityMessage {
return repositories.ApplyServicePlanVisibilityMessage{
PlanGUID: planGUID,
Type: p.Type,
}
}
Loading

0 comments on commit fc29672

Please sign in to comment.