github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/collector_test.go (about)

     1  package internal
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/lulzWill/go-agent/internal/crossagent"
    13  	"github.com/lulzWill/go-agent/internal/logger"
    14  )
    15  
    16  func TestLicenseInvalid(t *testing.T) {
    17  	r := CompactJSONString(`{
    18  		"exception":{
    19  			"message":"Invalid license key, please contact support@newrelic.com",
    20  			"error_type":"NewRelic::Agent::LicenseException"
    21  		}
    22  	}`)
    23  	reply, err := parseResponse([]byte(r))
    24  	if reply != nil {
    25  		t.Fatal(string(reply))
    26  	}
    27  	if !IsLicenseException(err) {
    28  		t.Fatal(err)
    29  	}
    30  }
    31  
    32  func TestRedirectSuccess(t *testing.T) {
    33  	r := `{"return_value":"staging-collector-101.newrelic.com"}`
    34  	reply, err := parseResponse([]byte(r))
    35  	if nil != err {
    36  		t.Fatal(err)
    37  	}
    38  	if string(reply) != `"staging-collector-101.newrelic.com"` {
    39  		t.Fatal(string(reply))
    40  	}
    41  }
    42  
    43  func TestEmptyHash(t *testing.T) {
    44  	reply, err := parseResponse([]byte(`{}`))
    45  	if nil != err {
    46  		t.Fatal(err)
    47  	}
    48  	if nil != reply {
    49  		t.Fatal(string(reply))
    50  	}
    51  }
    52  
    53  func TestReturnValueNull(t *testing.T) {
    54  	reply, err := parseResponse([]byte(`{"return_value":null}`))
    55  	if nil != err {
    56  		t.Fatal(err)
    57  	}
    58  	if "null" != string(reply) {
    59  		t.Fatal(string(reply))
    60  	}
    61  }
    62  
    63  func TestReplyNull(t *testing.T) {
    64  	reply, err := parseResponse(nil)
    65  
    66  	if nil == err || err.Error() != `unexpected end of JSON input` {
    67  		t.Fatal(err)
    68  	}
    69  	if nil != reply {
    70  		t.Fatal(string(reply))
    71  	}
    72  }
    73  
    74  func TestConnectSuccess(t *testing.T) {
    75  	inner := `{
    76  	"agent_run_id":"599551769342729",
    77  	"product_level":40,
    78  	"js_agent_file":"",
    79  	"cross_process_id":"12345#12345",
    80  	"collect_errors":true,
    81  	"url_rules":[
    82  		{
    83  			"each_segment":false,
    84  			"match_expression":".*\\.(txt|udl|plist|css)$",
    85  			"eval_order":1000,
    86  			"replace_all":false,
    87  			"ignore":false,
    88  			"terminate_chain":true,
    89  			"replacement":"\/*.\\1"
    90  		},
    91  		{
    92  			"each_segment":true,
    93  			"match_expression":"^[0-9][0-9a-f_,.-]*$",
    94  			"eval_order":1001,
    95  			"replace_all":false,
    96  			"ignore":false,
    97  			"terminate_chain":false,
    98  			"replacement":"*"
    99  		}
   100  	],
   101  	"messages":[
   102  		{
   103  			"message":"Reporting to staging",
   104  			"level":"INFO"
   105  		}
   106  	],
   107  	"data_report_period":60,
   108  	"collect_traces":true,
   109  	"sampling_rate":0,
   110  	"js_agent_loader":"",
   111  	"encoding_key":"the-encoding-key",
   112  	"apdex_t":0.5,
   113  	"collect_analytics_events":true,
   114  	"trusted_account_ids":[49402]
   115  }`
   116  	outer := `{"return_value":` + inner + `}`
   117  	reply, err := parseResponse([]byte(outer))
   118  
   119  	if nil != err {
   120  		t.Fatal(err)
   121  	}
   122  	if string(reply) != inner {
   123  		t.Fatal(string(reply))
   124  	}
   125  }
   126  
   127  func TestClientError(t *testing.T) {
   128  	r := `{"exception":{"message":"something","error_type":"my_error"}}`
   129  	reply, err := parseResponse([]byte(r))
   130  	if nil == err || err.Error() != "my_error: something" {
   131  		t.Fatal(err)
   132  	}
   133  	if nil != reply {
   134  		t.Fatal(string(reply))
   135  	}
   136  }
   137  
   138  func TestForceRestartException(t *testing.T) {
   139  	// NOTE: This string was generated manually, not taken from the actual
   140  	// collector.
   141  	r := CompactJSONString(`{
   142  		"exception":{
   143  			"message":"something",
   144  			"error_type":"NewRelic::Agent::ForceRestartException"
   145  		}
   146  	}`)
   147  	reply, err := parseResponse([]byte(r))
   148  	if reply != nil {
   149  		t.Fatal(string(reply))
   150  	}
   151  	if !IsRestartException(err) {
   152  		t.Fatal(err)
   153  	}
   154  }
   155  
   156  func TestForceDisconnectException(t *testing.T) {
   157  	// NOTE: This string was generated manually, not taken from the actual
   158  	// collector.
   159  	r := CompactJSONString(`{
   160  		"exception":{
   161  			"message":"something",
   162  			"error_type":"NewRelic::Agent::ForceDisconnectException"
   163  		}
   164  	}`)
   165  	reply, err := parseResponse([]byte(r))
   166  	if reply != nil {
   167  		t.Fatal(string(reply))
   168  	}
   169  	if !IsDisconnect(err) {
   170  		t.Fatal(err)
   171  	}
   172  }
   173  
   174  func TestRuntimeError(t *testing.T) {
   175  	// NOTE: This string was generated manually, not taken from the actual
   176  	// collector.
   177  	r := `{"exception":{"message":"something","error_type":"RuntimeError"}}`
   178  	reply, err := parseResponse([]byte(r))
   179  	if reply != nil {
   180  		t.Fatal(string(reply))
   181  	}
   182  	if !IsRuntime(err) {
   183  		t.Fatal(err)
   184  	}
   185  }
   186  
   187  func TestUnknownError(t *testing.T) {
   188  	r := `{"exception":{"message":"something","error_type":"unknown_type"}}`
   189  	reply, err := parseResponse([]byte(r))
   190  	if reply != nil {
   191  		t.Fatal(string(reply))
   192  	}
   193  	if nil == err || err.Error() != "unknown_type: something" {
   194  		t.Fatal(err)
   195  	}
   196  }
   197  
   198  func TestUrl(t *testing.T) {
   199  	cmd := RpmCmd{
   200  		Name:      "foo_method",
   201  		Collector: "example.com",
   202  	}
   203  	cs := RpmControls{
   204  		License:      "123abc",
   205  		Client:       nil,
   206  		Logger:       nil,
   207  		AgentVersion: "1",
   208  	}
   209  
   210  	out := rpmURL(cmd, cs)
   211  	u, err := url.Parse(out)
   212  	if err != nil {
   213  		t.Fatalf("url.Parse(%q) = %q", out, err)
   214  	}
   215  
   216  	got := u.Query().Get("license_key")
   217  	if got != cs.License {
   218  		t.Errorf("got=%q cmd.License=%q", got, cs.License)
   219  	}
   220  	if u.Scheme != "https" {
   221  		t.Error(u.Scheme)
   222  	}
   223  }
   224  
   225  const (
   226  	unknownRequiredPolicyBody = `{"return_value":{"redirect_host":"special_collector","security_policies":{"unknown_policy":{"enabled":true,"required":true}}}}`
   227  	redirectBody              = `{"return_value":{"redirect_host":"special_collector"}}`
   228  	connectBody               = `{"return_value":{"agent_run_id":"my_agent_run_id"}}`
   229  	disconnectBody            = `{"exception":{"error_type":"NewRelic::Agent::ForceDisconnectException"}}`
   230  	licenseBody               = `{"exception":{"error_type":"NewRelic::Agent::LicenseException"}}`
   231  	malformedBody             = `{"return_value":}}`
   232  )
   233  
   234  func makeResponse(code int, body string) *http.Response {
   235  	return &http.Response{
   236  		StatusCode: code,
   237  		Body:       ioutil.NopCloser(strings.NewReader(body)),
   238  	}
   239  }
   240  
   241  type endpointResult struct {
   242  	response *http.Response
   243  	err      error
   244  }
   245  
   246  type connectMockRoundTripper struct {
   247  	redirect endpointResult
   248  	connect  endpointResult
   249  }
   250  
   251  func (m connectMockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   252  	cmd := r.URL.Query().Get("method")
   253  	switch cmd {
   254  	case cmdPreconnect:
   255  		return m.redirect.response, m.redirect.err
   256  	case cmdConnect:
   257  		return m.connect.response, m.connect.err
   258  	default:
   259  		return nil, fmt.Errorf("unknown cmd: %s", cmd)
   260  	}
   261  }
   262  
   263  func (m connectMockRoundTripper) CancelRequest(req *http.Request) {}
   264  
   265  type testConfig struct{}
   266  
   267  func (tc testConfig) CreateConnectJSON(*SecurityPolicies) ([]byte, error) {
   268  	return []byte(`"connect-json"`), nil
   269  }
   270  
   271  func testConnectHelper(transport http.RoundTripper) (*ConnectReply, error) {
   272  	cs := RpmControls{
   273  		License:      "12345",
   274  		Client:       &http.Client{Transport: transport},
   275  		Logger:       logger.ShimLogger{},
   276  		AgentVersion: "1",
   277  	}
   278  
   279  	return ConnectAttempt(testConfig{}, "", cs)
   280  }
   281  
   282  func TestConnectAttemptSuccess(t *testing.T) {
   283  	run, err := testConnectHelper(connectMockRoundTripper{
   284  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   285  		connect:  endpointResult{response: makeResponse(200, connectBody)},
   286  	})
   287  	if nil == run || nil != err {
   288  		t.Fatal(run, err)
   289  	}
   290  	if run.Collector != "special_collector" {
   291  		t.Error(run.Collector)
   292  	}
   293  	if run.RunID != "my_agent_run_id" {
   294  		t.Error(run)
   295  	}
   296  }
   297  
   298  func TestConnectAttemptDisconnectOnRedirect(t *testing.T) {
   299  	run, err := testConnectHelper(connectMockRoundTripper{
   300  		redirect: endpointResult{response: makeResponse(200, disconnectBody)},
   301  		connect:  endpointResult{response: makeResponse(200, connectBody)},
   302  	})
   303  	if nil != run {
   304  		t.Error(run)
   305  	}
   306  	if !IsDisconnect(err) {
   307  		t.Fatal(err)
   308  	}
   309  }
   310  
   311  func TestConnectAttemptDisconnectOnConnect(t *testing.T) {
   312  	run, err := testConnectHelper(connectMockRoundTripper{
   313  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   314  		connect:  endpointResult{response: makeResponse(200, disconnectBody)},
   315  	})
   316  	if nil != run {
   317  		t.Error(run)
   318  	}
   319  	if !IsDisconnect(err) {
   320  		t.Fatal(err)
   321  	}
   322  }
   323  
   324  func TestConnectAttemptBadSecurityPolicies(t *testing.T) {
   325  	run, err := testConnectHelper(connectMockRoundTripper{
   326  		redirect: endpointResult{response: makeResponse(200, unknownRequiredPolicyBody)},
   327  		connect:  endpointResult{response: makeResponse(200, connectBody)},
   328  	})
   329  	if nil != run {
   330  		t.Error(run)
   331  	}
   332  	if !IsDisconnect(err) {
   333  		t.Fatal(err)
   334  	}
   335  }
   336  
   337  func TestConnectAttemptLicenseExceptionOnRedirect(t *testing.T) {
   338  	run, err := testConnectHelper(connectMockRoundTripper{
   339  		redirect: endpointResult{response: makeResponse(200, licenseBody)},
   340  		connect:  endpointResult{response: makeResponse(200, connectBody)},
   341  	})
   342  	if nil != run {
   343  		t.Error(run)
   344  	}
   345  	if !IsLicenseException(err) {
   346  		t.Fatal(err)
   347  	}
   348  }
   349  
   350  func TestConnectAttemptLicenseExceptionOnConnect(t *testing.T) {
   351  	run, err := testConnectHelper(connectMockRoundTripper{
   352  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   353  		connect:  endpointResult{response: makeResponse(200, licenseBody)},
   354  	})
   355  	if nil != run {
   356  		t.Error(run)
   357  	}
   358  	if !IsLicenseException(err) {
   359  		t.Fatal(err)
   360  	}
   361  }
   362  
   363  func TestConnectAttemptInvalidJSON(t *testing.T) {
   364  	run, err := testConnectHelper(connectMockRoundTripper{
   365  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   366  		connect:  endpointResult{response: makeResponse(200, malformedBody)},
   367  	})
   368  	if nil != run {
   369  		t.Error(run)
   370  	}
   371  	if nil == err {
   372  		t.Fatal("missing error")
   373  	}
   374  }
   375  
   376  func TestConnectAttemptCollectorNotString(t *testing.T) {
   377  	run, err := testConnectHelper(connectMockRoundTripper{
   378  		redirect: endpointResult{response: makeResponse(200, `{"return_value":123}`)},
   379  		connect:  endpointResult{response: makeResponse(200, connectBody)},
   380  	})
   381  	if nil != run {
   382  		t.Error(run)
   383  	}
   384  	if nil == err {
   385  		t.Fatal("missing error")
   386  	}
   387  }
   388  
   389  func TestConnectAttempt401(t *testing.T) {
   390  	run, err := testConnectHelper(connectMockRoundTripper{
   391  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   392  		connect:  endpointResult{response: makeResponse(401, connectBody)},
   393  	})
   394  	if nil != run {
   395  		t.Error(run)
   396  	}
   397  	if err != ErrUnauthorized {
   398  		t.Fatal(err)
   399  	}
   400  }
   401  
   402  func TestConnectAttempt413(t *testing.T) {
   403  	run, err := testConnectHelper(connectMockRoundTripper{
   404  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   405  		connect:  endpointResult{response: makeResponse(413, connectBody)},
   406  	})
   407  	if nil != run {
   408  		t.Error(run)
   409  	}
   410  	if err != ErrPayloadTooLarge {
   411  		t.Fatal(err)
   412  	}
   413  }
   414  
   415  func TestConnectAttempt415(t *testing.T) {
   416  	run, err := testConnectHelper(connectMockRoundTripper{
   417  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   418  		connect:  endpointResult{response: makeResponse(415, connectBody)},
   419  	})
   420  	if nil != run {
   421  		t.Error(run)
   422  	}
   423  	if err != ErrUnsupportedMedia {
   424  		t.Fatal(err)
   425  	}
   426  }
   427  
   428  func TestConnectAttemptUnexpectedCode(t *testing.T) {
   429  	run, err := testConnectHelper(connectMockRoundTripper{
   430  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   431  		connect:  endpointResult{response: makeResponse(404, connectBody)},
   432  	})
   433  	if nil != run {
   434  		t.Error(run)
   435  	}
   436  	if _, ok := err.(unexpectedStatusCodeErr); !ok {
   437  		t.Fatal(err)
   438  	}
   439  }
   440  
   441  func TestConnectAttemptUnexpectedError(t *testing.T) {
   442  	run, err := testConnectHelper(connectMockRoundTripper{
   443  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   444  		connect:  endpointResult{err: errors.New("unexpected error")},
   445  	})
   446  	if nil != run {
   447  		t.Error(run)
   448  	}
   449  	if nil == err {
   450  		t.Fatal("missing error")
   451  	}
   452  }
   453  
   454  func TestConnectAttemptMissingRunID(t *testing.T) {
   455  	run, err := testConnectHelper(connectMockRoundTripper{
   456  		redirect: endpointResult{response: makeResponse(200, redirectBody)},
   457  		connect:  endpointResult{response: makeResponse(200, `{"return_value":{}}`)},
   458  	})
   459  	if nil != run {
   460  		t.Error(run)
   461  	}
   462  	if nil == err {
   463  		t.Fatal("missing error")
   464  	}
   465  }
   466  
   467  func TestCalculatePreconnectHost(t *testing.T) {
   468  	// non-region license
   469  	host := calculatePreconnectHost("0123456789012345678901234567890123456789", "")
   470  	if host != preconnectHostDefault {
   471  		t.Error(host)
   472  	}
   473  	// override present
   474  	override := "other-collector.newrelic.com"
   475  	host = calculatePreconnectHost("0123456789012345678901234567890123456789", override)
   476  	if host != override {
   477  		t.Error(host)
   478  	}
   479  	// four letter region
   480  	host = calculatePreconnectHost("eu01xx6789012345678901234567890123456789", "")
   481  	if host != "collector.eu01.nr-data.net" {
   482  		t.Error(host)
   483  	}
   484  	// five letter region
   485  	host = calculatePreconnectHost("gov01x6789012345678901234567890123456789", "")
   486  	if host != "collector.gov01.nr-data.net" {
   487  		t.Error(host)
   488  	}
   489  	// six letter region
   490  	host = calculatePreconnectHost("foo001x6789012345678901234567890123456789", "")
   491  	if host != "collector.foo001.nr-data.net" {
   492  		t.Error(host)
   493  	}
   494  }
   495  
   496  func TestPreconnectHostCrossAgent(t *testing.T) {
   497  	var testcases []struct {
   498  		Name               string `json:"name"`
   499  		ConfigFileKey      string `json:"config_file_key"`
   500  		EnvKey             string `json:"env_key"`
   501  		ConfigOverrideHost string `json:"config_override_host"`
   502  		EnvOverrideHost    string `json:"env_override_host"`
   503  		ExpectHostname     string `json:"hostname"`
   504  	}
   505  	err := crossagent.ReadJSON("collector_hostname.json", &testcases)
   506  	if err != nil {
   507  		t.Fatal(err)
   508  	}
   509  
   510  	for _, tc := range testcases {
   511  		// mimic file/environment precendence of other agents
   512  		configKey := tc.ConfigFileKey
   513  		if "" != tc.EnvKey {
   514  			configKey = tc.EnvKey
   515  		}
   516  		overrideHost := tc.ConfigOverrideHost
   517  		if "" != tc.EnvOverrideHost {
   518  			overrideHost = tc.EnvOverrideHost
   519  		}
   520  
   521  		host := calculatePreconnectHost(configKey, overrideHost)
   522  		if host != tc.ExpectHostname {
   523  			t.Errorf(`test="%s" got="%s" expected="%s"`, tc.Name, host, tc.ExpectHostname)
   524  		}
   525  	}
   526  }