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