github.com/newrelic/go-agent@v3.26.0+incompatible/internal_cross_process_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package newrelic
     5  
     6  import (
     7  	"encoding/json"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"testing"
    11  
    12  	"errors"
    13  
    14  	"github.com/newrelic/go-agent/internal"
    15  	"github.com/newrelic/go-agent/internal/cat"
    16  )
    17  
    18  var (
    19  	crossProcessReplyFn = func(reply *internal.ConnectReply) {
    20  		reply.EncodingKey = "encoding_key"
    21  		reply.CrossProcessID = "12345#67890"
    22  		reply.TrustedAccounts = map[int]struct{}{
    23  			12345: {},
    24  		}
    25  	}
    26  	catIntrinsics = map[string]interface{}{
    27  		"name":                        "WebTransaction/Go/hello",
    28  		"nr.pathHash":                 "fa013f2a",
    29  		"nr.guid":                     internal.MatchAnything,
    30  		"nr.referringTransactionGuid": internal.MatchAnything,
    31  		"nr.referringPathHash":        "41c04f7d",
    32  		"nr.apdexPerfZone":            "S",
    33  		"client_cross_process_id":     "12345#67890",
    34  		"nr.tripId":                   internal.MatchAnything,
    35  	}
    36  )
    37  
    38  func inboundCrossProcessRequestFactory() *http.Request {
    39  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true }
    40  	app := testApp(crossProcessReplyFn, cfgFn, nil)
    41  	clientTxn := app.StartTransaction("client", nil, nil)
    42  	req, err := http.NewRequest("GET", "newrelic.com", nil)
    43  	StartExternalSegment(clientTxn, req)
    44  	if "" == req.Header.Get(cat.NewRelicIDName) {
    45  		panic("missing cat header NewRelicIDName: " + req.Header.Get(cat.NewRelicIDName))
    46  	}
    47  	if "" == req.Header.Get(cat.NewRelicTxnName) {
    48  		panic("missing cat header NewRelicTxnName: " + req.Header.Get(cat.NewRelicTxnName))
    49  	}
    50  	if nil != err {
    51  		panic(err)
    52  	}
    53  	return req
    54  }
    55  
    56  func outboundCrossProcessResponse() http.Header {
    57  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true }
    58  	app := testApp(crossProcessReplyFn, cfgFn, nil)
    59  	rw := httptest.NewRecorder()
    60  	txn := app.StartTransaction("txn", rw, inboundCrossProcessRequestFactory())
    61  	txn.WriteHeader(200)
    62  	return rw.HeaderMap
    63  }
    64  
    65  func TestCrossProcessWriteHeaderSuccess(t *testing.T) {
    66  	// Test that the CAT response header is present when the consumer uses
    67  	// txn.WriteHeader.
    68  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true }
    69  	app := testApp(crossProcessReplyFn, cfgFn, t)
    70  	w := httptest.NewRecorder()
    71  	txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory())
    72  	txn.WriteHeader(200)
    73  	txn.End()
    74  
    75  	if "" == w.Header().Get(cat.NewRelicAppDataName) {
    76  		t.Error(w.Header().Get(cat.NewRelicAppDataName))
    77  	}
    78  
    79  	app.ExpectMetrics(t, webMetrics)
    80  	app.ExpectTxnEvents(t, []internal.WantEvent{{
    81  		Intrinsics: catIntrinsics,
    82  		AgentAttributes: map[string]interface{}{
    83  			"request.method":   "GET",
    84  			"httpResponseCode": 200,
    85  			"request.uri":      "newrelic.com",
    86  		},
    87  		UserAttributes: map[string]interface{}{},
    88  	}})
    89  }
    90  
    91  func TestCrossProcessWriteSuccess(t *testing.T) {
    92  	// Test that the CAT response header is present when the consumer uses
    93  	// txn.Write.
    94  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true }
    95  	app := testApp(crossProcessReplyFn, cfgFn, t)
    96  	w := httptest.NewRecorder()
    97  	txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory())
    98  	txn.Write([]byte("response text"))
    99  	txn.End()
   100  
   101  	if "" == w.Header().Get(cat.NewRelicAppDataName) {
   102  		t.Error(w.Header().Get(cat.NewRelicAppDataName))
   103  	}
   104  
   105  	app.ExpectMetrics(t, webMetrics)
   106  	app.ExpectTxnEvents(t, []internal.WantEvent{{
   107  		Intrinsics: catIntrinsics,
   108  		// Do not test attributes here:  In Go 1.5
   109  		// response.headers.contentType will be not be present.
   110  		AgentAttributes: nil,
   111  		UserAttributes:  map[string]interface{}{},
   112  	}})
   113  }
   114  
   115  func TestCATRoundTripper(t *testing.T) {
   116  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true }
   117  	app := testApp(nil, cfgFn, t)
   118  	txn := app.StartTransaction("hello", nil, nil)
   119  	url := "http://example.com/"
   120  	client := &http.Client{}
   121  	inner := roundTripperFunc(func(r *http.Request) (*http.Response, error) {
   122  		// TODO test that request headers have been set here.
   123  		if r.URL.String() != url {
   124  			t.Error(r.URL.String())
   125  		}
   126  		return nil, errors.New("hello")
   127  	})
   128  	client.Transport = NewRoundTripper(txn, inner)
   129  	resp, err := client.Get(url)
   130  	if resp != nil || err == nil {
   131  		t.Error(resp, err.Error())
   132  	}
   133  	txn.NoticeError(myError{})
   134  	txn.End()
   135  	scope := "OtherTransaction/Go/hello"
   136  	app.ExpectMetrics(t, append([]internal.WantMetric{
   137  		{Name: "External/all", Scope: "", Forced: true, Data: nil},
   138  		{Name: "External/allOther", Scope: "", Forced: true, Data: nil},
   139  		{Name: "External/example.com/all", Scope: "", Forced: false, Data: nil},
   140  		{Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil},
   141  	}, backgroundErrorMetrics...))
   142  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   143  		Intrinsics: map[string]interface{}{
   144  			"error.class":       "newrelic.myError",
   145  			"error.message":     "my msg",
   146  			"transactionName":   "OtherTransaction/Go/hello",
   147  			"externalCallCount": 1,
   148  			"externalDuration":  internal.MatchAnything,
   149  		},
   150  	}})
   151  	app.ExpectTxnEvents(t, []internal.WantEvent{{
   152  		Intrinsics: map[string]interface{}{
   153  			"name":              "OtherTransaction/Go/hello",
   154  			"externalCallCount": 1,
   155  			"externalDuration":  internal.MatchAnything,
   156  			"nr.guid":           internal.MatchAnything,
   157  			"nr.tripId":         internal.MatchAnything,
   158  			"nr.pathHash":       internal.MatchAnything,
   159  		},
   160  	}})
   161  }
   162  
   163  func TestCrossProcessLocallyDisabled(t *testing.T) {
   164  	// Test that the CAT can be disabled by local configuration.
   165  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = false }
   166  	app := testApp(crossProcessReplyFn, cfgFn, t)
   167  	w := httptest.NewRecorder()
   168  	txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory())
   169  	txn.Write([]byte("response text"))
   170  	txn.End()
   171  
   172  	if "" != w.Header().Get(cat.NewRelicAppDataName) {
   173  		t.Error(w.Header().Get(cat.NewRelicAppDataName))
   174  	}
   175  
   176  	app.ExpectMetrics(t, webMetrics)
   177  	app.ExpectTxnEvents(t, []internal.WantEvent{{
   178  		Intrinsics: map[string]interface{}{
   179  			"name":             "WebTransaction/Go/hello",
   180  			"nr.apdexPerfZone": "S",
   181  		},
   182  		// Do not test attributes here:  In Go 1.5
   183  		// response.headers.contentType will be not be present.
   184  		AgentAttributes: nil,
   185  		UserAttributes:  map[string]interface{}{},
   186  	}})
   187  }
   188  
   189  func TestCrossProcessDisabledByServerSideConfig(t *testing.T) {
   190  	// Test that the CAT can be disabled by server-side-config.
   191  	cfgFn := func(cfg *Config) {}
   192  	replyfn := func(reply *internal.ConnectReply) {
   193  		crossProcessReplyFn(reply)
   194  		json.Unmarshal([]byte(`{"agent_config":{"cross_application_tracer.enabled":false}}`), reply)
   195  	}
   196  	app := testApp(replyfn, cfgFn, t)
   197  	w := httptest.NewRecorder()
   198  	txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory())
   199  	txn.Write([]byte("response text"))
   200  	txn.End()
   201  
   202  	if "" != w.Header().Get(cat.NewRelicAppDataName) {
   203  		t.Error(w.Header().Get(cat.NewRelicAppDataName))
   204  	}
   205  
   206  	app.ExpectMetrics(t, webMetrics)
   207  	app.ExpectTxnEvents(t, []internal.WantEvent{{
   208  		Intrinsics: map[string]interface{}{
   209  			"name":             "WebTransaction/Go/hello",
   210  			"nr.apdexPerfZone": "S",
   211  		},
   212  		// Do not test attributes here:  In Go 1.5
   213  		// response.headers.contentType will be not be present.
   214  		AgentAttributes: nil,
   215  		UserAttributes:  map[string]interface{}{},
   216  	}})
   217  }
   218  
   219  func TestCrossProcessEnabledByServerSideConfig(t *testing.T) {
   220  	// Test that the CAT can be enabled by server-side-config.
   221  	cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = false }
   222  	replyfn := func(reply *internal.ConnectReply) {
   223  		crossProcessReplyFn(reply)
   224  		json.Unmarshal([]byte(`{"agent_config":{"cross_application_tracer.enabled":true}}`), reply)
   225  	}
   226  	app := testApp(replyfn, cfgFn, t)
   227  	w := httptest.NewRecorder()
   228  	txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory())
   229  	txn.Write([]byte("response text"))
   230  	txn.End()
   231  
   232  	if "" == w.Header().Get(cat.NewRelicAppDataName) {
   233  		t.Error(w.Header().Get(cat.NewRelicAppDataName))
   234  	}
   235  
   236  	app.ExpectMetrics(t, webMetrics)
   237  	app.ExpectTxnEvents(t, []internal.WantEvent{{
   238  		Intrinsics: catIntrinsics,
   239  		// Do not test attributes here:  In Go 1.5
   240  		// response.headers.contentType will be not be present.
   241  		AgentAttributes: nil,
   242  		UserAttributes:  map[string]interface{}{},
   243  	}})
   244  }