github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/synthetics_test.go (about) 1 package internal 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "testing" 8 9 "github.com/lulzWill/go-agent/internal/cat" 10 "github.com/lulzWill/go-agent/internal/crossagent" 11 ) 12 13 type harvestedTxnTrace struct { 14 startTimeMs float64 15 durationToResponse float64 16 transactionName string 17 requestURL string 18 traceDetails struct { 19 attributes struct { 20 agentAttributes eventAttributes 21 userAttributes eventAttributes 22 intrinsics eventAttributes 23 } 24 } 25 catGUID string 26 forcePersistFlag bool 27 xraySessionID string 28 syntheticsResourceID string 29 } 30 31 func traceAttributesToEventAttributes(attrs map[string]interface{}) (eventAttributes, error) { 32 ea := make(eventAttributes) 33 34 for k, v := range attrs { 35 s, ok := v.(string) 36 if !ok { 37 return nil, fmt.Errorf("%s is not a string: type is %t; value %v", k, v, v) 38 } 39 ea[k] = s 40 } 41 42 return ea, nil 43 } 44 45 func (h *harvestedTxnTrace) UnmarshalJSON(data []byte) error { 46 var arr []interface{} 47 48 if err := json.Unmarshal(data, &arr); err != nil { 49 return err 50 } 51 52 if len(arr) != 10 { 53 return fmt.Errorf("unexpected number of transaction trace items: %d", len(arr)) 54 } 55 56 h.startTimeMs = arr[0].(float64) 57 h.durationToResponse = arr[1].(float64) 58 h.transactionName = arr[2].(string) 59 h.requestURL = arr[3].(string) 60 // Item 4 -- the trace -- will be dealt with shortly. 61 h.catGUID = arr[5].(string) 62 // Item 6 intentionally ignored. 63 h.forcePersistFlag = arr[7].(bool) 64 if arr[8] != nil { 65 h.xraySessionID = arr[8].(string) 66 } 67 h.syntheticsResourceID = arr[9].(string) 68 69 traceDetails := arr[4].([]interface{}) 70 attributes := traceDetails[4].(map[string]interface{}) 71 var err error 72 73 h.traceDetails.attributes.agentAttributes, err = traceAttributesToEventAttributes(attributes["agentAttributes"].(map[string]interface{})) 74 if err != nil { 75 return err 76 } 77 78 h.traceDetails.attributes.userAttributes, err = traceAttributesToEventAttributes(attributes["userAttributes"].(map[string]interface{})) 79 if err != nil { 80 return err 81 } 82 83 h.traceDetails.attributes.intrinsics, err = traceAttributesToEventAttributes(attributes["intrinsics"].(map[string]interface{})) 84 if err != nil { 85 return err 86 } 87 88 return nil 89 } 90 91 func harvestTxnDataTrace(t *TxnData) (*harvestedTxnTrace, error) { 92 // Since transaction trace JSON is built using string manipulation, we have 93 // to do an awkward marshal/unmarshal shuffle to be able to verify the 94 // intrinsics. 95 ht := HarvestTrace{ 96 TxnEvent: t.TxnEvent, 97 Trace: t.TxnTrace, 98 } 99 js, err := ht.MarshalJSON() 100 if err != nil { 101 return nil, err 102 } 103 104 trace := &harvestedTxnTrace{} 105 if err := json.Unmarshal(js, trace); err != nil { 106 return nil, err 107 } 108 109 return trace, nil 110 } 111 112 func TestSynthetics(t *testing.T) { 113 var testcases []struct { 114 Name string `json:"name"` 115 Settings struct { 116 AgentEncodingKey string `json:"agentEncodingKey"` 117 SyntheticsEncodingKey string `json:"syntheticsEncodingKey"` 118 TransactionGUID string `json:"transactionGuid"` 119 TrustedAccountIDs []int `json:"trustedAccountIds"` 120 } `json:"settings"` 121 InputHeaderPayload json.RawMessage `json:"inputHeaderPayload"` 122 InputObfuscatedHeader map[string]string `json:"inputObfuscatedHeader"` 123 OutputTransactionTrace struct { 124 Header struct { 125 Field9 string `json:"field_9"` 126 } `json:"header"` 127 ExpectedIntrinsics map[string]string `json:"expectedIntrinsics"` 128 NonExpectedIntrinsics []string `json:"nonExpectedIntrinsics"` 129 } `json:"outputTransactionTrace"` 130 OutputTransactionEvent struct { 131 ExpectedAttributes map[string]string `json:"expectedAttributes"` 132 NonExpectedAttributes []string `json:"nonExpectedAttributes"` 133 } `json:"outputTransactionEvent"` 134 OutputExternalRequestHeader struct { 135 ExpectedHeader map[string]string `json:"expectedHeader"` 136 NonExpectedHeader []string `json:"nonExpectedHeader"` 137 } `json:"outputExternalRequestHeader"` 138 } 139 140 err := crossagent.ReadJSON("synthetics/synthetics.json", &testcases) 141 if err != nil { 142 t.Fatal(err) 143 } 144 145 for _, tc := range testcases { 146 // Fake enough transaction data to run the test. 147 tr := &TxnData{ 148 Name: "txn", 149 } 150 151 tr.CrossProcess.Init(false, &ConnectReply{ 152 CrossProcessID: "1#1", 153 TrustedAccounts: make(map[int]struct{}), 154 EncodingKey: tc.Settings.AgentEncodingKey, 155 }, CrossProcessMetadata{}) 156 157 // Set up the trusted accounts. 158 for _, account := range tc.Settings.TrustedAccountIDs { 159 tr.CrossProcess.TrustedAccounts[account] = struct{}{} 160 } 161 162 // Set up the GUID. 163 if tc.Settings.TransactionGUID != "" { 164 tr.CrossProcess.GUID = tc.Settings.TransactionGUID 165 } 166 167 // Parse the input header, ignoring any errors. 168 inputHeaders := make(http.Header) 169 for k, v := range tc.InputObfuscatedHeader { 170 inputHeaders.Add(k, v) 171 } 172 173 tr.CrossProcess.handleInboundRequestEncodedSynthetics(inputHeaders.Get(cat.NewRelicSyntheticsName)) 174 175 // Get the headers for an external request. 176 metadata, err := tr.CrossProcess.CreateCrossProcessMetadata("txn", "app") 177 if err != nil { 178 t.Fatalf("%s: error creating outbound request headers: %v", tc.Name, err) 179 } 180 181 // Verify that the header either exists or doesn't exist, depending on the 182 // test case. 183 headers := MetadataToHTTPHeader(metadata) 184 for key, value := range tc.OutputExternalRequestHeader.ExpectedHeader { 185 obfuscated := headers.Get(key) 186 if obfuscated == "" { 187 t.Errorf("%s: expected output header %s not found", tc.Name, key) 188 } else if value != obfuscated { 189 t.Errorf("%s: expected output header %s mismatch: expected=%s; got=%s", tc.Name, key, value, obfuscated) 190 } 191 } 192 193 for _, key := range tc.OutputExternalRequestHeader.NonExpectedHeader { 194 if value := headers.Get(key); value != "" { 195 t.Errorf("%s: output header %s expected to be missing; got %s", tc.Name, key, value) 196 } 197 } 198 199 // Harvest the trace. 200 trace, err := harvestTxnDataTrace(tr) 201 if err != nil { 202 t.Errorf("%s: error harvesting trace data: %v", tc.Name, err) 203 } 204 205 // Check the synthetics resource ID. 206 if trace.syntheticsResourceID != tc.OutputTransactionTrace.Header.Field9 { 207 t.Errorf("%s: unexpected field 9: expected=%s; got=%s", tc.Name, tc.OutputTransactionTrace.Header.Field9, trace.syntheticsResourceID) 208 } 209 210 // Check for expected intrinsics. 211 for key, value := range tc.OutputTransactionTrace.ExpectedIntrinsics { 212 // First, check if the key exists at all. 213 if !trace.traceDetails.attributes.intrinsics.has(key) { 214 t.Fatalf("%s: missing intrinsic %s", tc.Name, key) 215 } 216 217 // Everything we're looking for is a string, so we can be a little lazy 218 // here. 219 if err := trace.traceDetails.attributes.intrinsics.isString(key, value); err != nil { 220 t.Errorf("%s: %v", tc.Name, err) 221 } 222 } 223 224 // Now we verify that the unexpected intrinsics didn't miraculously appear. 225 for _, key := range tc.OutputTransactionTrace.NonExpectedIntrinsics { 226 if trace.traceDetails.attributes.intrinsics.has(key) { 227 t.Errorf("%s: expected intrinsic %s to be missing; instead, got value %v", tc.Name, key, trace.traceDetails.attributes.intrinsics[key]) 228 } 229 } 230 231 // Harvest the event. 232 event, err := harvestTxnDataEvent(tr) 233 if err != nil { 234 t.Errorf("%s: error harvesting event data: %v", tc.Name, err) 235 } 236 237 // Now we have the event, let's look for the expected intrinsics. 238 for key, value := range tc.OutputTransactionEvent.ExpectedAttributes { 239 // First, check if the key exists at all. 240 if !event.intrinsics.has(key) { 241 t.Fatalf("%s: missing intrinsic %s", tc.Name, key) 242 } 243 244 // Everything we're looking for is a string, so we can be a little lazy 245 // here. 246 if err := event.intrinsics.isString(key, value); err != nil { 247 t.Errorf("%s: %v", tc.Name, err) 248 } 249 } 250 251 // Now we verify that the unexpected intrinsics didn't miraculously appear. 252 for _, key := range tc.OutputTransactionEvent.NonExpectedAttributes { 253 if event.intrinsics.has(key) { 254 t.Errorf("%s: expected intrinsic %s to be missing; instead, got value %v", tc.Name, key, event.intrinsics[key]) 255 } 256 } 257 } 258 }