Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return 404 from GET Bindings for Expired Bindings #1355

6 changes: 6 additions & 0 deletions internal/broker/bind_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"time"

"github.com/kyma-project/kyma-environment-broker/internal/storage"
"github.com/pivotal-cf/brokerapi/v8/domain"
Expand Down Expand Up @@ -34,6 +35,11 @@ func (b *GetBindingEndpoint) GetBinding(_ context.Context, instanceID, bindingID
return domain.GetBindingSpec{}, apiresponses.NewFailureResponse(fmt.Errorf(message), http.StatusNotFound, message)
}

if binding.ExpiresAt.Before(time.Now()) {
message := "Binding expired"
return domain.GetBindingSpec{}, apiresponses.NewFailureResponse(fmt.Errorf(message), http.StatusNotFound, message)
}

if err != nil {
b.log.Errorf("GetBinding error: %s", err)
message := fmt.Sprintf("Unexpected error: %s", err)
Expand Down
44 changes: 44 additions & 0 deletions internal/broker/bind_get_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package broker

import (
"context"
"net/http"
"testing"
"time"

"github.com/kyma-project/kyma-environment-broker/internal"
mocks "github.com/kyma-project/kyma-environment-broker/internal/storage/automock"
"github.com/pivotal-cf/brokerapi/v8/domain"
"github.com/pivotal-cf/brokerapi/v8/domain/apiresponses"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)

func TestGetBinding(t *testing.T) {

t.Run("should return 404 code for the expired binding", func(t *testing.T) {
// given
mockBindings := new(mocks.Bindings)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a question, why just not use in-memory implementation?

Copy link
Member Author

@ralikio ralikio Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. With mocks I am guaranteed the bindings' "Get" method is called.
  2. I do not need to check if anything has been created in the test, it just for quick return and assertion that 404 is returned when for prefabricated binding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using in memory implementation in other unit test which works fine, I don't like to introduce a different solution which does not give us any special value. Such pattern is also used in fake kubernetes client. Mocking database can be risky.
The title of the test is "should return 404 code for the expired binding", not "should call 'get' method", it is not important in my opinion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still find value in asserting execution. In order to meet the argument of unnecessary test modifications when interface changes and mock regeneration is required I would suggest to treat interface as something that rarely should change or rely on new interfaces in new logic.

Done as requested though. Let's discuss offline what are the possibilities to use spies or sth else to assert method executions in our project.

Copy link
Contributor

@jaroslaw-pieszka jaroslaw-pieszka Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about interface changing but implementation changing. We relied on implementation (test relied on the knowledge how logic is implemented) which in this case I found excessive, or even inappropriate.

Copy link
Member Author

@ralikio ralikio Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that is not true. The test contained assertion on logic so that in the future if our "black box" called bindings multiple times we would know that it is not aligned with initial assumptions. Think about it not as an assertion if specific instruction that the unit uses but as requirement introduced because unit relies on external dependency in the form of "Bindings" interface. In the current form, we could remove Bindings dependency, return 404 with correct message from the unit and the test would be green. The Bindings interface is external, not internal to the tested unit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I beg to disagree. Test assumed that implementation calls Get method once. This is not part of contract.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that we test contract and use black-box testing, we do not need to know that there is any external dependency, we do not need to know how it is implemented, how many invocations of any kind is made.
We know only, that if the binding in question is expired, we get 404.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can testing if there is only one Get call on repository, but... then we should test if the binding manager deletes... deployments or something else by accident. We cannot test everything! such tests would be a problem, not a help


expiredBinding := &internal.Binding{
ExpiresAt: time.Now().Add(-1 * time.Hour),
}

mockBindings.On("Get", "test-instance-id", "test-binding-id").Return(expiredBinding, nil)

endpoint := &GetBindingEndpoint{
bindings: mockBindings,
log: &logrus.Logger{}, // Assuming you have a mock logger
}

// when
_, err := endpoint.GetBinding(context.Background(), "test-instance-id", "test-binding-id", domain.FetchBindingDetails{})

// then
require.NotNil(t, err)
apiErr, ok := err.(*apiresponses.FailureResponse)
require.True(t, ok)
require.Equal(t, http.StatusNotFound, apiErr.ValidatedStatusCode(nil))
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved
mockBindings.AssertExpectations(t)
})
}
123 changes: 123 additions & 0 deletions internal/storage/automock/bindings.go

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

1 change: 1 addition & 0 deletions internal/storage/ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type SubaccountStates interface {
ListStates() ([]internal.SubaccountState, error)
}

//go:generate mockery --name=Bindings --output=automock --case=underscore
type Bindings interface {
Insert(binding *internal.Binding) error
Get(instanceID string, bindingID string) (*internal.Binding, error)
Expand Down
Loading