Skip to content

Commit

Permalink
test(segment): replace mock client with stub for delete's tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jskelin committed Dec 19, 2024
1 parent b4cbe16 commit 7fda2be
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 92 deletions.
68 changes: 30 additions & 38 deletions pkg/delete/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,29 +1151,31 @@ func TestDelete_Documents(t *testing.T) {
})
}

type fakeSegmentsClient struct {
type stubClient struct {
called bool
list func() (segments.Response, error)
getAll func() ([]segments.Response, error)
delete func() (segments.Response, error)
}

func (c fakeSegmentsClient) List(_ context.Context) (segments.Response, error) {
func (c *stubClient) List(_ context.Context) (segments.Response, error) {
return c.list()
}

func (c fakeSegmentsClient) GetAll(_ context.Context) ([]segments.Response, error) {
func (c *stubClient) GetAll(_ context.Context) ([]segments.Response, error) {
return c.getAll()
}

func (c fakeSegmentsClient) Delete(_ context.Context, _ string) (segments.Response, error) {
func (c *stubClient) Delete(_ context.Context, _ string) (segments.Response, error) {
c.called = true
return c.delete()
}

func TestDelete_SegmentsWithFakes(t *testing.T) {
func TestDelete_Segments(t *testing.T) {
t.Run("simple case", func(t *testing.T) {
t.Setenv(featureflags.Temporary[featureflags.Segments].EnvName(), "true")

fakeClient := fakeSegmentsClient{
c := stubClient{
delete: func() (segments.Response, error) {
return segments.Response{StatusCode: http.StatusOK}, nil
},
Expand All @@ -1187,18 +1189,15 @@ func TestDelete_SegmentsWithFakes(t *testing.T) {
},
},
}
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: fakeClient}, given)
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: &c}, given)
assert.NoError(t, err)
assert.True(t, c.called, "there should be delete call")
})

}
t.Run("simple case with FF turned off", func(t *testing.T) {
t.Setenv(featureflags.Temporary[featureflags.Segments].EnvName(), "false")

func TestDelete_Segments(t *testing.T) {
t.Run("simple case", func(t *testing.T) {
t.Setenv(featureflags.Temporary[featureflags.Segments].EnvName(), "true")

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1)
c := stubClient{}

given := delete.DeleteEntries{
"segment": {
Expand All @@ -1208,44 +1207,37 @@ func TestDelete_Segments(t *testing.T) {
},
},
}
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: c}, given)
assert.NoError(t, err)
})

t.Run("FF is turned off", func(t *testing.T) {
c := client.NewMockSegmentClient(gomock.NewController(t))
// no calls to client

entriesToDelete := delete.DeleteEntries{
"segment": {
{
Type: "segment",
OriginObjectId: "originObjectID",
},
},
}
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: c}, entriesToDelete)
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: &c}, given)
assert.NoError(t, err)
assert.False(t, c.called, "there should NOT be delete call")
})
}

