github.com/newrelic/go-agent@v3.26.0+incompatible/internal/cat_test.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package internal 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "testing" 10 11 "github.com/newrelic/go-agent/internal/crossagent" 12 ) 13 14 type eventAttributes map[string]interface{} 15 16 func (e eventAttributes) has(key string) bool { 17 _, ok := e[key] 18 return ok 19 } 20 21 func (e eventAttributes) isString(key string, expected string) error { 22 actual, ok := e[key].(string) 23 if !ok { 24 return fmt.Errorf("key %s is not a string; got type %t with value %v", key, e[key], e[key]) 25 } 26 27 if actual != expected { 28 return fmt.Errorf("key %s has unexpected value: expected=%s; got=%s", key, expected, actual) 29 } 30 31 return nil 32 } 33 34 type harvestedTxnEvent struct { 35 intrinsics eventAttributes 36 userAttributes eventAttributes 37 agentAttributes eventAttributes 38 } 39 40 func (h *harvestedTxnEvent) UnmarshalJSON(data []byte) error { 41 var arr []eventAttributes 42 43 if err := json.Unmarshal(data, &arr); err != nil { 44 return err 45 } 46 47 if len(arr) != 3 { 48 return fmt.Errorf("unexpected number of transaction event items: %d", len(arr)) 49 } 50 51 h.intrinsics = arr[0] 52 h.userAttributes = arr[1] 53 h.agentAttributes = arr[2] 54 55 return nil 56 } 57 58 func harvestTxnDataEvent(t *TxnData) (*harvestedTxnEvent, error) { 59 // Since transaction event JSON is built using string manipulation, we have 60 // to do an awkward marshal/unmarshal shuffle to be able to verify the 61 // intrinsics. 62 js, err := json.Marshal(&t.TxnEvent) 63 if err != nil { 64 return nil, err 65 } 66 67 event := &harvestedTxnEvent{} 68 if err := json.Unmarshal(js, event); err != nil { 69 return nil, err 70 } 71 72 return event, nil 73 } 74 75 // This function implements as close as we can get to the round trip tests in 76 // the cross agent tests. 77 func TestCatMap(t *testing.T) { 78 var testcases []struct { 79 Name string `json:"name"` 80 AppName string `json:"appName"` 81 TransactionName string `json:"transactionName"` 82 TransactionGUID string `json:"transactionGuid"` 83 InboundPayload []interface{} `json:"inboundPayload"` 84 ExpectedIntrinsicFields map[string]string `json:"expectedIntrinsicFields"` 85 NonExpectedIntrinsicFields []string `json:"nonExpectedIntrinsicFields"` 86 OutboundRequests []struct { 87 OutboundTxnName string `json:"outboundTxnName"` 88 ExpectedOutboundPayload json.RawMessage `json:"expectedOutboundPayload"` 89 } `json:"outboundRequests"` 90 } 91 92 err := crossagent.ReadJSON("cat/cat_map.json", &testcases) 93 if err != nil { 94 t.Fatal(err) 95 } 96 97 for _, tc := range testcases { 98 // Fake enough transaction data to run the test. 99 tr := &TxnData{ 100 Name: tc.TransactionName, 101 } 102 103 tr.CrossProcess.Init(true, false, &ConnectReply{ 104 CrossProcessID: "1#1", 105 EncodingKey: "foo", 106 TrustedAccounts: map[int]struct{}{1: {}}, 107 }) 108 109 // Marshal the inbound payload into JSON for easier testing. 110 txnData, err := json.Marshal(tc.InboundPayload) 111 if err != nil { 112 t.Errorf("%s: error marshalling inbound payload: %v", tc.Name, err) 113 } 114 115 // Set up the GUID. 116 if tc.TransactionGUID != "" { 117 tr.CrossProcess.GUID = tc.TransactionGUID 118 } 119 120 // Swallow errors, since some of these tests are testing the behaviour when 121 // erroneous headers are provided. 122 tr.CrossProcess.handleInboundRequestTxnData(txnData) 123 124 // Simulate outbound requests. 125 for _, req := range tc.OutboundRequests { 126 metadata, err := tr.CrossProcess.CreateCrossProcessMetadata(req.OutboundTxnName, tc.AppName) 127 if err != nil { 128 t.Errorf("%s: error creating outbound request headers: %v", tc.Name, err) 129 } 130 131 // Grab and deobfuscate the txndata that would have been sent to the 132 // external service. 133 txnData, err := Deobfuscate(metadata.TxnData, tr.CrossProcess.EncodingKey) 134 if err != nil { 135 t.Errorf("%s: error deobfuscating outbound request header: %v", tc.Name, err) 136 } 137 138 // Check the JSON against the expected value. 139 compacted := CompactJSONString(string(txnData)) 140 expected := CompactJSONString(string(req.ExpectedOutboundPayload)) 141 if compacted != expected { 142 t.Errorf("%s: outbound metadata does not match expected value: expected=%s; got=%s", tc.Name, expected, compacted) 143 } 144 } 145 146 // Finalise the transaction, ignoring errors. 147 tr.CrossProcess.Finalise(tc.TransactionName, tc.AppName) 148 149 // Harvest the event. 150 event, err := harvestTxnDataEvent(tr) 151 if err != nil { 152 t.Errorf("%s: error harvesting event data: %v", tc.Name, err) 153 } 154 155 // Now we have the event, let's look for the expected intrinsics. 156 for key, value := range tc.ExpectedIntrinsicFields { 157 // First, check if the key exists at all. 158 if !event.intrinsics.has(key) { 159 t.Fatalf("%s: missing intrinsic %s", tc.Name, key) 160 } 161 162 // Everything we're looking for is a string, so we can be a little lazy 163 // here. 164 if err := event.intrinsics.isString(key, value); err != nil { 165 t.Errorf("%s: %v", tc.Name, err) 166 } 167 } 168 169 // Finally, we verify that the unexpected intrinsics didn't miraculously 170 // appear. 171 for _, key := range tc.NonExpectedIntrinsicFields { 172 if event.intrinsics.has(key) { 173 t.Errorf("%s: expected intrinsic %s to be missing; instead, got value %v", tc.Name, key, event.intrinsics[key]) 174 } 175 } 176 } 177 }