Skip to content

Commit

Permalink
Added unit tests for the new code
Browse files Browse the repository at this point in the history
  • Loading branch information
liorokman committed Aug 16, 2018
1 parent 9f046d0 commit d8f27d6
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 20 deletions.
21 changes: 17 additions & 4 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func AttachRoutes(router *mux.Router, serviceBroker ServiceBroker, logger lager.
router.HandleFunc("/v2/service_instances/{instance_id}/last_operation", handler.lastOperation).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}", handler.update).Methods("PATCH")

router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", handler.getBinding).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", handler.bind).Methods("PUT")
router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", handler.unbind).Methods("DELETE")

Expand All @@ -104,6 +105,7 @@ func (h serviceBrokerHandler) catalog(w http.ResponseWriter, req *http.Request)
logger := h.logger.Session(catalogLogKey, lager.Data{})

if _, err := checkBrokerAPIVersionHdr(req); err != nil {
logger.Error("Check failed", err)
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
Description: err.Error(),
})
Expand Down Expand Up @@ -449,7 +451,7 @@ func (h serviceBrokerHandler) bind(w http.ResponseWriter, req *http.Request) {

asyncAllowed := req.FormValue("accepts_incomplete") == "true"
if asyncAllowed && versionCompatibility.Minor < 14 {
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
h.respond(w, http.StatusUnprocessableEntity, ErrorResponse{
Description: "async binding only supported from OSB version 2.14 and up",
})
logger.Error(apiVersionInvalidKey, err)
Expand Down Expand Up @@ -484,6 +486,7 @@ func (h serviceBrokerHandler) bind(w http.ResponseWriter, req *http.Request) {
h.respond(w, http.StatusAccepted, AsyncBindResponse{
OperationData: binding.OperationData,
})
return
}

if versionCompatibility.Minor == 8 || versionCompatibility.Minor == 9 {
Expand Down Expand Up @@ -568,7 +571,7 @@ func (h serviceBrokerHandler) unbind(w http.ResponseWriter, req *http.Request) {

asyncAllowed := req.FormValue("accepts_incomplete") == "true"
if asyncAllowed && versionCompatibility.Minor < 14 {
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
h.respond(w, http.StatusUnprocessableEntity, ErrorResponse{
Description: "async unbinding only supported from OSB version 2.14 and up",
})
logger.Error(apiVersionInvalidKey, err)
Expand Down Expand Up @@ -614,13 +617,21 @@ func (h serviceBrokerHandler) lastBindingOperation(w http.ResponseWriter, req *h
instanceIDLogKey: instanceID,
})

if _, err := checkBrokerAPIVersionHdr(req); err != nil {
versionCompatibility, err := checkBrokerAPIVersionHdr(req)
if err != nil {
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
Description: err.Error(),
})
logger.Error(apiVersionInvalidKey, err)
return
}
if versionCompatibility.Minor < 14 {
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
Description: "get binding endpoint only supported starting with OSB version 2.14",
})
logger.Error(apiVersionInvalidKey, err)
return
}

logger.Info("starting-check-for-binding-operation")

Expand Down Expand Up @@ -721,7 +732,9 @@ func checkBrokerAPIVersionHdr(req *http.Request) (brokerVersion, error) {
if apiVersion == "" {
return version, errors.New("X-Broker-API-Version Header not set")
}
fmt.Sscanf("%d.%d", apiVersion, &version.Major, &version.Minor)
if n, err := fmt.Sscanf(apiVersion, "%d.%d", &version.Major, &version.Minor); err != nil || n < 2 {
return version, errors.New("X-Broker-API-Version Header must contain a version")
}

if version.Major != 2 {
return version, errors.New("X-Broker-API-Version Header must be 2.x")
Expand Down
133 changes: 122 additions & 11 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ var _ = Describe("Service Broker API", func() {
makeRequest := func(method, path, body string) *httptest.ResponseRecorder {
recorder := httptest.NewRecorder()
request, _ := http.NewRequest(method, path, strings.NewReader(body))
request.Header.Add("X-Broker-API-Version", "2.13")
request.Header.Add("X-Broker-API-Version", "2.14")
request.SetBasicAuth(credentials.Username, credentials.Password)
request = request.WithContext(ctx)
brokerAPI.ServeHTTP(recorder, request)
Expand Down Expand Up @@ -168,6 +168,16 @@ var _ = Describe("Service Broker API", func() {
Expect(fakeServiceBroker.ReceivedContext).To(BeTrue())
})

Specify("a get binding operation endpoint which passes the request context to the broker", func() {
makeRequest("GET", "/v2/service_instances/instance-id/service_bindings/binding-id", "{}")
Expect(fakeServiceBroker.ReceivedContext).To(BeTrue())
})

Specify("a last binding operation endpoint which passes the request context to the broker", func() {
makeRequest("GET", "/v2/service_instances/instance-id/service_bindings/binding-id/last_operation", "{}")
Expect(fakeServiceBroker.ReceivedContext).To(BeTrue())
})

Specify("a last operation endpoint which passes the request context to the broker", func() {
makeRequest("GET", "/v2/service_instances/instance-id/last_operation", "{}")
Expect(fakeServiceBroker.ReceivedContext).To(BeTrue())
Expand Down Expand Up @@ -1199,11 +1209,56 @@ var _ = Describe("Service Broker API", func() {
})

Describe("binding lifecycle endpoint", func() {
makeBindingRequestWithSpecificAPIVersion := func(instanceID, bindingID string, details map[string]interface{}, apiVersion string) *testflight.Response {

makeLastBindingOperationRequestWithSpecificAPIVersion := func(instanceID, bindingID string, apiVersion string) *testflight.Response {
response := &testflight.Response{}
testflight.WithServer(brokerAPI, func(r *testflight.Requester) {
path := fmt.Sprintf("/v2/service_instances/%s/service_bindings/%s/last_operation", instanceID, bindingID)

buffer := &bytes.Buffer{}

request, err := http.NewRequest("GET", path, buffer)

Expect(err).NotTo(HaveOccurred())

if apiVersion != "" {
request.Header.Add("X-Broker-Api-Version", apiVersion)
}
request.Header.Add("Content-Type", "application/json")
request.SetBasicAuth("username", "password")

response = r.Do(request)
})
return response
}

makeGetBindingRequestWithSpecificAPIVersion := func(instanceID, bindingID string, apiVersion string) *testflight.Response {
response := &testflight.Response{}
testflight.WithServer(brokerAPI, func(r *testflight.Requester) {
path := fmt.Sprintf("/v2/service_instances/%s/service_bindings/%s",
instanceID, bindingID)
path := fmt.Sprintf("/v2/service_instances/%s/service_bindings/%s", instanceID, bindingID)

buffer := &bytes.Buffer{}

request, err := http.NewRequest("GET", path, buffer)

Expect(err).NotTo(HaveOccurred())

if apiVersion != "" {
request.Header.Add("X-Broker-Api-Version", apiVersion)
}
request.Header.Add("Content-Type", "application/json")
request.SetBasicAuth("username", "password")

response = r.Do(request)
})
return response
}

makeBindingRequestWithSpecificAPIVersion := func(instanceID, bindingID string, details map[string]interface{}, apiVersion string, async bool) *testflight.Response {
response := &testflight.Response{}
testflight.WithServer(brokerAPI, func(r *testflight.Requester) {
path := fmt.Sprintf("/v2/service_instances/%s/service_bindings/%s?accepts_incomplete=%v",
instanceID, bindingID, async)

buffer := &bytes.Buffer{}

Expand All @@ -1227,7 +1282,11 @@ var _ = Describe("Service Broker API", func() {
}

makeBindingRequest := func(instanceID, bindingID string, details map[string]interface{}) *testflight.Response {
return makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, details, "2.10")
return makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, details, "2.10", false)
}

makeAsyncBindingRequest := func(instanceID, bindingID string, details map[string]interface{}) *testflight.Response {
return makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, details, "2.14", true)
}

Describe("binding", func() {
Expand Down Expand Up @@ -1256,28 +1315,28 @@ var _ = Describe("Service Broker API", func() {
})

It("missing header X-Broker-API-Version", func() {
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{}, "")
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{}, "", false)
Expect(response.StatusCode).To(Equal(412))
Expect(lastLogLine().Message).To(ContainSubstring(".bind.broker-api-version-invalid"))
Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set"))
})

It("has wrong version of API", func() {
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{}, "1.14")
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{}, "1.14", false)
Expect(response.StatusCode).To(Equal(412))
Expect(lastLogLine().Message).To(ContainSubstring(".bind.broker-api-version-invalid"))
Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x"))
})

It("missing service-id", func() {
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{"plan_id": "123"}, "2.14")
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{"plan_id": "123"}, "2.14", false)
Expect(response.StatusCode).To(Equal(400))
Expect(lastLogLine().Message).To(ContainSubstring(".bind.service-id-missing"))
Expect(lastLogLine().Data["error"]).To(ContainSubstring("service_id missing"))
})

It("missing plan-id", func() {
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{"service_id": "123"}, "2.14")
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{"service_id": "123"}, "2.14", false)
Expect(response.StatusCode).To(Equal(400))
Expect(lastLogLine().Message).To(ContainSubstring(".bind.plan-id-missing"))
Expect(lastLogLine().Data["error"]).To(ContainSubstring("plan_id missing"))
Expand Down Expand Up @@ -1359,14 +1418,14 @@ var _ = Describe("Service Broker API", func() {

Context("when the broker API version is 2.9", func() {
It("responds with an experimental volume mount", func() {
response := makeBindingRequestWithSpecificAPIVersion(uniqueInstanceID(), uniqueBindingID(), details, "2.9")
response := makeBindingRequestWithSpecificAPIVersion(uniqueInstanceID(), uniqueBindingID(), details, "2.9", false)
Expect(response.Body).To(MatchJSON(fixture("binding_with_experimental_volume_mounts.json")))
})
})

Context("when the broker API version is 2.8", func() {
It("responds with an experimental volume mount", func() {
response := makeBindingRequestWithSpecificAPIVersion(uniqueInstanceID(), uniqueBindingID(), details, "2.8")
response := makeBindingRequestWithSpecificAPIVersion(uniqueInstanceID(), uniqueBindingID(), details, "2.8", false)
Expect(response.Body).To(MatchJSON(fixture("binding_with_experimental_volume_mounts.json")))
})
})
Expand Down Expand Up @@ -1567,6 +1626,58 @@ var _ = Describe("Service Broker API", func() {
Expect(lastLogLine().Data["error"]).To(ContainSubstring("I failed in unique and interesting ways"))
})
})

Context("when an async binding is requested", func() {
var (
fakeAsyncServiceBroker *fakes.FakeAsyncServiceBroker
)
BeforeEach(func() {
fakeAsyncServiceBroker = &fakes.FakeAsyncServiceBroker{
FakeServiceBroker: *fakeServiceBroker,
ShouldBindAsync: true,
}
brokerAPI = brokerapi.New(fakeAsyncServiceBroker, brokerLogger, credentials)
})

It("when the api version is before 2.14 for Bind request", func() {
response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, details, "2.13", true)
Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity))
})

It("when the api version is before 2.14 for LastBindingOperation request", func() {
response := makeLastBindingOperationRequestWithSpecificAPIVersion(instanceID, bindingID, "1.13")
Expect(response.StatusCode).To(Equal(http.StatusPreconditionFailed))
response = makeLastBindingOperationRequestWithSpecificAPIVersion(instanceID, bindingID, "2.13")
Expect(response.StatusCode).To(Equal(http.StatusPreconditionFailed))
})

It("when the api version is before 2.14 for GetBinding request", func() {
response := makeGetBindingRequestWithSpecificAPIVersion(instanceID, bindingID, "1.13")
Expect(response.StatusCode).To(Equal(http.StatusPreconditionFailed))
response = makeGetBindingRequestWithSpecificAPIVersion(instanceID, bindingID, "2.13")
Expect(response.StatusCode).To(Equal(http.StatusPreconditionFailed))
})

It("it returns an appropriate status code and operation data", func() {
response := makeAsyncBindingRequest(instanceID, bindingID, details)
Expect(response.StatusCode).To(Equal(http.StatusAccepted))
Expect(response.Body).To(MatchJSON(fixture("async_bind_response.json")))
})

It("can be polled with lastBindingOperation", func() {
fakeAsyncServiceBroker.LastOperationState = "succeeded"
fakeAsyncServiceBroker.LastOperationDescription = "some description"
response := makeLastBindingOperationRequestWithSpecificAPIVersion(instanceID, bindingID, "2.14")
Expect(response.StatusCode).To(Equal(http.StatusOK))
Expect(response.Body).To(MatchJSON(fixture("last_operation_succeeded.json")))
})

It("getBinding returns the binding for the async request", func() {
response := makeGetBindingRequestWithSpecificAPIVersion(instanceID, bindingID, "2.14")
Expect(response.StatusCode).To(Equal(http.StatusOK))
Expect(response.Body).To(MatchJSON(fixture("binding.json")))
})
})
})

Describe("unbinding", func() {
Expand Down
47 changes: 44 additions & 3 deletions fakes/fake_service_broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type FakeServiceBroker struct {
type FakeAsyncServiceBroker struct {
FakeServiceBroker
ShouldProvisionAsync bool
ShouldBindAsync bool
}

type FakeAsyncOnlyServiceBroker struct {
Expand Down Expand Up @@ -290,10 +291,42 @@ func (fakeBroker *FakeAsyncServiceBroker) Deprovision(context context.Context, i
return brokerapi.DeprovisionServiceSpec{OperationData: fakeBroker.OperationDataToReturn, IsAsync: asyncAllowed}, brokerapi.ErrInstanceDoesNotExist
}

func (fakeBroker *FakeServiceBroker) GetBinding(ctx context.Context, instanceID, bindingID string) (brokerapi.GetBindingSpec, error) {
func (fakeBroker *FakeServiceBroker) GetBinding(context context.Context, instanceID, bindingID string) (brokerapi.GetBindingSpec, error) {
fakeBroker.BrokerCalled = true

return brokerapi.GetBindingSpec{}, nil
if val, ok := context.Value("test_context").(bool); ok {
fakeBroker.ReceivedContext = val
}

return brokerapi.GetBindingSpec{
Credentials: FakeCredentials{
Host: "127.0.0.1",
Port: 3000,
Username: "batman",
Password: "robin",
},
SyslogDrainURL: fakeBroker.SyslogDrainURL,
RouteServiceURL: fakeBroker.RouteServiceURL,
VolumeMounts: fakeBroker.VolumeMounts,
}, nil
}

func (fakeBroker *FakeAsyncServiceBroker) Bind(context context.Context, instanceID, bindingID string, details brokerapi.BindDetails, asyncAllowed bool) (brokerapi.Binding, error) {
fakeBroker.BrokerCalled = true

fakeBroker.BoundBindingDetails = details

fakeBroker.BoundInstanceIDs = append(fakeBroker.BoundInstanceIDs, instanceID)
fakeBroker.BoundBindingIDs = append(fakeBroker.BoundBindingIDs, bindingID)

if fakeBroker.ShouldBindAsync {
return brokerapi.Binding{
IsAsync: true,
OperationData: "0xDEADBEEF",
}, nil
} else {
return fakeBroker.FakeServiceBroker.Bind(context, instanceID, bindingID, details, false)
}
}

func (fakeBroker *FakeServiceBroker) Bind(context context.Context, instanceID, bindingID string, details brokerapi.BindDetails, asyncAllowed bool) (brokerapi.Binding, error) {
Expand Down Expand Up @@ -350,7 +383,15 @@ func (fakeBroker *FakeServiceBroker) Unbind(context context.Context, instanceID,

func (fakeBroker *FakeServiceBroker) LastBindingOperation(context context.Context, instanceID, bindingID string, details brokerapi.PollDetails) (brokerapi.LastOperation, error) {

return brokerapi.LastOperation{}, nil
if val, ok := context.Value("test_context").(bool); ok {
fakeBroker.ReceivedContext = val
}

if fakeBroker.LastOperationError != nil {
return brokerapi.LastOperation{}, fakeBroker.LastOperationError
}

return brokerapi.LastOperation{State: fakeBroker.LastOperationState, Description: fakeBroker.LastOperationDescription}, nil
}

func (fakeBroker *FakeServiceBroker) LastOperation(context context.Context, instanceID string, details brokerapi.PollDetails) (brokerapi.LastOperation, error) {
Expand Down
3 changes: 3 additions & 0 deletions fixtures/async_bind_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"operation":"0xDEADBEEF"
}
2 changes: 1 addition & 1 deletion response.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type BindingResponse struct {

type GetBindingResponse struct {
BindingResponse
Parameters interface{} `json:"parameters"`
Parameters interface{} `json:"parameters,omitempty"`
}

type UnbindResponse struct {
Expand Down
2 changes: 1 addition & 1 deletion response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ var _ = Describe("Provisioning Response", func() {
var _ = Describe("Binding Response", func() {
Describe("JSON encoding", func() {
It("has a credentials object", func() {
binding := brokerapi.Binding{}
binding := brokerapi.BindingResponse{}
jsonString := `{"credentials":null}`

Expect(json.Marshal(binding)).To(MatchJSON(jsonString))
Expand Down

0 comments on commit d8f27d6

Please sign in to comment.