github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/txn_cross_process_test.go (about)

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