...

Source file src/github.com/launchdarkly/go-server-sdk/v6/ldclient_end_to_end_test.go

Documentation: github.com/launchdarkly/go-server-sdk/v6

     1  package ldclient
     2  
     3  import (
     4  	"crypto/x509"
     5  	"encoding/json"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/launchdarkly/go-sdk-common/v3/ldlog"
    12  	"github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
    13  	"github.com/launchdarkly/go-sdk-common/v3/lduser"
    14  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    15  	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
    16  	"github.com/launchdarkly/go-server-sdk/v6/interfaces"
    17  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
    18  	"github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
    19  	"github.com/launchdarkly/go-server-sdk/v6/testhelpers/ldservices"
    20  
    21  	"github.com/launchdarkly/go-test-helpers/v3/httphelpers"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  const (
    28  	initializationFailedErrorMessage = "LaunchDarkly client initialization failed"
    29  	pollingModeWarningMessage        = "You should only disable the streaming API if instructed to do so by LaunchDarkly support"
    30  )
    31  
    32  var (
    33  	alwaysTrueFlag = ldbuilders.NewFlagBuilder("always-true-flag").SingleVariation(ldvalue.Bool(true)).Build()
    34  	testUser       = lduser.NewUser("test-user-key")
    35  )
    36  
    37  // This file contains smoke tests for a complete SDK instance running against embedded HTTP servers. We have many
    38  // component-level tests elsewhere (including tests of the components' network behavior using an instrumented
    39  // HTTPClient), but the end-to-end tests verify that the client is setting those components up correctly, with a
    40  // configuration that's as close to the default configuration as possible (just changing the service URIs).
    41  
    42  func assertNoMoreRequests(t *testing.T, requestsCh <-chan httphelpers.HTTPRequestInfo) {
    43  	assert.Equal(t, 0, len(requestsCh))
    44  }
    45  
    46  func TestDefaultDataSourceIsStreaming(t *testing.T) {
    47  	data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
    48  	streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
    49  	httphelpers.WithServer(streamHandler, func(streamServer *httptest.Server) {
    50  		logCapture := ldlogtest.NewMockLog()
    51  		defer logCapture.DumpIfTestFailed(t)
    52  
    53  		config := Config{
    54  			Events:           ldcomponents.NoEvents(),
    55  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
    56  			ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL},
    57  		}
    58  
    59  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
    60  		require.NoError(t, err)
    61  		defer client.Close()
    62  
    63  		assert.Equal(t, string(interfaces.DataSourceStateValid), string(client.GetDataSourceStatusProvider().GetStatus().State))
    64  
    65  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
    66  		assert.True(t, value)
    67  	})
    68  }
    69  
    70  func TestClientStartsInStreamingMode(t *testing.T) {
    71  	data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
    72  	streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
    73  	handler, requestsCh := httphelpers.RecordingHandler(streamHandler)
    74  	httphelpers.WithServer(handler, func(streamServer *httptest.Server) {
    75  		logCapture := ldlogtest.NewMockLog()
    76  		defer logCapture.DumpIfTestFailed(t)
    77  
    78  		config := Config{
    79  			Events:           ldcomponents.NoEvents(),
    80  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
    81  			ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL},
    82  		}
    83  
    84  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
    85  		require.NoError(t, err)
    86  		defer client.Close()
    87  
    88  		assert.Equal(t, string(interfaces.DataSourceStateValid), string(client.GetDataSourceStatusProvider().GetStatus().State))
    89  
    90  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
    91  		assert.True(t, value)
    92  
    93  		r := <-requestsCh
    94  		assert.Equal(t, testSdkKey, r.Request.Header.Get("Authorization"))
    95  		assertNoMoreRequests(t, requestsCh)
    96  
    97  		assert.Len(t, logCapture.GetOutput(ldlog.Error), 0)
    98  		assert.Len(t, logCapture.GetOutput(ldlog.Warn), 0)
    99  	})
   100  }
   101  
   102  func TestClientFailsToStartInStreamingModeWith401Error(t *testing.T) {
   103  	handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(401))
   104  	httphelpers.WithServer(handler, func(streamServer *httptest.Server) {
   105  		logCapture := ldlogtest.NewMockLog()
   106  
   107  		config := Config{
   108  			Events:           ldcomponents.NoEvents(),
   109  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
   110  			ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL},
   111  		}
   112  
   113  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   114  		require.Error(t, err)
   115  		require.NotNil(t, client)
   116  		defer client.Close()
   117  
   118  		assert.Equal(t, initializationFailedErrorMessage, err.Error())
   119  
   120  		assert.Equal(t, string(interfaces.DataSourceStateOff), string(client.GetDataSourceStatusProvider().GetStatus().State))
   121  
   122  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
   123  		assert.False(t, value)
   124  
   125  		r := <-requestsCh
   126  		assert.Equal(t, testSdkKey, r.Request.Header.Get("Authorization"))
   127  		assertNoMoreRequests(t, requestsCh)
   128  
   129  		expectedError := "Error in stream connection (giving up permanently): HTTP error 401 (invalid SDK key)"
   130  		assert.Equal(t, []string{expectedError}, logCapture.GetOutput(ldlog.Error))
   131  		assert.Equal(t, []string{initializationFailedErrorMessage}, logCapture.GetOutput(ldlog.Warn))
   132  	})
   133  }
   134  
   135  func TestClientRetriesConnectionInStreamingModeWithNonFatalError(t *testing.T) {
   136  	data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
   137  	streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
   138  	failThenSucceedHandler := httphelpers.SequentialHandler(httphelpers.HandlerWithStatus(503), streamHandler)
   139  	handler, requestsCh := httphelpers.RecordingHandler(failThenSucceedHandler)
   140  	httphelpers.WithServer(handler, func(streamServer *httptest.Server) {
   141  		logCapture := ldlogtest.NewMockLog()
   142  
   143  		config := Config{
   144  			Events:           ldcomponents.NoEvents(),
   145  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
   146  			ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL},
   147  		}
   148  
   149  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   150  		require.NoError(t, err)
   151  		defer client.Close()
   152  
   153  		assert.Equal(t, string(interfaces.DataSourceStateValid), string(client.GetDataSourceStatusProvider().GetStatus().State))
   154  
   155  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
   156  		assert.True(t, value)
   157  
   158  		r0 := <-requestsCh
   159  		assert.Equal(t, testSdkKey, r0.Request.Header.Get("Authorization"))
   160  		r1 := <-requestsCh
   161  		assert.Equal(t, testSdkKey, r1.Request.Header.Get("Authorization"))
   162  		assertNoMoreRequests(t, requestsCh)
   163  
   164  		expectedWarning := "Error in stream connection (will retry): HTTP error 503"
   165  		assert.Equal(t, []string{expectedWarning}, logCapture.GetOutput(ldlog.Warn))
   166  		assert.Len(t, logCapture.GetOutput(ldlog.Error), 0)
   167  	})
   168  }
   169  
   170  func TestClientStartsInPollingMode(t *testing.T) {
   171  	data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
   172  	pollHandler, requestsCh := httphelpers.RecordingHandler(ldservices.ServerSidePollingServiceHandler(data))
   173  	httphelpers.WithServer(pollHandler, func(pollServer *httptest.Server) {
   174  		logCapture := ldlogtest.NewMockLog()
   175  
   176  		config := Config{
   177  			DataSource:       ldcomponents.PollingDataSource(),
   178  			Events:           ldcomponents.NoEvents(),
   179  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
   180  			ServiceEndpoints: interfaces.ServiceEndpoints{Polling: pollServer.URL},
   181  		}
   182  
   183  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   184  		require.NoError(t, err)
   185  		defer client.Close()
   186  
   187  		assert.Equal(t, string(interfaces.DataSourceStateValid), string(client.GetDataSourceStatusProvider().GetStatus().State))
   188  
   189  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
   190  		assert.True(t, value)
   191  
   192  		r := <-requestsCh
   193  		assert.Equal(t, testSdkKey, r.Request.Header.Get("Authorization"))
   194  		assertNoMoreRequests(t, requestsCh)
   195  
   196  		assert.Len(t, logCapture.GetOutput(ldlog.Error), 0)
   197  		assert.Equal(t, []string{pollingModeWarningMessage}, logCapture.GetOutput(ldlog.Warn))
   198  	})
   199  }
   200  
   201  func TestClientFailsToStartInPollingModeWith401Error(t *testing.T) {
   202  	handler, requestsCh := httphelpers.RecordingHandler(httphelpers.HandlerWithStatus(401))
   203  	httphelpers.WithServer(handler, func(pollServer *httptest.Server) {
   204  		logCapture := ldlogtest.NewMockLog()
   205  
   206  		config := Config{
   207  			DataSource:       ldcomponents.PollingDataSource(),
   208  			Events:           ldcomponents.NoEvents(),
   209  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
   210  			ServiceEndpoints: interfaces.ServiceEndpoints{Polling: pollServer.URL},
   211  		}
   212  
   213  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   214  		require.Error(t, err)
   215  		require.NotNil(t, client)
   216  		defer client.Close()
   217  
   218  		assert.Equal(t, initializationFailedErrorMessage, err.Error())
   219  
   220  		assert.Equal(t, string(interfaces.DataSourceStateOff), string(client.GetDataSourceStatusProvider().GetStatus().State))
   221  
   222  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
   223  		assert.False(t, value)
   224  
   225  		r := <-requestsCh
   226  		assert.Equal(t, testSdkKey, r.Request.Header.Get("Authorization"))
   227  		assertNoMoreRequests(t, requestsCh)
   228  
   229  		expectedError := "Error on polling request (giving up permanently): HTTP error 401 (invalid SDK key)"
   230  		assert.Equal(t, []string{expectedError}, logCapture.GetOutput(ldlog.Error))
   231  		assert.Equal(t, []string{pollingModeWarningMessage, initializationFailedErrorMessage}, logCapture.GetOutput(ldlog.Warn))
   232  	})
   233  }
   234  
   235  func TestClientSendsEventWithoutDiagnostics(t *testing.T) {
   236  	eventsHandler, eventRequestsCh := httphelpers.RecordingHandler(ldservices.ServerSideEventsServiceHandler())
   237  	httphelpers.WithServer(eventsHandler, func(eventsServer *httptest.Server) {
   238  		data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
   239  		streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
   240  		httphelpers.WithServer(streamHandler, func(streamServer *httptest.Server) {
   241  			logCapture := ldlogtest.NewMockLog()
   242  
   243  			config := Config{
   244  				DiagnosticOptOut: true,
   245  				Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
   246  				ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL, Events: eventsServer.URL},
   247  			}
   248  
   249  			client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   250  			require.NoError(t, err)
   251  			defer client.Close()
   252  
   253  			client.Identify(testUser)
   254  			client.Flush()
   255  
   256  			r := <-eventRequestsCh
   257  			assert.Equal(t, testSdkKey, r.Request.Header.Get("Authorization"))
   258  			assert.Equal(t, "/bulk", r.Request.URL.Path)
   259  			assertNoMoreRequests(t, eventRequestsCh)
   260  
   261  			var jsonValue ldvalue.Value
   262  			err = json.Unmarshal(r.Body, &jsonValue)
   263  			assert.NoError(t, err)
   264  			assert.Equal(t, ldvalue.String("identify"), jsonValue.GetByIndex(0).GetByKey("kind"))
   265  		})
   266  	})
   267  }
   268  
   269  func TestClientSendsDiagnostics(t *testing.T) {
   270  	eventsHandler, eventRequestsCh := httphelpers.RecordingHandler(ldservices.ServerSideEventsServiceHandler())
   271  	httphelpers.WithServer(eventsHandler, func(eventsServer *httptest.Server) {
   272  		data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
   273  		streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
   274  		httphelpers.WithServer(streamHandler, func(streamServer *httptest.Server) {
   275  			config := Config{
   276  				Logging:          ldcomponents.Logging().Loggers(sharedtest.NewTestLoggers()),
   277  				ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL, Events: eventsServer.URL},
   278  			}
   279  
   280  			client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   281  			require.NoError(t, err)
   282  			defer client.Close()
   283  
   284  			r := <-eventRequestsCh
   285  			assert.Equal(t, testSdkKey, r.Request.Header.Get("Authorization"))
   286  			assert.Equal(t, "/diagnostic", r.Request.URL.Path)
   287  			var jsonValue ldvalue.Value
   288  			err = json.Unmarshal(r.Body, &jsonValue)
   289  			assert.NoError(t, err)
   290  			assert.Equal(t, ldvalue.String("diagnostic-init"), jsonValue.GetByKey("kind"))
   291  		})
   292  	})
   293  }
   294  
   295  func TestClientUsesCustomTLSConfiguration(t *testing.T) {
   296  	data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
   297  	streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
   298  
   299  	httphelpers.WithSelfSignedServer(streamHandler, func(server *httptest.Server, certData []byte, certs *x509.CertPool) {
   300  		config := Config{
   301  			Events:           ldcomponents.NoEvents(),
   302  			HTTP:             ldcomponents.HTTPConfiguration().CACert(certData),
   303  			Logging:          ldcomponents.Logging().Loggers(sharedtest.NewTestLoggers()),
   304  			ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: server.URL},
   305  		}
   306  
   307  		client, err := MakeCustomClient(testSdkKey, config, time.Second*5)
   308  		require.NoError(t, err)
   309  		defer client.Close()
   310  
   311  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
   312  		assert.True(t, value)
   313  	})
   314  }
   315  
   316  func TestClientStartupTimesOut(t *testing.T) {
   317  	data := ldservices.NewServerSDKData().Flags(&alwaysTrueFlag)
   318  	streamHandler, _ := ldservices.ServerSideStreamingServiceHandler(data.ToPutEvent())
   319  	slowHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   320  		time.Sleep(300 * time.Millisecond)
   321  		streamHandler.ServeHTTP(w, r)
   322  	})
   323  
   324  	httphelpers.WithServer(slowHandler, func(streamServer *httptest.Server) {
   325  		logCapture := ldlogtest.NewMockLog()
   326  
   327  		config := Config{
   328  			Events:           ldcomponents.NoEvents(),
   329  			Logging:          ldcomponents.Logging().Loggers(logCapture.Loggers),
   330  			ServiceEndpoints: interfaces.ServiceEndpoints{Streaming: streamServer.URL},
   331  		}
   332  
   333  		client, err := MakeCustomClient(testSdkKey, config, time.Millisecond*100)
   334  		require.Error(t, err)
   335  		require.NotNil(t, client)
   336  		defer client.Close()
   337  
   338  		assert.Equal(t, "timeout encountered waiting for LaunchDarkly client initialization", err.Error())
   339  
   340  		value, _ := client.BoolVariation(alwaysTrueFlag.Key, testUser, false)
   341  		assert.False(t, value)
   342  
   343  		assert.Equal(t, []string{"Timeout encountered waiting for LaunchDarkly client initialization"}, logCapture.GetOutput(ldlog.Warn))
   344  		assert.Len(t, logCapture.GetOutput(ldlog.Error), 0)
   345  	})
   346  }
   347  

View as plain text