...

Source file src/github.com/launchdarkly/go-sdk-events/v2/events_output_test.go

Documentation: github.com/launchdarkly/go-sdk-events/v2

     1  package ldevents
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
     8  	"github.com/launchdarkly/go-sdk-common/v3/ldreason"
     9  	"github.com/launchdarkly/go-sdk-common/v3/lduser"
    10  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    11  
    12  	m "github.com/launchdarkly/go-test-helpers/v3/matchers"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  var (
    19  	withoutReasons = NewEventFactory(false, fakeTimeFn)
    20  	withReasons    = NewEventFactory(true, fakeTimeFn)
    21  )
    22  
    23  func withTestContextsAndConfigs(t *testing.T, action func(*testing.T, EventInputContext, EventsConfiguration)) {
    24  	singleCtx := Context(ldcontext.New("user-key"))
    25  	multiCtx := Context(ldcontext.NewMulti(ldcontext.New("user-key"), ldcontext.NewWithKind("org", "org-key")))
    26  
    27  	privateConfig := basicConfigWithoutPrivateAttrs()
    28  	privateConfig.AllAttributesPrivate = true
    29  
    30  	t.Run("single kind, no private attributes", func(t *testing.T) {
    31  		action(t, singleCtx, basicConfigWithoutPrivateAttrs())
    32  	})
    33  
    34  	t.Run("multi-kind, no private attributes", func(t *testing.T) {
    35  		action(t, multiCtx, basicConfigWithoutPrivateAttrs())
    36  	})
    37  
    38  	t.Run("single kind, with private attributes", func(t *testing.T) {
    39  		action(t, singleCtx, privateConfig)
    40  	})
    41  
    42  	t.Run("multi-kind, with private attributes", func(t *testing.T) {
    43  		action(t, multiCtx, privateConfig)
    44  	})
    45  }
    46  
    47  func TestEventOutputFullEvents(t *testing.T) {
    48  	withTestContextsAndConfigs(t, func(t *testing.T, context EventInputContext, config EventsConfiguration) {
    49  		flag := FlagEventProperties{Key: "flagkey", Version: 100}
    50  
    51  		formatter := eventOutputFormatter{
    52  			contextFormatter: newEventContextFormatter(config),
    53  			config:           config,
    54  		}
    55  
    56  		// In this test, we are assuming that the output of eventContextFormatter is correct with regard to
    57  		// private attributes; those details are covered in the tests for eventContextFormatter itself. We
    58  		// just want to verify here that eventOutputFormatter is actually *using* eventContextFormatter with
    59  		// the specified configuration.
    60  		contextJSON := contextJSON(context, config)
    61  		contextKeys := expectedContextKeys(context.context)
    62  
    63  		t.Run("feature", func(t *testing.T) {
    64  			event1 := withoutReasons.NewEvaluationData(flag, context, ldreason.NewEvaluationDetail(ldvalue.String("v"), 1, noReason),
    65  				false, ldvalue.String("dv"), "")
    66  			verifyEventOutput(t, formatter, event1,
    67  				m.JSONEqual(map[string]interface{}{
    68  					"kind":         "feature",
    69  					"creationDate": fakeTime,
    70  					"key":          flag.Key,
    71  					"version":      flag.Version,
    72  					"contextKeys":  contextKeys,
    73  					"variation":    1,
    74  					"value":        "v",
    75  					"default":      "dv",
    76  				}))
    77  
    78  			event1r := withReasons.NewEvaluationData(flag, context,
    79  				ldreason.NewEvaluationDetail(ldvalue.String("v"), 1, ldreason.NewEvalReasonFallthrough()),
    80  				false, ldvalue.String("dv"), "")
    81  			verifyEventOutput(t, formatter, event1r,
    82  				m.JSONEqual(map[string]interface{}{
    83  					"kind":         "feature",
    84  					"creationDate": fakeTime,
    85  					"key":          flag.Key,
    86  					"version":      flag.Version,
    87  					"contextKeys":  contextKeys,
    88  					"variation":    1,
    89  					"value":        "v",
    90  					"default":      "dv",
    91  					"reason":       json.RawMessage(`{"kind":"FALLTHROUGH"}`),
    92  				}))
    93  
    94  			event2 := withoutReasons.NewEvaluationData(flag, context, ldreason.EvaluationDetail{Value: ldvalue.String("v")},
    95  				false, ldvalue.String("dv"), "")
    96  			event2.Variation = ldvalue.OptionalInt{}
    97  			verifyEventOutput(t, formatter, event2,
    98  				m.JSONEqual(map[string]interface{}{
    99  					"kind":         "feature",
   100  					"creationDate": fakeTime,
   101  					"key":          flag.Key,
   102  					"version":      flag.Version,
   103  					"contextKeys":  contextKeys,
   104  					"value":        "v",
   105  					"default":      "dv",
   106  				}))
   107  
   108  			event3 := withoutReasons.NewEvaluationData(flag, context, ldreason.NewEvaluationDetail(ldvalue.String("v"), 1, noReason),
   109  				false, ldvalue.String("dv"), "pre")
   110  			verifyEventOutput(t, formatter, event3,
   111  				m.JSONEqual(map[string]interface{}{
   112  					"kind":         "feature",
   113  					"creationDate": fakeTime,
   114  					"key":          flag.Key,
   115  					"version":      flag.Version,
   116  					"contextKeys":  contextKeys,
   117  					"variation":    1,
   118  					"value":        "v",
   119  					"default":      "dv",
   120  					"prereqOf":     "pre",
   121  				}))
   122  
   123  			event4 := withoutReasons.NewUnknownFlagEvaluationData("flagkey", context,
   124  				ldvalue.String("dv"), ldreason.EvaluationReason{})
   125  			verifyEventOutput(t, formatter, event4,
   126  				m.JSONEqual(map[string]interface{}{
   127  					"kind":         "feature",
   128  					"creationDate": fakeTime,
   129  					"key":          flag.Key,
   130  					"contextKeys":  contextKeys,
   131  					"value":        "dv",
   132  					"default":      "dv",
   133  				}))
   134  		})
   135  
   136  		t.Run("debug", func(t *testing.T) {
   137  			event1 := withoutReasons.NewEvaluationData(flag, context, ldreason.NewEvaluationDetail(ldvalue.String("v"), 1, noReason),
   138  				false, ldvalue.String("dv"), "")
   139  			event1.debug = true
   140  			verifyEventOutput(t, formatter, event1,
   141  				m.JSONEqual(map[string]interface{}{
   142  					"kind":         "debug",
   143  					"creationDate": fakeTime,
   144  					"key":          flag.Key,
   145  					"version":      flag.Version,
   146  					"context":      contextJSON,
   147  					"variation":    1,
   148  					"value":        "v",
   149  					"default":      "dv",
   150  				}))
   151  		})
   152  
   153  		t.Run("identify", func(t *testing.T) {
   154  			event := withoutReasons.NewIdentifyEventData(context)
   155  			verifyEventOutput(t, formatter, event,
   156  				m.JSONEqual(map[string]interface{}{
   157  					"kind":         "identify",
   158  					"creationDate": fakeTime,
   159  					"context":      contextJSON,
   160  				}))
   161  		})
   162  
   163  		t.Run("custom", func(t *testing.T) {
   164  			event1 := withoutReasons.NewCustomEventData("eventkey", context, ldvalue.Null(), false, 0)
   165  			verifyEventOutput(t, formatter, event1,
   166  				m.JSONEqual(map[string]interface{}{
   167  					"kind":         "custom",
   168  					"key":          "eventkey",
   169  					"creationDate": fakeTime,
   170  					"contextKeys":  contextKeys,
   171  				}))
   172  
   173  			event2 := withoutReasons.NewCustomEventData("eventkey", context, ldvalue.String("d"), false, 0)
   174  			verifyEventOutput(t, formatter, event2,
   175  				m.JSONEqual(map[string]interface{}{
   176  					"kind":         "custom",
   177  					"key":          "eventkey",
   178  					"creationDate": fakeTime,
   179  					"contextKeys":  contextKeys,
   180  					"data":         "d",
   181  				}))
   182  
   183  			event3 := withoutReasons.NewCustomEventData("eventkey", context, ldvalue.Null(), true, 2.5)
   184  			verifyEventOutput(t, formatter, event3,
   185  				m.JSONEqual(map[string]interface{}{
   186  					"kind":         "custom",
   187  					"key":          "eventkey",
   188  					"creationDate": fakeTime,
   189  					"contextKeys":  contextKeys,
   190  					"metricValue":  2.5,
   191  				}))
   192  		})
   193  
   194  		t.Run("index", func(t *testing.T) {
   195  			event := indexEvent{BaseEvent: BaseEvent{CreationDate: fakeTime, Context: context}}
   196  			verifyEventOutput(t, formatter, event,
   197  				m.JSONEqual(map[string]interface{}{
   198  					"kind":         "index",
   199  					"creationDate": fakeTime,
   200  					"context":      contextJSON,
   201  				}))
   202  		})
   203  
   204  		t.Run("raw", func(t *testing.T) {
   205  			rawData := json.RawMessage(`{"kind":"alias","arbitrary":["we","don't","care","what's","in","here"]}`)
   206  			event := rawEvent{data: rawData}
   207  			verifyEventOutput(t, formatter, event, m.JSONEqual(rawData))
   208  		})
   209  	})
   210  }
   211  
   212  func TestEventOutputSummaryEvents(t *testing.T) {
   213  	user := Context(lduser.NewUser("u"))
   214  	flag1v1 := FlagEventProperties{Key: "flag1", Version: 100}
   215  	flag1v2 := FlagEventProperties{Key: "flag1", Version: 200}
   216  	flag1Default := ldvalue.String("default1")
   217  	flag2 := FlagEventProperties{Key: "flag2", Version: 1}
   218  	flag2Default := ldvalue.String("default2")
   219  
   220  	formatter := eventOutputFormatter{
   221  		contextFormatter: newEventContextFormatter(basicConfigWithoutPrivateAttrs()),
   222  		config:           basicConfigWithoutPrivateAttrs(),
   223  	}
   224  
   225  	t.Run("summary - single flag, single counter", func(t *testing.T) {
   226  		es1 := newEventSummarizer()
   227  		event1 := withoutReasons.NewEvaluationData(flag1v1, user, ldreason.NewEvaluationDetail(ldvalue.String("v"), 1, noReason),
   228  			false, ldvalue.String("dv"), "")
   229  		es1.summarizeEvent(event1)
   230  		verifySummaryEventOutput(t, formatter, es1.snapshot(),
   231  			m.JSONEqual(map[string]interface{}{
   232  				"kind":      "summary",
   233  				"startDate": fakeTime,
   234  				"endDate":   fakeTime,
   235  				"features": map[string]interface{}{
   236  					"flag1": map[string]interface{}{
   237  						"counters":     json.RawMessage(`[{"count":1,"value":"v","variation":1,"version":100}]`),
   238  						"contextKinds": []string{"user"},
   239  						"default":      "dv",
   240  					},
   241  				},
   242  			}))
   243  
   244  		es2 := newEventSummarizer()
   245  		event2 := withoutReasons.NewEvaluationData(flag1v1, user, ldreason.EvaluationDetail{Value: ldvalue.String("dv")},
   246  			false, ldvalue.String("dv"), "")
   247  		event2.Variation = ldvalue.OptionalInt{}
   248  		es2.summarizeEvent(event2)
   249  		verifySummaryEventOutput(t, formatter, es2.snapshot(),
   250  			m.JSONEqual(map[string]interface{}{
   251  				"kind":      "summary",
   252  				"startDate": fakeTime,
   253  				"endDate":   fakeTime,
   254  				"features": map[string]interface{}{
   255  					"flag1": map[string]interface{}{
   256  						"counters":     json.RawMessage(`[{"count":1,"value":"dv","version":100}]`),
   257  						"contextKinds": []string{"user"},
   258  						"default":      "dv",
   259  					},
   260  				},
   261  			}))
   262  
   263  		es3 := newEventSummarizer()
   264  		event3 := withoutReasons.NewUnknownFlagEvaluationData("flagkey", user,
   265  			ldvalue.String("dv"), ldreason.EvaluationReason{})
   266  		es3.summarizeEvent(event3)
   267  		verifySummaryEventOutput(t, formatter, es3.snapshot(),
   268  			m.JSONEqual(map[string]interface{}{
   269  				"kind":      "summary",
   270  				"startDate": fakeTime,
   271  				"endDate":   fakeTime,
   272  				"features": map[string]interface{}{
   273  					"flagkey": map[string]interface{}{
   274  						"counters":     json.RawMessage(`[{"count":1,"value":"dv","unknown":true}]`),
   275  						"contextKinds": []string{"user"},
   276  						"default":      "dv",
   277  					},
   278  				},
   279  			}))
   280  	})
   281  
   282  	t.Run("summary - multiple counters", func(t *testing.T) {
   283  		es := newEventSummarizer()
   284  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v1, user, ldreason.NewEvaluationDetail(ldvalue.String("a"), 1, noReason),
   285  			false, flag1Default, ""))
   286  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v1, user, ldreason.NewEvaluationDetail(ldvalue.String("b"), 2, noReason),
   287  			false, flag1Default, ""))
   288  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v1, user, ldreason.NewEvaluationDetail(ldvalue.String("a"), 1, noReason),
   289  			false, flag1Default, ""))
   290  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v2, user, ldreason.NewEvaluationDetail(ldvalue.String("a"), 1, noReason),
   291  			false, flag1Default, ""))
   292  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag2, user, ldreason.NewEvaluationDetail(ldvalue.String("c"), 3, noReason),
   293  			false, flag2Default, ""))
   294  
   295  		bytes, count := formatter.makeOutputEvents(nil, es.snapshot())
   296  		require.Equal(t, 1, count)
   297  
   298  		// Using a nested matcher expression here, rather than an equality assertion on the whole JSON object,
   299  		// because the ordering of array items in "counters" is indeterminate so we need m.ItemsInAnyOrder().
   300  		m.In(t).Assert(bytes, m.JSONArray().Should(m.Items(
   301  			m.MapOf(
   302  				m.KV("kind", m.Equal("summary")),
   303  				m.KV("startDate", m.Not(m.BeNil())),
   304  				m.KV("endDate", m.Not(m.BeNil())),
   305  				m.KV("features", m.MapOf(
   306  					m.KV("flag1", m.MapOf(
   307  						m.KV("default", m.JSONEqual(flag1Default)),
   308  						m.KV("counters", m.ItemsInAnyOrder(
   309  							m.JSONStrEqual(`{"version":100,"variation":1,"value":"a","count":2}`),
   310  							m.JSONStrEqual(`{"version":100,"variation":2,"value":"b","count":1}`),
   311  							m.JSONStrEqual(`{"version":200,"variation":1,"value":"a","count":1}`),
   312  						)),
   313  						m.KV("contextKinds", m.Items(m.Equal("user"))),
   314  					)),
   315  					m.KV("flag2", m.MapOf(
   316  						m.KV("default", m.JSONEqual(flag2Default)),
   317  						m.KV("counters", m.ItemsInAnyOrder(
   318  							m.JSONStrEqual(`{"version":1,"variation":3,"value":"c","count":1}`),
   319  						)),
   320  						m.KV("contextKinds", m.Items(m.Equal("user"))),
   321  					)),
   322  				)),
   323  			),
   324  		)))
   325  	})
   326  
   327  	t.Run("summary with multiple context kinds", func(t *testing.T) {
   328  		context1, context2, context3 := ldcontext.New("userkey1"), ldcontext.New("userkey2"), ldcontext.NewWithKind("org", "orgkey")
   329  
   330  		es := newEventSummarizer()
   331  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v1, Context(context1), ldreason.NewEvaluationDetail(ldvalue.String("a"), 1, noReason),
   332  			false, flag1Default, ""))
   333  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v1, Context(context2), ldreason.NewEvaluationDetail(ldvalue.String("b"), 2, noReason),
   334  			false, flag1Default, ""))
   335  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v1, Context(context3), ldreason.NewEvaluationDetail(ldvalue.String("a"), 1, noReason),
   336  			false, flag1Default, ""))
   337  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag1v2, Context(context1), ldreason.NewEvaluationDetail(ldvalue.String("a"), 1, noReason),
   338  			false, flag1Default, ""))
   339  		es.summarizeEvent(withoutReasons.NewEvaluationData(flag2, Context(context1), ldreason.NewEvaluationDetail(ldvalue.String("c"), 3, noReason),
   340  			false, flag2Default, ""))
   341  
   342  		bytes, count := formatter.makeOutputEvents(nil, es.snapshot())
   343  		require.Equal(t, 1, count)
   344  
   345  		m.In(t).Assert(bytes, m.JSONArray().Should(m.Items(
   346  			m.MapOf(
   347  				m.KV("kind", m.Equal("summary")),
   348  				m.KV("startDate", m.Not(m.BeNil())),
   349  				m.KV("endDate", m.Not(m.BeNil())),
   350  				m.KV("features", m.MapOf(
   351  					m.KV("flag1", m.MapOf(
   352  						m.KV("default", m.JSONEqual(flag1Default)),
   353  						m.KV("counters", m.Length().Should(m.Equal(3))),
   354  						m.KV("contextKinds", m.ItemsInAnyOrder(m.Equal("user"), m.Equal("org"))),
   355  					)),
   356  					m.KV("flag2", m.MapOf(
   357  						m.KV("default", m.JSONEqual(flag2Default)),
   358  						m.KV("counters", m.Length().Should(m.Equal(1))),
   359  						m.KV("contextKinds", m.Items(m.Equal("user"))),
   360  					)),
   361  				)),
   362  			),
   363  		)))
   364  	})
   365  
   366  	t.Run("empty payload", func(t *testing.T) {
   367  		bytes, count := formatter.makeOutputEvents([]anyEventOutput{}, eventSummary{})
   368  		assert.Nil(t, bytes)
   369  		assert.Equal(t, 0, count)
   370  	})
   371  }
   372  
   373  func verifyEventOutput(t *testing.T, formatter eventOutputFormatter, event anyEventInput, jsonMatcher m.Matcher) {
   374  	t.Helper()
   375  	bytes, count := formatter.makeOutputEvents([]anyEventOutput{event}, eventSummary{})
   376  	require.Equal(t, 1, count)
   377  	m.In(t).Assert(bytes, m.JSONArray().Should(m.Items(jsonMatcher)))
   378  }
   379  
   380  func verifySummaryEventOutput(t *testing.T, formatter eventOutputFormatter, summary eventSummary, jsonMatcher m.Matcher) {
   381  	t.Helper()
   382  	bytes, count := formatter.makeOutputEvents(nil, summary)
   383  	require.Equal(t, 1, count)
   384  	m.In(t).Assert(bytes, m.JSONArray().Should(m.Items(jsonMatcher)))
   385  }
   386  

View as plain text