Skip to content

Commit

Permalink
Add DELETE v3/service_plans/:guid/visibility/:org_guid
Browse files Browse the repository at this point in the history
  • Loading branch information
klapkov committed Dec 3, 2024
1 parent c3efa00 commit 804fba2
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 5 deletions.
78 changes: 78 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.

31 changes: 26 additions & 5 deletions api/handlers/service_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import (
)

const (
ServicePlansPath = "/v3/service_plans"
ServicePlanVisivilityPath = "/v3/service_plans/{guid}/visibility"
ServicePlansPath = "/v3/service_plans"
ServicePlanVisibilityPath = "/v3/service_plans/{guid}/visibility"
ServicePlanVisibilityOrgPath = ServicePlanVisibilityPath + "/{org-guid}"
)

//counterfeiter:generate -o fake -fake-name CFServicePlanRepository . CFServicePlanRepository
Expand All @@ -27,6 +28,7 @@ type CFServicePlanRepository interface {
ListPlans(context.Context, authorization.Info, repositories.ListServicePlanMessage) ([]repositories.ServicePlanRecord, error)
ApplyPlanVisibility(context.Context, authorization.Info, repositories.ApplyServicePlanVisibilityMessage) (repositories.ServicePlanRecord, error)
UpdatePlanVisibility(context.Context, authorization.Info, repositories.UpdateServicePlanVisibilityMessage) (repositories.ServicePlanRecord, error)
DeletePlanVisibility(context.Context, authorization.Info, repositories.DeleteServicePlanVisibilityMessage) error
}

type ServicePlan struct {
Expand Down Expand Up @@ -130,15 +132,34 @@ func (h *ServicePlan) updatePlanVisibility(r *http.Request) (*routing.Response,
return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServicePlanVisibility(visibility, h.serverURL)), nil
}

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

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

if err := h.servicePlanRepo.DeletePlanVisibility(r.Context(), authInfo, repositories.DeleteServicePlanVisibilityMessage{
PlanGUID: planGUID,
OrgGUID: orgGUID,
}); err != nil {
return nil, apierrors.LogAndReturn(logger, err, "failed to delete org: %s for plan visibility", orgGUID)
}

return routing.NewResponse(http.StatusNoContent), 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},
{Method: "GET", Pattern: ServicePlanVisivilityPath, Handler: h.getPlanVisibility},
{Method: "POST", Pattern: ServicePlanVisivilityPath, Handler: h.applyPlanVisibility},
{Method: "PATCH", Pattern: ServicePlanVisivilityPath, Handler: h.updatePlanVisibility},
{Method: "GET", Pattern: ServicePlanVisibilityPath, Handler: h.getPlanVisibility},
{Method: "POST", Pattern: ServicePlanVisibilityPath, Handler: h.applyPlanVisibility},
{Method: "PATCH", Pattern: ServicePlanVisibilityPath, Handler: h.updatePlanVisibility},
{Method: "DELETE", Pattern: ServicePlanVisibilityOrgPath, Handler: h.deletePlanVisibility},
}
}
43 changes: 43 additions & 0 deletions api/handlers/service_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"strings"

apierrors "code.cloudfoundry.org/korifi/api/errors"
. "code.cloudfoundry.org/korifi/api/handlers"
"code.cloudfoundry.org/korifi/api/handlers/fake"
"code.cloudfoundry.org/korifi/api/payloads"
Expand Down Expand Up @@ -350,4 +351,46 @@ var _ = Describe("ServicePlan", func() {
})
})
})

Describe("DELETE /v3/service_plans/{guid}/visibility/{org-guid}", func() {
BeforeEach(func() {
servicePlanRepo.DeletePlanVisibilityReturns(nil)
})

JustBeforeEach(func() {
req, err := http.NewRequestWithContext(ctx, "DELETE", "/v3/service_plans/my-service-plan/visibility/org-guid", nil)
Expect(err).NotTo(HaveOccurred())

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

It("deletes the service plan visibility", func() {
Expect(servicePlanRepo.DeletePlanVisibilityCallCount()).To(Equal(1))
_, actualAuthInfo, actualMessage := servicePlanRepo.DeletePlanVisibilityArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))
Expect(actualMessage.PlanGUID).To(Equal("my-service-plan"))
Expect(actualMessage.OrgGUID).To(Equal("org-guid"))
Expect(rr).To(HaveHTTPStatus(http.StatusNoContent))
})

When("deleting the visibility fails with not found", func() {
BeforeEach(func() {
servicePlanRepo.DeletePlanVisibilityReturns(apierrors.NewNotFoundError(nil, repositories.ServicePlanVisibilityResourceType))
})

It("returns 404 Not Found", func() {
expectNotFoundError("Service Plan Visibility")
})
})

When("deleting the visibility fails with an error", func() {
BeforeEach(func() {
servicePlanRepo.DeletePlanVisibilityReturns(errors.New("visibility-err"))
})

It("returns an error", func() {
expectUnknownError()
})
})
})
})
21 changes: 21 additions & 0 deletions api/repositories/service_plan_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ func (m *UpdateServicePlanVisibilityMessage) apply(cfServicePlan *korifiv1alpha1
cfServicePlan.Spec.Visibility.Organizations = tools.Uniq(m.Organizations)
}

