github.com/newrelic/go-agent@v3.26.0+incompatible/_integrations/nrmongo/nrmongo_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package nrmongo
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  
    10  	newrelic "github.com/newrelic/go-agent"
    11  	"github.com/newrelic/go-agent/internal"
    12  	"github.com/newrelic/go-agent/internal/integrationsupport"
    13  	"go.mongodb.org/mongo-driver/bson"
    14  	"go.mongodb.org/mongo-driver/bson/primitive"
    15  	"go.mongodb.org/mongo-driver/event"
    16  )
    17  
    18  var (
    19  	connID       = "localhost:27017[-1]"
    20  	reqID  int64 = 10
    21  	raw, _       = bson.Marshal(bson.D{primitive.E{Key: "commName", Value: "collName"}, {Key: "$db", Value: "testing"}})
    22  	ste          = &event.CommandStartedEvent{
    23  		Command:      raw,
    24  		DatabaseName: "testdb",
    25  		CommandName:  "commName",
    26  		RequestID:    reqID,
    27  		ConnectionID: connID,
    28  	}
    29  	finishedEvent = event.CommandFinishedEvent{
    30  		DurationNanos: 5,
    31  		CommandName:   "name",
    32  		RequestID:     reqID,
    33  		ConnectionID:  connID,
    34  	}
    35  	se = &event.CommandSucceededEvent{
    36  		CommandFinishedEvent: finishedEvent,
    37  		Reply:                nil,
    38  	}
    39  	fe = &event.CommandFailedEvent{
    40  		CommandFinishedEvent: finishedEvent,
    41  		Failure:              "failureCause",
    42  	}
    43  )
    44  
    45  func TestOrigMonitorsAreCalled(t *testing.T) {
    46  	var started, succeeded, failed bool
    47  	origMonitor := &event.CommandMonitor{
    48  		Started:   func(ctx context.Context, e *event.CommandStartedEvent) { started = true },
    49  		Succeeded: func(ctx context.Context, e *event.CommandSucceededEvent) { succeeded = true },
    50  		Failed:    func(ctx context.Context, e *event.CommandFailedEvent) { failed = true },
    51  	}
    52  	ctx := context.Background()
    53  	nrMonitor := NewCommandMonitor(origMonitor)
    54  
    55  	nrMonitor.Started(ctx, ste)
    56  	if !started {
    57  		t.Error("started not called")
    58  	}
    59  	nrMonitor.Succeeded(ctx, se)
    60  	if !succeeded {
    61  		t.Error("succeeded not called")
    62  	}
    63  	nrMonitor.Failed(ctx, fe)
    64  	if !failed {
    65  		t.Error("failed not called")
    66  	}
    67  }
    68  
    69  func TestClientOptsWithNullFunctions(t *testing.T) {
    70  	origMonitor := &event.CommandMonitor{} // the monitor isn't nil, but its functions are.
    71  	ctx := context.Background()
    72  	nrMonitor := NewCommandMonitor(origMonitor)
    73  
    74  	// Verifying no nil pointer dereferences
    75  	nrMonitor.Started(ctx, ste)
    76  	nrMonitor.Succeeded(ctx, se)
    77  	nrMonitor.Failed(ctx, fe)
    78  }
    79  
    80  func TestHostAndPort(t *testing.T) {
    81  	type hostAndPort struct {
    82  		host string
    83  		port string
    84  	}
    85  	testCases := map[string]hostAndPort{
    86  		"localhost:8080[-1]":                     {host: "localhost", port: "8080"},
    87  		"something.com:987[-789]":                {host: "something.com", port: "987"},
    88  		"thisformatiswrong":                      {host: "", port: ""},
    89  		"somethingunix.sock[-876]":               {host: "somethingunix.sock", port: ""},
    90  		"/var/dir/path/somethingunix.sock[-876]": {host: "/var/dir/path/somethingunix.sock", port: ""},
    91  	}
    92  	for test, expected := range testCases {
    93  		h, p := calcHostAndPort(test)
    94  		if expected.host != h {
    95  			t.Errorf("unexpected host - expected %s, got %s", expected.host, h)
    96  		}
    97  		if expected.port != p {
    98  			t.Errorf("unexpected port - expected %s, got %s", expected.port, p)
    99  		}
   100  	}
   101  }
   102  
   103  func TestMonitor(t *testing.T) {
   104  	var started, succeeded, failed bool
   105  	origMonitor := &event.CommandMonitor{
   106  		Started:   func(ctx context.Context, e *event.CommandStartedEvent) { started = true },
   107  		Succeeded: func(ctx context.Context, e *event.CommandSucceededEvent) { succeeded = true },
   108  		Failed:    func(ctx context.Context, e *event.CommandFailedEvent) { failed = true },
   109  	}
   110  	nrMonitor := mongoMonitor{
   111  		segmentMap:  make(map[int64]*newrelic.DatastoreSegment),
   112  		origCommMon: origMonitor,
   113  	}
   114  	app := createTestApp()
   115  	txn := app.StartTransaction("txnName", nil, nil)
   116  	ctx := newrelic.NewContext(context.Background(), txn)
   117  	nrMonitor.started(ctx, ste)
   118  	if !started {
   119  		t.Error("Original monitor not started")
   120  	}
   121  	if len(nrMonitor.segmentMap) != 1 {
   122  		t.Errorf("Wrong number of segments, expected 1 but got %d", len(nrMonitor.segmentMap))
   123  	}
   124  	nrMonitor.succeeded(ctx, se)
   125  	if !succeeded {
   126  		t.Error("Original monitor not succeeded")
   127  	}
   128  	if len(nrMonitor.segmentMap) != 0 {
   129  		t.Errorf("Wrong number of segments, expected 0 but got %d", len(nrMonitor.segmentMap))
   130  	}
   131  	txn.End()
   132  
   133  	app.ExpectMetrics(t, []internal.WantMetric{
   134  		{Name: "OtherTransactionTotalTime/Go/txnName", Scope: "", Forced: false, Data: nil},
   135  		{Name: "Datastore/instance/MongoDB/" + internal.ThisHost + "/27017", Scope: "", Forced: false, Data: nil},
   136  		{Name: "Datastore/operation/MongoDB/commName", Scope: "", Forced: false, Data: nil},
   137  		{Name: "OtherTransaction/Go/txnName", Scope: "", Forced: true, Data: nil},
   138  		{Name: "Datastore/all", Scope: "", Forced: true, Data: nil},
   139  		{Name: "Datastore/allOther", Scope: "", Forced: true, Data: []float64{1.0}},
   140  		{Name: "Datastore/MongoDB/all", Scope: "", Forced: true, Data: []float64{1.0}},
   141  		{Name: "Datastore/MongoDB/allOther", Scope: "", Forced: true, Data: []float64{1.0}},
   142  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   143  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   144  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   145  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   146  		{Name: "Datastore/statement/MongoDB/collName/commName", Scope: "", Forced: false, Data: []float64{1.0}},
   147  		{Name: "Datastore/statement/MongoDB/collName/commName", Scope: "OtherTransaction/Go/txnName", Forced: false, Data: []float64{1.0}},
   148  	})
   149  	app.ExpectSpanEvents(t, []internal.WantEvent{
   150  		{
   151  			Intrinsics: map[string]interface{}{
   152  				"name":          "OtherTransaction/Go/txnName",
   153  				"sampled":       true,
   154  				"category":      "generic",
   155  				"nr.entryPoint": true,
   156  			},
   157  			UserAttributes:  map[string]interface{}{},
   158  			AgentAttributes: map[string]interface{}{},
   159  		},
   160  		{
   161  			Intrinsics: map[string]interface{}{
   162  				"name":      "Datastore/statement/MongoDB/collName/commName",
   163  				"sampled":   true,
   164  				"category":  "datastore",
   165  				"component": "MongoDB",
   166  				"span.kind": "client",
   167  				"parentId":  internal.MatchAnything,
   168  			},
   169  			UserAttributes: map[string]interface{}{},
   170  			AgentAttributes: map[string]interface{}{
   171  				"peer.address":  internal.ThisHost + ":27017",
   172  				"peer.hostname": internal.ThisHost,
   173  				"db.statement":  "'commName' on 'collName' using 'MongoDB'",
   174  				"db.instance":   "testdb",
   175  				"db.collection": "collName",
   176  			},
   177  		},
   178  	})
   179  
   180  	txn = app.StartTransaction("txnName", nil, nil)
   181  	ctx = newrelic.NewContext(context.Background(), txn)
   182  	nrMonitor.started(ctx, ste)
   183  	if len(nrMonitor.segmentMap) != 1 {
   184  		t.Errorf("Wrong number of segments, expected 1 but got %d", len(nrMonitor.segmentMap))
   185  	}
   186  	nrMonitor.failed(ctx, fe)
   187  	if !failed {
   188  		t.Error("Original monitor not succeeded")
   189  	}
   190  	if len(nrMonitor.segmentMap) != 0 {
   191  		t.Errorf("Wrong number of segments, expected 0 but got %d", len(nrMonitor.segmentMap))
   192  	}
   193  	txn.End()
   194  
   195  	app.ExpectMetrics(t, []internal.WantMetric{
   196  		{Name: "OtherTransactionTotalTime/Go/txnName", Scope: "", Forced: false, Data: nil},
   197  		{Name: "Datastore/instance/MongoDB/" + internal.ThisHost + "/27017", Scope: "", Forced: false, Data: nil},
   198  		{Name: "Datastore/operation/MongoDB/commName", Scope: "", Forced: false, Data: nil},
   199  		{Name: "OtherTransaction/Go/txnName", Scope: "", Forced: true, Data: nil},
   200  		{Name: "Datastore/all", Scope: "", Forced: true, Data: nil},
   201  		{Name: "Datastore/allOther", Scope: "", Forced: true, Data: []float64{2.0}},
   202  		{Name: "Datastore/MongoDB/all", Scope: "", Forced: true, Data: []float64{2.0}},
   203  		{Name: "Datastore/MongoDB/allOther", Scope: "", Forced: true, Data: []float64{2.0}},
   204  		{Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil},
   205  		{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil},
   206  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil},
   207  		{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil},
   208  		{Name: "Datastore/statement/MongoDB/collName/commName", Scope: "", Forced: false, Data: []float64{2.0}},
   209  		{Name: "Datastore/statement/MongoDB/collName/commName", Scope: "OtherTransaction/Go/txnName", Forced: false, Data: []float64{2.0}},
   210  	})
   211  }
   212  
   213  func TestCollName(t *testing.T) {
   214  	command := "find"
   215  	ex1, _ := bson.Marshal(bson.D{{Key: command, Value: "numbers"}, {Key: "$db", Value: "testing"}})
   216  	ex2, _ := bson.Marshal(bson.D{{Key: "filter", Value: ""}})
   217  	testCases := map[string]bson.Raw{
   218  		"numbers": ex1,
   219  		"":        ex2,
   220  	}
   221  	for name, raw := range testCases {
   222  		e := event.CommandStartedEvent{
   223  			Command:     raw,
   224  			CommandName: command,
   225  		}
   226  		result := collName(&e)
   227  		if result != name {
   228  			t.Errorf("Wrong collection name: %s", result)
   229  		}
   230  	}
   231  
   232  }
   233  
   234  func createTestApp() integrationsupport.ExpectApp {
   235  	return integrationsupport.NewTestApp(replyFn, cfgFn)
   236  }
   237  
   238  var cfgFn = func(cfg *newrelic.Config) {
   239  	cfg.Enabled = false
   240  	cfg.DistributedTracer.Enabled = true
   241  	cfg.TransactionTracer.SegmentThreshold = 0
   242  	cfg.TransactionTracer.Threshold.IsApdexFailing = false
   243  	cfg.TransactionTracer.Threshold.Duration = 0
   244  }
   245  
   246  var replyFn = func(reply *internal.ConnectReply) {
   247  	reply.AdaptiveSampler = internal.SampleEverything{}
   248  }