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  }