func TestDeleteAll_Segments(t *testing.T) {
t.Run("simple case", func(t *testing.T) {
t.Setenv(featureflags.Temporary[featureflags.Segments].EnvName(), "true")

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Return(segments.Response{StatusCode: http.StatusOK, Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil).Times(1)
c.EXPECT().Delete(gomock.Any(), gomock.Any()).Times(3)
c := stubClient{
list: func() (segments.Response, error) {
return segments.Response{StatusCode: http.StatusOK, Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil
},
delete: func() (segments.Response, error) {
return segments.Response{StatusCode: http.StatusOK}, nil
},
}

err := delete.All(context.TODO(), client.ClientSet{SegmentClient: c}, api.APIs{})
err := delete.All(context.TODO(), client.ClientSet{SegmentClient: &c}, api.APIs{})
assert.NoError(t, err)
assert.True(t, c.called, "there should be delete call")
})

t.Run("FF is turned off", func(t *testing.T) {
c := client.NewMockSegmentClient(gomock.NewController(t))
// no calls to client
t.Setenv(featureflags.Temporary[featureflags.Segments].EnvName(), "false")

c := stubClient{}

err := delete.All(context.TODO(), client.ClientSet{SegmentClient: c}, api.APIs{})
err := delete.All(context.TODO(), client.ClientSet{SegmentClient: &c}, api.APIs{})
assert.NoError(t, err)
assert.False(t, c.called, "there should NOT be delete call")
})
}
177 changes: 123 additions & 54 deletions pkg/delete/internal/segment/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,29 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"

libAPI "github.com/dynatrace/dynatrace-configuration-as-code-core/api"
libSegment "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/segments"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/idutils"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete/internal/segment"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete/pointer"
)

type stubClient struct {
called bool
delete func(id string) (libSegment.Response, error)
list func() (libSegment.Response, error)
}

func (s *stubClient) List(_ context.Context) (libSegment.Response, error) {
return s.list()
}

func (s *stubClient) Delete(_ context.Context, id string) (libSegment.Response, error) {
s.called = true
return s.delete(id)
}

func TestDelete(t *testing.T) {

t.Run("delete via coordinate", func(t *testing.T) {
Expand All @@ -42,15 +55,21 @@ func TestDelete(t *testing.T) {
Identifier: "monaco_identifier",
Project: "project",
}

externalID, _ := idutils.GenerateExternalIDForDocument(given.AsCoordinate())
c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Times(1).
Return(libSegment.Response{Data: []byte(fmt.Sprintf(`[{"uid": "uid_1", "externalId":"%s"},{"uid": "uid_2", "externalId":"wrong"}]`, externalID))}, nil)
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_1")).Times(1)

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given})
c := stubClient{
list: func() (libSegment.Response, error) {
return libSegment.Response{Data: []byte(fmt.Sprintf(`[{"uid": "uid_1", "externalId":"%s"},{"uid": "uid_2", "externalId":"wrong"}]`, externalID))}, nil
},
delete: func(id string) (libSegment.Response, error) {
assert.Equal(t, "uid_1", id)
return libSegment.Response{}, nil
},
}

err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.NoError(t, err)
assert.True(t, c.called, "delete command wasn't invoked")
})

t.Run("config declared via coordinate doesn't exists - no error (wanted state achieved)", func(t *testing.T) {
Expand All @@ -60,11 +79,13 @@ func TestDelete(t *testing.T) {
Project: "project",
}

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Times(1).
Return(libSegment.Response{Data: []byte("[{\"uid\": \"uid_2\", \"externalId\":\"wrong\"}]")}, nil)
c := stubClient{
list: func() (libSegment.Response, error) {
return libSegment.Response{Data: []byte(`[{"uid": "uid_2", "externalId":"wrong"}]`)}, nil
},
}

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given})
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.NoError(t, err)
})

Expand All @@ -76,26 +97,31 @@ func TestDelete(t *testing.T) {
}

externalID, _ := idutils.GenerateExternalIDForDocument(given.AsCoordinate())
c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Times(1).
Return(libSegment.Response{Data: []byte(fmt.Sprintf(`[{"uid": "uid_1", "externalId":"%s"},{"uid": "uid_2", "externalId":"%s"}]`, externalID, externalID))}, nil)
c := stubClient{
list: func() (libSegment.Response, error) {
return libSegment.Response{Data: []byte(fmt.Sprintf(`[{"uid": "uid_1", "externalId":"%s"},{"uid": "uid_2", "externalId":"%s"}]`, externalID, externalID))}, nil
},
}

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given})
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.Error(t, err)
assert.False(t, c.called, "it's not known what needs to be deleted")
})

t.Run("config declared via coordinate failed to get externalId (server error) - an error", func(t *testing.T) {
t.Run("config declared via coordinate failed to get externalId - an error", func(t *testing.T) {
given := pointer.DeletePointer{
Type: "segment",
Identifier: "monaco_identifier",
Project: "project",
}

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Times(1).
Return(libSegment.Response{}, errors.New("some unpredictable error"))
c := stubClient{
list: func() (libSegment.Response, error) {
return libSegment.Response{}, errors.New("some unpredictable error")
},
}

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given})
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.Error(t, err)
})

