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 }