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  }