Expand All @@ -105,11 +131,16 @@ func TestDelete(t *testing.T) {
OriginObjectId: "originObjectID",
}

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1)
c := stubClient{
delete: func(id string) (libSegment.Response, error) {
assert.Equal(t, given.OriginObjectId, id)
return libSegment.Response{}, nil
},
}

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given})
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.NoError(t, err)
assert.True(t, c.called)
})

t.Run("config declared via originID doesn't exists - no error (wanted state achieved)", func(t *testing.T) {
Expand All @@ -118,52 +149,90 @@ func TestDelete(t *testing.T) {
OriginObjectId: "originObjectID",
}

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusNotFound})
c := stubClient{
delete: func(id string) (libSegment.Response, error) {
assert.Equal(t, given.OriginObjectId, id)
return libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusNotFound}
},
}

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given})
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.NoError(t, err)
})

t.Run("error during delete - continue to delete, an error", func(t *testing.T) {
t.Run("error during delete - an error", func(t *testing.T) {
given := pointer.DeletePointer{
Type: "segment",
OriginObjectId: "originObjectID",
Project: "project",
}

c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusNotFound})
c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusInternalServerError}) // the error can be any kind except 404
c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusNotFound})
c := stubClient{
delete: func(_ string) (libSegment.Response, error) {
return libSegment.Response{}, errors.New("some unpredictable error")
},
}

err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given, given, given})
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.Error(t, err)
})
}

func TestDeleteAll(t *testing.T) {
t.Run("simple case", func(t *testing.T) {
c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Times(1).
Return(libSegment.Response{Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil)
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_1")).Times(1)
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_2")).Times(1)
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_3")).Times(1)

err := segment.DeleteAll(context.TODO(), c)
assert.NoError(t, err)
})
t.Run("server error during delete - an error", func(t *testing.T) {
given := pointer.DeletePointer{
Type: "segment",
OriginObjectId: "originObjectID",
Project: "project",
}

t.Run("error during delete - continue to delete, an error", func(t *testing.T) {
c := client.NewMockSegmentClient(gomock.NewController(t))
c.EXPECT().List(gomock.Any()).Times(1).
Return(libSegment.Response{Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil)
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_1")).Times(1)
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_2")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusInternalServerError}) // the error can be any kind except 404
c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_3")).Times(1)
c := stubClient{
delete: func(_ string) (libSegment.Response, error) {
return libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusInternalServerError}
},
}

err := segment.DeleteAll(context.TODO(), c)
err := segment.Delete(context.TODO(), &c, []pointer.DeletePointer{given})
assert.Error(t, err)
})

// t.Run("error during delete - continue to delete, an error", func(t *testing.T) {
// given := pointer.DeletePointer{
// Type: "segment",
// OriginObjectId: "originObjectID",
// Project: "project",
// }
//
// c := client.NewMockSegmentClient(gomock.NewController(t))
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusNotFound})
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusInternalServerError}) // the error can be any kind except 404
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusNotFound})
//
// err := segment.Delete(context.TODO(), c, []pointer.DeletePointer{given, given, given})
// assert.Error(t, err)
// })
}

func TestDeleteAll(t *testing.T) {
// t.Run("simple case", func(t *testing.T) {
// c := client.NewMockSegmentClient(gomock.NewController(t))
// c.EXPECT().List(gomock.Any()).Times(1).
// Return(libSegment.Response{Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil)
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_1")).Times(1)
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_2")).Times(1)
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_3")).Times(1)
//
// err := segment.DeleteAll(context.TODO(), c)
// assert.NoError(t, err)
// })

// t.Run("error during delete - continue to delete, an error", func(t *testing.T) {
// c := client.NewMockSegmentClient(gomock.NewController(t))
// c.EXPECT().List(gomock.Any()).Times(1).
// Return(libSegment.Response{Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil)
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_1")).Times(1)
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_2")).Times(1).Return(libAPI.Response{}, libAPI.APIError{StatusCode: http.StatusInternalServerError}) // the error can be any kind except 404
// c.EXPECT().Delete(gomock.Any(), gomock.Eq("uid_3")).Times(1)
//
// err := segment.DeleteAll(context.TODO(), c)
// assert.Error(t, err)
// })
}

0 comments on commit 7fda2be

Please sign in to comment.