Skip to content

Commit

Permalink
FFM-11470 Unit tests for sendDataAndResetCache
Browse files Browse the repository at this point in the history
  • Loading branch information
erdirowlands committed May 13, 2024
1 parent 276f9ae commit 3a5e792
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 3 deletions.
6 changes: 3 additions & 3 deletions analyticsservice/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ func (as *AnalyticsService) startTimer(ctx context.Context) {
for {
select {
case <-time.After(as.timeout):
as.sendDataAndResetCache(ctx)
timeStamp := time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
as.sendDataAndResetCache(ctx, timeStamp)
case <-ctx.Done():
as.logger.Infof("%s Metrics stopped", sdk_codes.MetricsStopped)
return
Expand Down Expand Up @@ -197,7 +198,7 @@ func convertInterfaceToString(i interface{}) string {
}
}

func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context) {
func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context, timeStamp int64) {

// Clone and reset the evaluation analytics cache to minimise the duration
// for which locks are held, so that metrics processing does not affect flag evaluations performance.
Expand All @@ -216,7 +217,6 @@ func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context) {
as.logTargetLimitReached.Store(false)

// Process evaluation metrics
timeStamp := time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
metricData := as.processEvaluationMetrics(evaluationAnalyticsClone, timeStamp)

// Process target metrics
Expand Down
132 changes: 132 additions & 0 deletions analyticsservice/analytics_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package analyticsservice

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

Expand All @@ -11,6 +14,27 @@ import (
"github.com/stretchr/testify/assert"
)

type MockMetricsClient struct {
LastBody metricsclient.PostMetricsJSONRequestBody
CallCount int
}

func (c *MockMetricsClient) PostMetricsWithResponse(ctx context.Context, environmentUUID metricsclient.EnvironmentPathParam, params *metricsclient.PostMetricsParams, body metricsclient.PostMetricsJSONRequestBody, reqEditors ...metricsclient.RequestEditorFn) (*metricsclient.PostMetricsResponse, error) {
c.LastBody = body
c.CallCount++

return &metricsclient.PostMetricsResponse{
HTTPResponse: &http.Response{
StatusCode: 200,
},
}, nil
}

func (c *MockMetricsClient) PostMetricsWithBodyWithResponse(ctx context.Context, environmentUUID metricsclient.EnvironmentPathParam, params *metricsclient.PostMetricsParams, contentType string, body io.Reader, reqEditors ...metricsclient.RequestEditorFn) (*metricsclient.PostMetricsResponse, error) {

return nil, nil // default to returning no error and a nil response
}

func TestListenerHandlesEventsCorrectly(t *testing.T) {
noOpLogger := logger.NewNoOpLogger()

Expand Down Expand Up @@ -401,6 +425,114 @@ func Test_ProcessTargetMetrics(t *testing.T) {
}
}

func TestSendDataAndResetCache(t *testing.T) {
noOpLogger := logger.NewNoOpLogger()

evaluationAnalytics := newSafeEvaluationAnalytics()
evaluationAnalytics.set("key1", analyticsEvent{
featureConfig: &rest.FeatureConfig{Feature: "feature1"},
variation: &rest.Variation{Identifier: "var1", Value: "value1"},
count: 3,
})
evaluationAnalytics.set("key2", analyticsEvent{
featureConfig: &rest.FeatureConfig{Feature: "feature2"},
variation: &rest.Variation{Identifier: "var2", Value: "value2"},
count: 1,
})

targetAnalytics := newSafeTargetAnalytics()

// Use anonymous and attributes
targetAnalytics.set("target1", evaluation.Target{
Identifier: "target1",
Name: "Target One",
Anonymous: boolPtr(false),
Attributes: &map[string]interface{}{"key1": "value1"},
})

// No attributes or anonymous set
targetAnalytics.set("target2", evaluation.Target{
Identifier: "target2",
Name: "Target Two",
})

mClient := &MockMetricsClient{}

service := AnalyticsService{
evaluationAnalytics: evaluationAnalytics,
targetAnalytics: targetAnalytics,
logger: noOpLogger,
metricsClient: mClient,
environmentID: "test-env",
}

ctx := context.Background()

var timeStamp int64 = 1715600410545
service.sendDataAndResetCache(ctx, timeStamp)

expectedAnalyticsPayload := metricsclient.PostMetricsJSONRequestBody{
MetricsData: &[]metricsclient.MetricsData{
{
Count: 3,
Attributes: []metricsclient.KeyValue{
{Key: featureIdentifierAttribute, Value: "feature1"},
{Key: featureNameAttribute, Value: "feature1"},
{Key: variationIdentifierAttribute, Value: "var1"},
{Key: variationValueAttribute, Value: "value1"},
{Key: sdkTypeAttribute, Value: sdkType},
{Key: sdkLanguageAttribute, Value: sdkLanguage},
{Key: sdkVersionAttribute, Value: SdkVersion},
{Key: targetAttribute, Value: globalTarget},
},
MetricsType: metricsclient.MetricsDataMetricsType(ffMetricType),
Timestamp: timeStamp,
},
{
Count: 1,
Attributes: []metricsclient.KeyValue{
{Key: featureIdentifierAttribute, Value: "feature2"},
{Key: featureNameAttribute, Value: "feature2"},
{Key: variationIdentifierAttribute, Value: "var2"},
{Key: variationValueAttribute, Value: "value2"},
{Key: sdkTypeAttribute, Value: sdkType},
{Key: sdkLanguageAttribute, Value: sdkLanguage},
{Key: sdkVersionAttribute, Value: SdkVersion},
{Key: targetAttribute, Value: globalTarget},
},
MetricsType: metricsclient.MetricsDataMetricsType(ffMetricType),
Timestamp: timeStamp,
},
},
TargetData: &[]metricsclient.TargetData{
{
Identifier: "target1",
Name: "Target One",
Attributes: []metricsclient.KeyValue{{Key: "key1", Value: "value1"}},
},
{
Identifier: "target2",
Name: "Target Two",
Attributes: []metricsclient.KeyValue{},
},
},
}

// Verify that PostMetricsWithResponse was called correctly
assert.Equal(t, 1, mClient.CallCount, "PostMetricsWithResponse should be called once")
assert.ElementsMatch(t, *mClient.LastBody.MetricsData, *expectedAnalyticsPayload.MetricsData)
assert.ElementsMatch(t, *mClient.LastBody.TargetData, *expectedAnalyticsPayload.TargetData)

// Verify both caches were reset
if service.evaluationAnalytics.size() != 0 {
t.Errorf("Expected the evaluation analytics cache to be reset")
}

if service.targetAnalytics.size() != 0 {
t.Errorf("Expected the target analytics cache to be reset")
}
}

func boolPtr(b bool) *bool {
return &b
}

0 comments on commit 3a5e792

Please sign in to comment.