type DeleteServicePlanVisibilityMessage struct {
PlanGUID string
OrgGUID string
}

func (m *DeleteServicePlanVisibilityMessage) apply(cfServicePlan *korifiv1alpha1.CFServicePlan) {
for i, org := range cfServicePlan.Spec.Visibility.Organizations {
if org == m.OrgGUID {
cfServicePlan.Spec.Visibility.Organizations = append(cfServicePlan.Spec.Visibility.Organizations[:i], cfServicePlan.Spec.Visibility.Organizations[i+1:]...)
}
}
}

func NewServicePlanRepo(
userClientFactory authorization.UserK8sClientFactory,
rootNamespace string,
Expand Down Expand Up @@ -155,6 +168,14 @@ func (r *ServicePlanRepo) UpdatePlanVisibility(ctx context.Context, authInfo aut
return r.patchServicePlan(ctx, authInfo, message.PlanGUID, message.apply)
}

func (r *ServicePlanRepo) DeletePlanVisibility(ctx context.Context, authInfo authorization.Info, message DeleteServicePlanVisibilityMessage) error {
if _, err := r.patchServicePlan(ctx, authInfo, message.PlanGUID, message.apply); err != nil {
return err
}

return nil
}

func (r *ServicePlanRepo) patchServicePlan(
ctx context.Context,
authInfo authorization.Info,
Expand Down
93 changes: 93 additions & 0 deletions api/repositories/service_plan_repository_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package repositories_test

import (
"errors"

apierrors "code.cloudfoundry.org/korifi/api/errors"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/api/repositories/fakeawaiter"
Expand Down Expand Up @@ -600,5 +602,96 @@ var _ = Describe("ServicePlanRepo", func() {
})
})
})

Describe("DeletePlanVisibility", func() {
BeforeEach(func() {
cfServicePlan := &korifiv1alpha1.CFServicePlan{
ObjectMeta: metav1.ObjectMeta{
Namespace: rootNamespace,
Name: planGUID,
},
}
Expect(k8s.PatchResource(ctx, k8sClient, cfServicePlan, func() {
cfServicePlan.Spec.Visibility.Type = visibilityType
cfServicePlan.Spec.Visibility.Organizations = []string{cfOrg.Name, anotherOrg.Name}
})).To(Succeed())
})

When("the user is not authorized", func() {
BeforeEach(func() {
visibilityErr = repo.DeletePlanVisibility(ctx, authInfo, repositories.DeleteServicePlanVisibilityMessage{
PlanGUID: planGUID,
OrgGUID: anotherOrg.Name,
})
})

It("returns unauthorized error", func() {
Expect(visibilityErr).To(matchers.WrapErrorAssignableToTypeOf(apierrors.ForbiddenError{}))
})
})

When("The user has persmissions", func() {
BeforeEach(func() {
createRoleBinding(ctx, userName, adminRole.Name, rootNamespace)
})

When("the plan and org visibility exist", func() {
BeforeEach(func() {
visibilityErr = repo.DeletePlanVisibility(ctx, authInfo, repositories.DeleteServicePlanVisibilityMessage{
PlanGUID: planGUID,
OrgGUID: anotherOrg.Name,
})
})

It("deletes the plan visibility in kubernetes", func() {
Expect(visibilityErr).ToNot(HaveOccurred())

servicePlan := &korifiv1alpha1.CFServicePlan{
ObjectMeta: metav1.ObjectMeta{
Namespace: rootNamespace,
Name: planGUID,
},
}
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(servicePlan), servicePlan)).To(Succeed())
Expect(servicePlan.Spec.Visibility.Organizations).To(Equal([]string{cfOrg.Name}))
})
})

When("the plan does not exist", func() {
JustBeforeEach(func() {
visibilityErr = repo.DeletePlanVisibility(ctx, authInfo, repositories.DeleteServicePlanVisibilityMessage{
PlanGUID: "does-not-exist",
OrgGUID: anotherOrg.Name,
})
})

It("returns an NotFoundError", func() {
Expect(errors.As(visibilityErr, &apierrors.NotFoundError{})).To(BeTrue())
})
})

When("the org does not exist", func() {
BeforeEach(func() {
visibilityErr = repo.DeletePlanVisibility(ctx, authInfo, repositories.DeleteServicePlanVisibilityMessage{
PlanGUID: planGUID,
OrgGUID: "does-not-exist",
})
})

It("does not change the visibility orgs", func() {
Expect(visibilityErr).ToNot(HaveOccurred())

servicePlan := &korifiv1alpha1.CFServicePlan{
ObjectMeta: metav1.ObjectMeta{
Namespace: rootNamespace,
Name: planGUID,
},
}
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(servicePlan), servicePlan)).To(Succeed())
Expect(servicePlan.Spec.Visibility.Organizations).To(Equal([]string{cfOrg.Name, anotherOrg.Name}))
})
})
})
})
})
})

0 comments on commit 804fba2

Please sign in to comment.