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 }