diff --git a/splitio/proxy/controllers/sdk.go b/splitio/proxy/controllers/sdk.go index ba542763..2d3e0e51 100644 --- a/splitio/proxy/controllers/sdk.go +++ b/splitio/proxy/controllers/sdk.go @@ -68,6 +68,7 @@ func (c *SdkServerController) Memberships(ctx *gin.Context) { if err != nil { c.logger.Error(fmt.Sprintf("error fetching segments for user '%s': %s", key, err.Error())) ctx.JSON(http.StatusInternalServerError, gin.H{}) + return } mySegments := make([]dtos.Segment, 0, len(segmentList)) diff --git a/splitio/proxy/controllers/sdk_test.go b/splitio/proxy/controllers/sdk_test.go index bb7928a3..ef2c40ea 100644 --- a/splitio/proxy/controllers/sdk_test.go +++ b/splitio/proxy/controllers/sdk_test.go @@ -570,6 +570,93 @@ func TestMySegmentsError(t *testing.T) { segmentStorage.AssertExpectations(t) } +func TestMemberships(t *testing.T) { + gin.SetMode(gin.TestMode) + + var splitFetcher splitFetcherMock + var splitStorage psmocks.ProxySplitStorageMock + var segmentStorage psmocks.ProxySegmentStorageMock + segmentStorage.On("SegmentsFor", "keyTest"). + Return([]string{"segment1", "segment2"}, nil). + Once() + + var largeSegmentStorageMock largeSegmentStorageMock + largeSegmentStorageMock.On("LargeSegmentsForUser", "keyTest"). + Return([]string{"largeSegment1", "largeSegment2"}). + Once() + + resp := httptest.NewRecorder() + ctx, router := gin.CreateTestContext(resp) + + logger := logging.NewLogger(nil) + + group := router.Group("/api") + controller := NewSdkServerController(logger, &splitFetcher, &splitStorage, &segmentStorage, flagsets.NewMatcher(false, nil), &largeSegmentStorageMock) + controller.Register(group) + + ctx.Request, _ = http.NewRequest(http.MethodGet, "/api/memberships/keyTest", nil) + ctx.Request.Header.Set("Authorization", "Bearer someApiKey") + ctx.Request.Header.Set("SplitSDKVersion", "go-1.1.1") + ctx.Request.Header.Set("SplitSDKMachineIp", "1.2.3.4") + ctx.Request.Header.Set("SplitSDKMachineName", "ip-1-2-3-4") + router.ServeHTTP(resp, ctx.Request) + assert.Equal(t, 200, resp.Code) + + body, err := io.ReadAll(resp.Body) + assert.Nil(t, err) + + var actualDTO dtos.MembershipsResponseDTO + err = json.Unmarshal(body, &actualDTO) + assert.Nil(t, err) + + expectedDTO := dtos.MembershipsResponseDTO{ + MySegments: dtos.Memberships{ + Segments: []dtos.Segment{{Name: "segment1"}, {Name: "segment2"}}, + }, + MyLargeSegments: dtos.Memberships{ + Segments: []dtos.Segment{{Name: "largeSegment1"}, {Name: "largeSegment2"}}, + }, + } + assert.Equal(t, expectedDTO, actualDTO) + + splitStorage.AssertExpectations(t) + splitFetcher.AssertExpectations(t) + segmentStorage.AssertExpectations(t) +} + +func TestMembershipsError(t *testing.T) { + gin.SetMode(gin.TestMode) + + var splitFetcher splitFetcherMock + var splitStorage psmocks.ProxySplitStorageMock + var largeSegmentStorageMock largeSegmentStorageMock + var segmentStorage psmocks.ProxySegmentStorageMock + segmentStorage.On("SegmentsFor", "keyTest"). + Return([]string{}, errors.New("error message.")). + Once() + + resp := httptest.NewRecorder() + ctx, router := gin.CreateTestContext(resp) + + logger := logging.NewLogger(nil) + + group := router.Group("/api") + controller := NewSdkServerController(logger, &splitFetcher, &splitStorage, &segmentStorage, flagsets.NewMatcher(false, nil), &largeSegmentStorageMock) + controller.Register(group) + + ctx.Request, _ = http.NewRequest(http.MethodGet, "/api/memberships/dale ya ", nil) + ctx.Request.Header.Set("Authorization", "Bearer someApiKey") + ctx.Request.Header.Set("SplitSDKVersion", "go-1.1.1") + ctx.Request.Header.Set("SplitSDKMachineIp", "1.2.3.4") + ctx.Request.Header.Set("SplitSDKMachineName", "ip-1-2-3-4") + router.ServeHTTP(resp, ctx.Request) + assert.Equal(t, 500, resp.Code) + + splitStorage.AssertExpectations(t) + splitFetcher.AssertExpectations(t) + segmentStorage.AssertExpectations(t) +} + type splitFetcherMock struct { mock.Mock } diff --git a/splitio/proxy/initialization.go b/splitio/proxy/initialization.go index d45c5667..7b3f0efa 100644 --- a/splitio/proxy/initialization.go +++ b/splitio/proxy/initialization.go @@ -262,7 +262,7 @@ func Start(logger logging.LoggerInterface, cfg *pconf.Main) error { TLSConfig: tlsConfig, FlagSets: cfg.FlagSetsFilter, FlagSetsStrictMatching: cfg.FlagSetStrictMatching, - LargeSegmentStorage: largeSegmentStorage, + ProxyLargeSegmentStorage: largeSegmentStorage, } if ilcfg := cfg.Integrations.ImpressionListener; ilcfg.Endpoint != "" { diff --git a/splitio/proxy/proxy.go b/splitio/proxy/proxy.go index 059f0110..cf26ec22 100644 --- a/splitio/proxy/proxy.go +++ b/splitio/proxy/proxy.go @@ -51,6 +51,9 @@ type Options struct { // used to resolve segmentChanges & mySegments requests ProxySegmentStorage storage.ProxySegmentStorage + // ProxyLargeSegmentStorage + ProxyLargeSegmentStorage cmnStorage.LargeSegmentsStorage + // what to do with received impression bulk payloads ImpressionsSink tasks.DeferredRecordingTask @@ -84,9 +87,6 @@ type Options struct { FlagSets []string FlagSetsStrictMatching bool - - // LargeSegmentStorage - LargeSegmentStorage cmnStorage.LargeSegmentsStorage } // API bundles all components required to answer API calls from Split sdks @@ -164,7 +164,7 @@ func setupSdkController(options *Options) *controllers.SdkServerController { options.ProxySplitStorage, options.ProxySegmentStorage, flagsets.NewMatcher(options.FlagSetsStrictMatching, options.FlagSets), - options.LargeSegmentStorage, + options.ProxyLargeSegmentStorage, ) } diff --git a/splitio/proxy/proxy_test.go b/splitio/proxy/proxy_test.go index faad8614..4ec41de8 100644 --- a/splitio/proxy/proxy_test.go +++ b/splitio/proxy/proxy_test.go @@ -236,6 +236,41 @@ func TestSegmentChangesAndMySegmentsEndpoints(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", headers.Get("Content-Type")) } +func TestMembershipEndpoint(t *testing.T) { + var segmentStorage pstorageMocks.ProxySegmentStorageMock + var lsStorage pstorageMocks.ProxyLargeSegmentStorageMock + + opts := makeOpts() + opts.ProxySegmentStorage = &segmentStorage + opts.ProxyLargeSegmentStorage = &lsStorage + proxy := New(opts) + go proxy.Start() + time.Sleep(1 * time.Second) // Let the scheduler switch the current thread/gr and start the server + + // Test that a request without auth fails and is not cached + status, _, _ := get("memberships/mauro", opts.Port, nil) + if status != 401 { + t.Error("status should be 401. Is", status) + } + + segmentStorage.On("SegmentsFor", "mauro").Return([]string{"segment1"}, nil).Once() + lsStorage.On("LargeSegmentsForUser", "mauro").Return([]string{"largeSegment1", "largeSegment2"}).Once() + + status, body, headers := get("memberships/mauro", opts.Port, map[string]string{"Authorization": "Bearer someApiKey"}) + response := memberships(body) + expected := dtos.MembershipsResponseDTO{ + MySegments: dtos.Memberships{ + Segments: []dtos.Segment{{Name: "segment1"}}, + }, + MyLargeSegments: dtos.Memberships{ + Segments: []dtos.Segment{{Name: "largeSegment1"}, {Name: "largeSegment2"}}, + }, + } + assert.Equal(t, 200, status) + assert.Equal(t, expected, response) + assert.Equal(t, "application/json; charset=utf-8", headers.Get("Content-Type")) +} + func makeOpts() *Options { return &Options{ Logger: logging.NewLogger(nil), @@ -307,3 +342,12 @@ func toMySegments(body []byte) []dtos.MySegmentDTO { } return c["mySegments"] } + +func memberships(body []byte) dtos.MembershipsResponseDTO { + var c dtos.MembershipsResponseDTO + err := json.Unmarshal(body, &c) + if err != nil { + panic(err.Error()) + } + return c +} diff --git a/splitio/proxy/storage/mocks/mocks.go b/splitio/proxy/storage/mocks/mocks.go index 5910c7ca..c802fd89 100644 --- a/splitio/proxy/storage/mocks/mocks.go +++ b/splitio/proxy/storage/mocks/mocks.go @@ -35,3 +35,34 @@ func (p *ProxySegmentStorageMock) SegmentsFor(key string) ([]string, error) { func (p *ProxySegmentStorageMock) CountRemovedKeys(segmentName string) int { return p.Called(segmentName).Int(0) } + +type ProxyLargeSegmentStorageMock struct { + mock.Mock +} + +func (s *ProxyLargeSegmentStorageMock) SetChangeNumber(name string, till int64) { + s.Called(name, till).Error(0) +} + +func (s *ProxyLargeSegmentStorageMock) Update(name string, userKeys []string, till int64) { + s.Called(name, userKeys, till) +} +func (s *ProxyLargeSegmentStorageMock) ChangeNumber(name string) int64 { + args := s.Called(name) + return args.Get(0).(int64) +} +func (s *ProxyLargeSegmentStorageMock) Count() int { + args := s.Called() + return args.Get(0).(int) +} +func (s *ProxyLargeSegmentStorageMock) LargeSegmentsForUser(userKey string) []string { + args := s.Called(userKey) + return args.Get(0).([]string) +} +func (s *ProxyLargeSegmentStorageMock) IsInLargeSegment(name string, key string) (bool, error) { + args := s.Called(name, key) + return args.Get(0).(bool), args.Error(1) +} +func (s *ProxyLargeSegmentStorageMock) TotalKeys(name string) int { + return s.Called(name).Get(0).(int) +}