github.com/newrelic/go-agent@v3.26.0+incompatible/internal/txn_cross_process_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  	"fmt"
     8  	"net/http"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/newrelic/go-agent/internal/cat"
    14  )
    15  
    16  var (
    17  	replyAccountOne = &ConnectReply{
    18  		CrossProcessID:  "1#1",
    19  		EncodingKey:     "foo",
    20  		TrustedAccounts: map[int]struct{}{1: {}},
    21  	}
    22  
    23  	replyAccountTwo = &ConnectReply{
    24  		CrossProcessID:  "2#2",
    25  		EncodingKey:     "foo",
    26  		TrustedAccounts: map[int]struct{}{2: {}},
    27  	}
    28  
    29  	requestEmpty            = newRequest().Request
    30  	requestCATOne           = newRequest().withCAT(newTxnCrossProcessFromConnectReply(replyAccountOne), "txn", "app").Request
    31  	requestSyntheticsOne    = newRequest().withSynthetics(1, "foo").Request
    32  	requestCATSyntheticsOne = newRequest().withCAT(newTxnCrossProcessFromConnectReply(replyAccountOne), "txn", "app").withSynthetics(1, "foo").Request
    33  )
    34  
    35  func mustObfuscate(input, encodingKey string) string {
    36  	output, err := Obfuscate([]byte(input), []byte(encodingKey))
    37  	if err != nil {
    38  		panic(err)
    39  	}
    40  
    41  	return string(output)
    42  }
    43  
    44  func newTxnCrossProcessFromConnectReply(reply *ConnectReply) *TxnCrossProcess {
    45  	txp := &TxnCrossProcess{GUID: "abcdefgh"}
    46  	txp.Init(true, false, reply)
    47  
    48  	return txp
    49  }
    50  
    51  type request struct {
    52  	*http.Request
    53  }
    54  
    55  func newRequest() *request {
    56  	req, err := http.NewRequest("GET", "http://foo.bar", nil)
    57  	if err != nil {
    58  		panic(err)
    59  	}
    60  
    61  	return &request{Request: req}
    62  }
    63  
    64  func (req *request) withCAT(txp *TxnCrossProcess, txnName, appName string) *request {
    65  	metadata, err := txp.CreateCrossProcessMetadata(txnName, appName)
    66  	if err != nil {
    67  		panic(err)
    68  	}
    69  
    70  	for k, values := range MetadataToHTTPHeader(metadata) {
    71  		for _, v := range values {
    72  			req.Header.Add(k, v)
    73  		}
    74  	}
    75  
    76  	return req
    77  }
    78  
    79  func (req *request) withSynthetics(account int, encodingKey string) *request {
    80  	header := fmt.Sprintf(`[1,%d,"resource","job","monitor"]`, account)
    81  	obfuscated, err := Obfuscate([]byte(header), []byte(encodingKey))
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  
    86  	req.Header.Add(cat.NewRelicSyntheticsName, string(obfuscated))
    87  	return req
    88  }
    89  
    90  func TestTxnCrossProcessInit(t *testing.T) {
    91  	for _, tc := range []struct {
    92  		name          string
    93  		enabled       bool
    94  		reply         *ConnectReply
    95  		req           *http.Request
    96  		expected      *TxnCrossProcess
    97  		expectedError bool
    98  	}{
    99  		{
   100  			name:    "disabled",
   101  			enabled: false,
   102  			reply:   replyAccountOne,
   103  			req:     nil,
   104  			expected: &TxnCrossProcess{
   105  				CrossProcessID:  []byte("1#1"),
   106  				EncodingKey:     []byte("foo"),
   107  				Enabled:         false,
   108  				TrustedAccounts: map[int]struct{}{1: {}},
   109  			},
   110  			expectedError: false,
   111  		},
   112  		{
   113  			name:    "normal connect reply without a request",
   114  			enabled: true,
   115  			reply:   replyAccountOne,
   116  			req:     nil,
   117  			expected: &TxnCrossProcess{
   118  				CrossProcessID:  []byte("1#1"),
   119  				EncodingKey:     []byte("foo"),
   120  				Enabled:         true,
   121  				TrustedAccounts: map[int]struct{}{1: {}},
   122  			},
   123  			expectedError: false,
   124  		},
   125  		{
   126  			name:    "normal connect reply with a request without headers",
   127  			enabled: true,
   128  			reply:   replyAccountOne,
   129  			req:     requestEmpty,
   130  			expected: &TxnCrossProcess{
   131  				CrossProcessID:  []byte("1#1"),
   132  				EncodingKey:     []byte("foo"),
   133  				Enabled:         true,
   134  				TrustedAccounts: map[int]struct{}{1: {}},
   135  			},
   136  			expectedError: false,
   137  		},
   138  		{
   139  			name:    "normal connect reply with a request with untrusted headers",
   140  			enabled: true,
   141  			reply:   replyAccountTwo,
   142  			req:     requestCATOne,
   143  			expected: &TxnCrossProcess{
   144  				CrossProcessID:  []byte("2#2"),
   145  				EncodingKey:     []byte("foo"),
   146  				Enabled:         true,
   147  				TrustedAccounts: map[int]struct{}{2: {}},
   148  			},
   149  			expectedError: true,
   150  		},
   151  		{
   152  			name:    "normal connect reply with a request with trusted headers",
   153  			enabled: true,
   154  			reply:   replyAccountOne,
   155  			req:     requestCATOne,
   156  			expected: &TxnCrossProcess{
   157  				CrossProcessID:  []byte("1#1"),
   158  				EncodingKey:     []byte("foo"),
   159  				Enabled:         true,
   160  				TrustedAccounts: map[int]struct{}{1: {}},
   161  			},
   162  			expectedError: false,
   163  		},
   164  	} {
   165  		actual := &TxnCrossProcess{}
   166  
   167  		id := ""
   168  		txnData := ""
   169  		synthetics := ""
   170  		if tc.req != nil {
   171  			id = tc.req.Header.Get(cat.NewRelicIDName)
   172  			txnData = tc.req.Header.Get(cat.NewRelicTxnName)
   173  			synthetics = tc.req.Header.Get(cat.NewRelicSyntheticsName)
   174  		}
   175  
   176  		actual.Init(tc.enabled, false, tc.reply)
   177  		err := actual.handleInboundRequestHeaders(CrossProcessMetadata{id, txnData, synthetics})
   178  
   179  		if tc.expectedError == false && err != nil {
   180  			t.Errorf("%s: unexpected error returned from Init: %v", tc.name, err)
   181  		} else if tc.expectedError && err == nil {
   182  			t.Errorf("%s: no error returned from Init when one was expected", tc.name)
   183  		}
   184  
   185  		if !reflect.DeepEqual(actual.EncodingKey, tc.expected.EncodingKey) {
   186  			t.Errorf("%s: EncodingKey mismatch: expected=%v; got=%v", tc.name, tc.expected.EncodingKey, actual.EncodingKey)
   187  		}
   188  
   189  		if !reflect.DeepEqual(actual.CrossProcessID, tc.expected.CrossProcessID) {
   190  			t.Errorf("%s: CrossProcessID mismatch: expected=%v; got=%v", tc.name, tc.expected.CrossProcessID, actual.CrossProcessID)
   191  		}
   192  
   193  		if !reflect.DeepEqual(actual.TrustedAccounts, tc.expected.TrustedAccounts) {
   194  			t.Errorf("%s: TrustedAccounts mismatch: expected=%v; got=%v", tc.name, tc.expected.TrustedAccounts, actual.TrustedAccounts)
   195  		}
   196  
   197  		if actual.Enabled != tc.expected.Enabled {
   198  			t.Errorf("%s: Enabled mismatch: expected=%v; got=%v", tc.name, tc.expected.Enabled, actual.Enabled)
   199  		}
   200  	}
   201  }
   202  
   203  func TestTxnCrossProcessCreateCrossProcessMetadata(t *testing.T) {
   204  	for _, tc := range []struct {
   205  		name             string
   206  		enabled          bool
   207  		reply            *ConnectReply
   208  		req              *http.Request
   209  		txnName          string
   210  		appName          string
   211  		expectedError    bool
   212  		expectedMetadata CrossProcessMetadata
   213  	}{
   214  		{
   215  			name:             "disabled, no header",
   216  			enabled:          false,
   217  			reply:            replyAccountOne,
   218  			req:              nil,
   219  			txnName:          "txn",
   220  			appName:          "app",
   221  			expectedError:    false,
   222  			expectedMetadata: CrossProcessMetadata{},
   223  		},
   224  		{
   225  			name:             "disabled, header",
   226  			enabled:          false,
   227  			reply:            replyAccountOne,
   228  			req:              requestCATOne,
   229  			txnName:          "txn",
   230  			appName:          "app",
   231  			expectedError:    false,
   232  			expectedMetadata: CrossProcessMetadata{},
   233  		},
   234  		{
   235  			name:          "disabled, synthetics",
   236  			enabled:       false,
   237  			reply:         replyAccountOne,
   238  			req:           requestSyntheticsOne,
   239  			txnName:       "txn",
   240  			appName:       "app",
   241  			expectedError: false,
   242  			expectedMetadata: CrossProcessMetadata{
   243  				Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"),
   244  			},
   245  		},
   246  		{
   247  			name:          "disabled, header, synthetics",
   248  			enabled:       false,
   249  			reply:         replyAccountOne,
   250  			req:           requestCATSyntheticsOne,
   251  			txnName:       "txn",
   252  			appName:       "app",
   253  			expectedError: false,
   254  			expectedMetadata: CrossProcessMetadata{
   255  				Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"),
   256  			},
   257  		},
   258  		{
   259  			name:          "enabled, no header, no synthetics",
   260  			enabled:       true,
   261  			reply:         replyAccountOne,
   262  			req:           requestEmpty,
   263  			txnName:       "txn",
   264  			appName:       "app",
   265  			expectedError: false,
   266  			expectedMetadata: CrossProcessMetadata{
   267  				ID:      mustObfuscate(`1#1`, "foo"),
   268  				TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"),
   269  			},
   270  		},
   271  		{
   272  			name:          "enabled, no header, synthetics",
   273  			enabled:       true,
   274  			reply:         replyAccountOne,
   275  			req:           requestSyntheticsOne,
   276  			txnName:       "txn",
   277  			appName:       "app",
   278  			expectedError: false,
   279  			expectedMetadata: CrossProcessMetadata{
   280  				ID:         mustObfuscate(`1#1`, "foo"),
   281  				TxnData:    mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"),
   282  				Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"),
   283  			},
   284  		},
   285  		{
   286  			name:          "enabled, header, no synthetics",
   287  			enabled:       true,
   288  			reply:         replyAccountOne,
   289  			req:           requestCATOne,
   290  			txnName:       "txn",
   291  			appName:       "app",
   292  			expectedError: false,
   293  			expectedMetadata: CrossProcessMetadata{
   294  				ID:      mustObfuscate(`1#1`, "foo"),
   295  				TxnData: mustObfuscate(`["00000000",false,"abcdefgh","cbec2654"]`, "foo"),
   296  			},
   297  		},
   298  		{
   299  			name:          "enabled, header, synthetics",
   300  			enabled:       true,
   301  			reply:         replyAccountOne,
   302  			req:           requestCATSyntheticsOne,
   303  			txnName:       "txn",
   304  			appName:       "app",
   305  			expectedError: false,
   306  			expectedMetadata: CrossProcessMetadata{
   307  				ID:         mustObfuscate(`1#1`, "foo"),
   308  				TxnData:    mustObfuscate(`["00000000",false,"abcdefgh","cbec2654"]`, "foo"),
   309  				Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"),
   310  			},
   311  		},
   312  	} {
   313  		txp := &TxnCrossProcess{GUID: "00000000"}
   314  		txp.Init(tc.enabled, false, tc.reply)
   315  		if nil != tc.req {
   316  			txp.InboundHTTPRequest(tc.req.Header)
   317  		}
   318  		metadata, err := txp.CreateCrossProcessMetadata(tc.txnName, tc.appName)
   319  
   320  		if tc.expectedError == false && err != nil {
   321  			t.Errorf("%s: unexpected error returned from CreateCrossProcessMetadata: %v", tc.name, err)
   322  		} else if tc.expectedError && err == nil {
   323  			t.Errorf("%s: no error returned from CreateCrossProcessMetadata when one was expected", tc.name)
   324  		}
   325  
   326  		if !reflect.DeepEqual(tc.expectedMetadata, metadata) {
   327  			t.Errorf("%s: metadata mismatch: expected=%v; got=%v", tc.name, tc.expectedMetadata, metadata)
   328  		}
   329  
   330  		// Ensure that a path hash was generated if TxnData was created.
   331  		if metadata.TxnData != "" && txp.PathHash == "" {
   332  			t.Errorf("%s: no path hash generated", tc.name)
   333  		}
   334  	}
   335  }
   336  
   337  func TestTxnCrossProcessCreateCrossProcessMetadataError(t *testing.T) {
   338  	// Ensure errors bubble back up from deeper within our obfuscation code.
   339  	// It's likely impossible to get outboundTxnData() to fail, but we can get
   340  	// outboundID() to fail by having an empty encoding key.
   341  	txp := &TxnCrossProcess{Enabled: true}
   342  	metadata, err := txp.CreateCrossProcessMetadata("txn", "app")
   343  	if metadata.ID != "" || metadata.TxnData != "" || metadata.Synthetics != "" {
   344  		t.Errorf("one or more metadata fields were set unexpectedly; got %v", metadata)
   345  	}
   346  	if err == nil {
   347  		t.Errorf("did not get expected error with an empty encoding key")
   348  	}
   349  
   350  	// Test the above with Synthetics support to ensure that the Synthetics
   351  	// payload is still set.
   352  	txp = &TxnCrossProcess{
   353  		Enabled:          true,
   354  		Type:             txnCrossProcessSynthetics,
   355  		SyntheticsHeader: "foo",
   356  		// This won't be actually examined, but can't be nil for the IsSynthetics()
   357  		// check to pass.
   358  		Synthetics: &cat.SyntheticsHeader{},
   359  	}
   360  	metadata, err = txp.CreateCrossProcessMetadata("txn", "app")
   361  	if metadata.ID != "" || metadata.TxnData != "" {
   362  		t.Errorf("one or more metadata fields were set unexpectedly; got %v", metadata)
   363  	}
   364  	if metadata.Synthetics != "foo" {
   365  		t.Errorf("unexpected synthetics metadata: expected %s; got %s", "foo", metadata.Synthetics)
   366  	}
   367  	if err == nil {
   368  		t.Errorf("did not get expected error with an empty encoding key")
   369  	}
   370  }
   371  
   372  func TestTxnCrossProcessFinalise(t *testing.T) {
   373  	// No CAT.
   374  	txp := &TxnCrossProcess{}
   375  	txp.Init(true, false, replyAccountOne)
   376  	if err := txp.Finalise("txn", "app"); err != nil {
   377  		t.Errorf("unexpected error: %v", err)
   378  	}
   379  	if txp.PathHash != "" {
   380  		t.Errorf("unexpected path hash: %s", txp.PathHash)
   381  	}
   382  
   383  	// CAT, but no path hash.
   384  	txp = &TxnCrossProcess{}
   385  	txp.Init(true, false, replyAccountOne)
   386  	txp.InboundHTTPRequest(requestCATOne.Header)
   387  	if txp.PathHash != "" {
   388  		t.Errorf("unexpected path hash: %s", txp.PathHash)
   389  	}
   390  	if err := txp.Finalise("txn", "app"); err != nil {
   391  		t.Errorf("unexpected error: %v", err)
   392  	}
   393  	if txp.PathHash == "" {
   394  		t.Error("unexpected lack of path hash")
   395  	}
   396  
   397  	// CAT, with a path hash.
   398  	txp = &TxnCrossProcess{}
   399  	txp.Init(true, false, replyAccountOne)
   400  	txp.InboundHTTPRequest(requestCATOne.Header)
   401  	txp.CreateCrossProcessMetadata("txn", "app")
   402  	if txp.PathHash == "" {
   403  		t.Error("unexpected lack of path hash")
   404  	}
   405  	if err := txp.Finalise("txn", "app"); err != nil {
   406  		t.Errorf("unexpected error: %v", err)
   407  	}
   408  	if txp.PathHash == "" {
   409  		t.Error("unexpected lack of path hash")
   410  	}
   411  }
   412  
   413  func TestTxnCrossProcessIsInbound(t *testing.T) {
   414  	for _, tc := range []struct {
   415  		txpType  uint8
   416  		expected bool
   417  	}{
   418  		{0, false},
   419  		{txnCrossProcessSynthetics, false},
   420  		{txnCrossProcessInbound, true},
   421  		{txnCrossProcessOutbound, false},
   422  		{txnCrossProcessSynthetics | txnCrossProcessInbound, true},
   423  		{txnCrossProcessSynthetics | txnCrossProcessOutbound, false},
   424  		{txnCrossProcessInbound | txnCrossProcessOutbound, true},
   425  		{txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, true},
   426  	} {
   427  		txp := &TxnCrossProcess{Type: tc.txpType}
   428  		actual := txp.IsInbound()
   429  		if actual != tc.expected {
   430  			t.Errorf("unexpected IsInbound result for input %d: expected=%v; got=%v", tc.txpType, tc.expected, actual)
   431  		}
   432  	}
   433  }
   434  
   435  func TestTxnCrossProcessIsOutbound(t *testing.T) {
   436  	for _, tc := range []struct {
   437  		txpType  uint8
   438  		expected bool
   439  	}{
   440  		{0, false},
   441  		{txnCrossProcessSynthetics, false},
   442  		{txnCrossProcessInbound, false},
   443  		{txnCrossProcessOutbound, true},
   444  		{txnCrossProcessSynthetics | txnCrossProcessInbound, false},
   445  		{txnCrossProcessSynthetics | txnCrossProcessOutbound, true},
   446  		{txnCrossProcessInbound | txnCrossProcessOutbound, true},
   447  		{txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, true},
   448  	} {
   449  		txp := &TxnCrossProcess{Type: tc.txpType}
   450  		actual := txp.IsOutbound()
   451  		if actual != tc.expected {
   452  			t.Errorf("unexpected IsOutbound result for input %d: expected=%v; got=%v", tc.txpType, tc.expected, actual)
   453  		}
   454  	}
   455  }
   456  
   457  func TestTxnCrossProcessIsSynthetics(t *testing.T) {
   458  	for _, tc := range []struct {
   459  		txpType    uint8
   460  		synthetics *cat.SyntheticsHeader
   461  		expected   bool
   462  	}{
   463  		{0, nil, false},
   464  		{txnCrossProcessSynthetics, nil, false},
   465  		{txnCrossProcessInbound, nil, false},
   466  		{txnCrossProcessOutbound, nil, false},
   467  		{txnCrossProcessSynthetics | txnCrossProcessInbound, nil, false},
   468  		{txnCrossProcessSynthetics | txnCrossProcessOutbound, nil, false},
   469  		{txnCrossProcessInbound | txnCrossProcessOutbound, nil, false},
   470  		{txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, nil, false},
   471  		{0, &cat.SyntheticsHeader{}, false},
   472  		{txnCrossProcessSynthetics, &cat.SyntheticsHeader{}, true},
   473  		{txnCrossProcessInbound, &cat.SyntheticsHeader{}, false},
   474  		{txnCrossProcessOutbound, &cat.SyntheticsHeader{}, false},
   475  		{txnCrossProcessSynthetics | txnCrossProcessInbound, &cat.SyntheticsHeader{}, true},
   476  		{txnCrossProcessSynthetics | txnCrossProcessOutbound, &cat.SyntheticsHeader{}, true},
   477  		{txnCrossProcessInbound | txnCrossProcessOutbound, &cat.SyntheticsHeader{}, false},
   478  		{txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, &cat.SyntheticsHeader{}, true},
   479  	} {
   480  		txp := &TxnCrossProcess{Type: tc.txpType, Synthetics: tc.synthetics}
   481  		actual := txp.IsSynthetics()
   482  		if actual != tc.expected {
   483  			t.Errorf("unexpected IsSynthetics result for input %d and %p: expected=%v; got=%v", tc.txpType, tc.synthetics, tc.expected, actual)
   484  		}
   485  	}
   486  }
   487  
   488  func TestTxnCrossProcessUsed(t *testing.T) {
   489  	for _, tc := range []struct {
   490  		txpType  uint8
   491  		expected bool
   492  	}{
   493  		{0, false},
   494  		{txnCrossProcessSynthetics, true},
   495  		{txnCrossProcessInbound, true},
   496  		{txnCrossProcessOutbound, true},
   497  		{txnCrossProcessSynthetics | txnCrossProcessInbound, true},
   498  		{txnCrossProcessSynthetics | txnCrossProcessOutbound, true},
   499  		{txnCrossProcessInbound | txnCrossProcessOutbound, true},
   500  		{txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, true},
   501  	} {
   502  		txp := &TxnCrossProcess{Type: tc.txpType}
   503  		actual := txp.Used()
   504  		if actual != tc.expected {
   505  			t.Errorf("unexpected Used result for input %d: expected=%v; got=%v", tc.txpType, tc.expected, actual)
   506  		}
   507  	}
   508  }
   509  
   510  func TestTxnCrossProcessSetInbound(t *testing.T) {
   511  	txp := &TxnCrossProcess{Type: 0}
   512  
   513  	txp.SetInbound(false)
   514  	if txp.IsInbound() != false {
   515  		t.Error("Inbound is not false after being set to false from false")
   516  	}
   517  
   518  	txp.SetInbound(true)
   519  	if txp.IsInbound() != true {
   520  		t.Error("Inbound is not true after being set to true from false")
   521  	}
   522  
   523  	txp.SetInbound(true)
   524  	if txp.IsInbound() != true {
   525  		t.Error("Inbound is not true after being set to true from true")
   526  	}
   527  
   528  	txp.SetInbound(false)
   529  	if txp.IsInbound() != false {
   530  		t.Error("Inbound is not false after being set to false from true")
   531  	}
   532  }
   533  
   534  func TestTxnCrossProcessSetOutbound(t *testing.T) {
   535  	txp := &TxnCrossProcess{Type: 0}
   536  
   537  	txp.SetOutbound(false)
   538  	if txp.IsOutbound() != false {
   539  		t.Error("Outbound is not false after being set to false from false")
   540  	}
   541  
   542  	txp.SetOutbound(true)
   543  	if txp.IsOutbound() != true {
   544  		t.Error("Outbound is not true after being set to true from false")
   545  	}
   546  
   547  	txp.SetOutbound(true)
   548  	if txp.IsOutbound() != true {
   549  		t.Error("Outbound is not true after being set to true from true")
   550  	}
   551  
   552  	txp.SetOutbound(false)
   553  	if txp.IsOutbound() != false {
   554  		t.Error("Outbound is not false after being set to false from true")
   555  	}
   556  }
   557  
   558  func TestTxnCrossProcessSetSynthetics(t *testing.T) {
   559  	// We'll always set SyntheticsHeader, since we're not really testing the full
   560  	// behaviour of IsSynthetics() here.
   561  	txp := &TxnCrossProcess{
   562  		Type:       0,
   563  		Synthetics: &cat.SyntheticsHeader{},
   564  	}
   565  
   566  	txp.SetSynthetics(false)
   567  	if txp.IsSynthetics() != false {
   568  		t.Error("Synthetics is not false after being set to false from false")
   569  	}
   570  
   571  	txp.SetSynthetics(true)
   572  	if txp.IsSynthetics() != true {
   573  		t.Error("Synthetics is not true after being set to true from false")
   574  	}
   575  
   576  	txp.SetSynthetics(true)
   577  	if txp.IsSynthetics() != true {
   578  		t.Error("Synthetics is not true after being set to true from true")
   579  	}
   580  
   581  	txp.SetSynthetics(false)
   582  	if txp.IsSynthetics() != false {
   583  		t.Error("Synthetics is not false after being set to false from true")
   584  	}
   585  }
   586  
   587  func TestTxnCrossProcessParseAppData(t *testing.T) {
   588  	for _, tc := range []struct {
   589  		name            string
   590  		encodingKey     string
   591  		input           string
   592  		expectedAppData *cat.AppDataHeader
   593  		expectedError   bool
   594  	}{
   595  		{
   596  			name:            "empty string",
   597  			encodingKey:     "foo",
   598  			input:           "",
   599  			expectedAppData: nil,
   600  			expectedError:   false,
   601  		},
   602  		{
   603  			name:            "invalidly encoded string",
   604  			encodingKey:     "foo",
   605  			input:           "xxx",
   606  			expectedAppData: nil,
   607  			expectedError:   true,
   608  		},
   609  		{
   610  			name:            "invalid JSON",
   611  			encodingKey:     "foo",
   612  			input:           mustObfuscate("xxx", "foo"),
   613  			expectedAppData: nil,
   614  			expectedError:   true,
   615  		},
   616  		{
   617  			name:            "invalid encoding key",
   618  			encodingKey:     "foo",
   619  			input:           mustObfuscate(`["xp","txn",1,2,3,"guid",false]`, "bar"),
   620  			expectedAppData: nil,
   621  			expectedError:   true,
   622  		},
   623  		{
   624  			name:        "success",
   625  			encodingKey: "foo",
   626  			input:       mustObfuscate(`["xp","txn",1,2,3,"guid",false]`, "foo"),
   627  			expectedAppData: &cat.AppDataHeader{
   628  				CrossProcessID:        "xp",
   629  				TransactionName:       "txn",
   630  				QueueTimeInSeconds:    1,
   631  				ResponseTimeInSeconds: 2,
   632  				ContentLength:         3,
   633  				TransactionGUID:       "guid",
   634  			},
   635  			expectedError: false,
   636  		},
   637  	} {
   638  		txp := &TxnCrossProcess{
   639  			Enabled:     true,
   640  			EncodingKey: []byte(tc.encodingKey),
   641  		}
   642  
   643  		actualAppData, actualErr := txp.ParseAppData(tc.input)
   644  
   645  		if tc.expectedError && actualErr == nil {
   646  			t.Errorf("%s: expected an error, but didn't get one", tc.name)
   647  		} else if tc.expectedError == false && actualErr != nil {
   648  			t.Errorf("%s: expected no error, but got %v", tc.name, actualErr)
   649  		}
   650  
   651  		if !reflect.DeepEqual(actualAppData, tc.expectedAppData) {
   652  			t.Errorf("%s: app data mismatched: expected=%v; got=%v", tc.name, tc.expectedAppData, actualAppData)
   653  		}
   654  	}
   655  }
   656  
   657  func TestTxnCrossProcessCreateAppData(t *testing.T) {
   658  	for _, tc := range []struct {
   659  		name            string
   660  		enabled         bool
   661  		crossProcessID  string
   662  		encodingKey     string
   663  		txnName         string
   664  		queueTime       time.Duration
   665  		responseTime    time.Duration
   666  		contentLength   int64
   667  		guid            string
   668  		expectedAppData string
   669  		expectedError   bool
   670  	}{
   671  		{
   672  			name:            "cat disabled",
   673  			enabled:         false,
   674  			crossProcessID:  "1#1",
   675  			encodingKey:     "foo",
   676  			txnName:         "txn",
   677  			queueTime:       1 * time.Second,
   678  			responseTime:    2 * time.Second,
   679  			contentLength:   4096,
   680  			guid:            "",
   681  			expectedAppData: "",
   682  			expectedError:   false,
   683  		},
   684  		{
   685  			name:            "invalid encoding key",
   686  			enabled:         true,
   687  			crossProcessID:  "1#1",
   688  			encodingKey:     "",
   689  			txnName:         "txn",
   690  			queueTime:       1 * time.Second,
   691  			responseTime:    2 * time.Second,
   692  			contentLength:   4096,
   693  			guid:            "",
   694  			expectedAppData: "",
   695  			expectedError:   true,
   696  		},
   697  		{
   698  			name:            "success",
   699  			enabled:         true,
   700  			crossProcessID:  "1#1",
   701  			encodingKey:     "foo",
   702  			txnName:         "txn",
   703  			queueTime:       1 * time.Second,
   704  			responseTime:    2 * time.Second,
   705  			contentLength:   4096,
   706  			guid:            "guid",
   707  			expectedAppData: mustObfuscate(`["1#1","txn",1,2,4096,"guid",false]`, "foo"),
   708  			expectedError:   false,
   709  		},
   710  	} {
   711  		txp := &TxnCrossProcess{
   712  			Enabled:        tc.enabled,
   713  			EncodingKey:    []byte(tc.encodingKey),
   714  			CrossProcessID: []byte(tc.crossProcessID),
   715  			GUID:           tc.guid,
   716  		}
   717  
   718  		actualAppData, actualErr := txp.CreateAppData(tc.txnName, tc.queueTime, tc.responseTime, tc.contentLength)
   719  
   720  		if tc.expectedError && actualErr == nil {
   721  			t.Errorf("%s: expected an error, but didn't get one", tc.name)
   722  		} else if tc.expectedError == false && actualErr != nil {
   723  			t.Errorf("%s: expected no error, but got %v", tc.name, actualErr)
   724  		}
   725  
   726  		if !reflect.DeepEqual(actualAppData, tc.expectedAppData) {
   727  			t.Errorf("%s: app data mismatched: expected=%v; got=%v", tc.name, tc.expectedAppData, actualAppData)
   728  		}
   729  	}
   730  }
   731  
   732  func TestTxnCrossProcessHandleInboundRequestHeaders(t *testing.T) {
   733  	for _, tc := range []struct {
   734  		name          string
   735  		enabled       bool
   736  		reply         *ConnectReply
   737  		metadata      CrossProcessMetadata
   738  		expectedError bool
   739  	}{
   740  		{
   741  			name:    "disabled, invalid encoding key, invalid synthetics",
   742  			enabled: false,
   743  			reply: &ConnectReply{
   744  				EncodingKey: "",
   745  			},
   746  			metadata: CrossProcessMetadata{
   747  				Synthetics: "foo",
   748  			},
   749  			expectedError: true,
   750  		},
   751  		{
   752  			name:    "disabled, valid encoding key, invalid synthetics",
   753  			enabled: false,
   754  			reply:   replyAccountOne,
   755  			metadata: CrossProcessMetadata{
   756  				Synthetics: "foo",
   757  			},
   758  			expectedError: true,
   759  		},
   760  		{
   761  			name:    "disabled, valid encoding key, valid synthetics",
   762  			enabled: false,
   763  			reply:   replyAccountOne,
   764  			metadata: CrossProcessMetadata{
   765  				Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"),
   766  			},
   767  			expectedError: false,
   768  		},
   769  		{
   770  			name:    "enabled, invalid encoding key, valid input",
   771  			enabled: true,
   772  			reply: &ConnectReply{
   773  				EncodingKey: "",
   774  			},
   775  			metadata: CrossProcessMetadata{
   776  				ID:      mustObfuscate(`1#1`, "foo"),
   777  				TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"),
   778  			},
   779  			expectedError: true,
   780  		},
   781  		{
   782  			name:    "enabled, valid encoding key, invalid id",
   783  			enabled: true,
   784  			reply:   replyAccountOne,
   785  			metadata: CrossProcessMetadata{
   786  				ID:      mustObfuscate(`1`, "foo"),
   787  				TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"),
   788  			},
   789  			expectedError: true,
   790  		},
   791  		{
   792  			name:    "enabled, valid encoding key, invalid txndata",
   793  			enabled: true,
   794  			reply:   replyAccountOne,
   795  			metadata: CrossProcessMetadata{
   796  				ID:      mustObfuscate(`1#1`, "foo"),
   797  				TxnData: mustObfuscate(`["00000000",invalid,"00000000","b95be233"]`, "foo"),
   798  			},
   799  			expectedError: true,
   800  		},
   801  		{
   802  			name:    "enabled, valid encoding key, valid input",
   803  			enabled: true,
   804  			reply:   replyAccountOne,
   805  			metadata: CrossProcessMetadata{
   806  				ID:      mustObfuscate(`1#1`, "foo"),
   807  				TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"),
   808  			},
   809  			expectedError: false,
   810  		},
   811  	} {
   812  		txp := &TxnCrossProcess{Enabled: tc.enabled}
   813  		txp.Init(tc.enabled, false, tc.reply)
   814  
   815  		err := txp.handleInboundRequestHeaders(tc.metadata)
   816  		if tc.expectedError && err == nil {
   817  			t.Errorf("%s: expected error, but didn't get one", tc.name)
   818  		} else if tc.expectedError == false && err != nil {
   819  			t.Errorf("%s: expected no error, but got %v", tc.name, err)
   820  		}
   821  	}
   822  }