github.com/waldiirawan/apm-agent-go/v2@v2.2.2/model/marshal_test.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package model_test
    19  
    20  import (
    21  	"encoding/json"
    22  	"net/http"
    23  	"net/url"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  	"unicode/utf8"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  
    32  	"github.com/waldiirawan/apm-agent-go/v2/model"
    33  	"go.elastic.co/fastjson"
    34  )
    35  
    36  func TestMarshalTransaction(t *testing.T) {
    37  	tx := fakeTransaction()
    38  
    39  	var w fastjson.Writer
    40  	tx.MarshalFastJSON(&w)
    41  
    42  	decoded := mustUnmarshalJSON(w)
    43  	expect := map[string]interface{}{
    44  		"trace_id":  "0102030405060708090a0b0c0d0e0f10",
    45  		"id":        "0102030405060708",
    46  		"parent_id": "0001020304050607",
    47  		"name":      "GET /foo/bar",
    48  		"type":      "request",
    49  		"timestamp": float64(123000000),
    50  		"duration":  123.456,
    51  		"result":    "418",
    52  		"context": map[string]interface{}{
    53  			"custom": map[string]interface{}{
    54  				"bar": true,
    55  				"baz": 3.45,
    56  				"foo": "one",
    57  				"qux": map[string]interface{}{"quux": float64(6)},
    58  			},
    59  			"service": map[string]interface{}{
    60  				"framework": map[string]interface{}{
    61  					"name":    "framework-name",
    62  					"version": "framework-version",
    63  				},
    64  			},
    65  			"request": map[string]interface{}{
    66  				"url": map[string]interface{}{
    67  					"full":     "https://testing.invalid/foo/bar?baz#qux",
    68  					"protocol": "https",
    69  					"hostname": "testing.invalid",
    70  					"pathname": "/foo/bar",
    71  					"search":   "baz",
    72  					"hash":     "qux",
    73  				},
    74  				"method": "GET",
    75  				"headers": map[string]interface{}{
    76  					"User-Agent": "Mosaic/0.2 (Windows 3.1)",
    77  					"Cookie":     "monster=yumyum; random=junk",
    78  				},
    79  				"body":         "ahoj",
    80  				"http_version": "1.1",
    81  				"cookies": map[string]interface{}{
    82  					"monster": "yumyum",
    83  					"random":  "junk",
    84  				},
    85  				"socket": map[string]interface{}{
    86  					"remote_address": "[::1]",
    87  				},
    88  			},
    89  			"response": map[string]interface{}{
    90  				"status_code": float64(418),
    91  				"headers": map[string]interface{}{
    92  					"Content-Type": "text/html",
    93  				},
    94  			},
    95  			"user": map[string]interface{}{
    96  				"username": "wanda",
    97  			},
    98  			"tags": map[string]interface{}{
    99  				"tag": "urit",
   100  			},
   101  		},
   102  		"span_count": map[string]interface{}{
   103  			"started": float64(99),
   104  			"dropped": float64(4),
   105  		},
   106  		"dropped_spans_stats": []interface{}{
   107  			map[string]interface{}{
   108  				"destination_service_resource": "http://elasticsearch:9200",
   109  				"duration": map[string]interface{}{
   110  					"count": float64(4),
   111  					"sum": map[string]interface{}{
   112  						"us": float64(1000),
   113  					},
   114  				},
   115  				"outcome": "success",
   116  			},
   117  		},
   118  		"otel": map[string]interface{}{
   119  			"span_kind": "MESSAGING",
   120  			"attributes": map[string]interface{}{
   121  				"messaging.system": "messaging",
   122  			},
   123  		},
   124  	}
   125  	assert.Equal(t, expect, decoded)
   126  }
   127  
   128  func TestMarshalSpan(t *testing.T) {
   129  	var w fastjson.Writer
   130  	span := fakeSpan()
   131  	span.Context = fakeDatabaseSpanContext()
   132  	span.MarshalFastJSON(&w)
   133  
   134  	decoded := mustUnmarshalJSON(w)
   135  	assert.Equal(t, map[string]interface{}{
   136  		"trace_id":       "000102030405060708090a0b0c0d0e0f",
   137  		"id":             "0001020304050607",
   138  		"parent_id":      "0001020304050607",
   139  		"transaction_id": "0001020304050607",
   140  		"name":           "SELECT FROM bar",
   141  		"timestamp":      float64(123000000),
   142  		"duration":       float64(3),
   143  		"type":           "db.postgresql.query",
   144  		"context": map[string]interface{}{
   145  			"db": map[string]interface{}{
   146  				"instance":  "wat",
   147  				"statement": `SELECT foo FROM bar WHERE baz LIKE 'qu%x'`,
   148  				"type":      "sql",
   149  				"user":      "barb",
   150  			},
   151  		},
   152  		"otel": map[string]interface{}{
   153  			"span_kind": "MESSAGING",
   154  			"attributes": map[string]interface{}{
   155  				"messaging.system": "messaging",
   156  			},
   157  		},
   158  	}, decoded)
   159  
   160  	w.Reset()
   161  	span.Duration = 4
   162  	span.Name = "GET testing.invalid:8000"
   163  	span.Type = "ext.http"
   164  	span.ParentID = model.SpanID{}      // parent_id is optional
   165  	span.TransactionID = model.SpanID{} // transaction_id is optional
   166  	span.Context = fakeHTTPSpanContext()
   167  	span.OTel = &model.OTel{
   168  		SpanKind: "SERVER",
   169  		Attributes: map[string]interface{}{
   170  			"numeric.data": 123.456,
   171  			"boolean.data": true,
   172  			"slice.data":   []string{"one", "two"},
   173  		},
   174  	}
   175  	span.MarshalFastJSON(&w)
   176  
   177  	decoded = mustUnmarshalJSON(w)
   178  	assert.Equal(t, map[string]interface{}{
   179  		"trace_id":  "000102030405060708090a0b0c0d0e0f",
   180  		"id":        "0001020304050607",
   181  		"name":      "GET testing.invalid:8000",
   182  		"timestamp": float64(123000000),
   183  		"duration":  float64(4),
   184  		"type":      "ext.http",
   185  		"context": map[string]interface{}{
   186  			"http": map[string]interface{}{
   187  				"url": "http://testing.invalid:8000/path?query#fragment",
   188  			},
   189  		},
   190  		"otel": map[string]interface{}{
   191  			"span_kind": "SERVER",
   192  			"attributes": map[string]interface{}{
   193  				"numeric.data": 123.456,
   194  				"boolean.data": true,
   195  				"slice.data":   []interface{}{"one", "two"},
   196  			},
   197  		},
   198  	}, decoded)
   199  }
   200  
   201  func TestMarshalSpanHTTPStatusCode(t *testing.T) {
   202  	var w fastjson.Writer
   203  	span := fakeSpan()
   204  	span.Context = &model.SpanContext{
   205  		HTTP: &model.HTTPSpanContext{StatusCode: 200},
   206  	}
   207  	span.MarshalFastJSON(&w)
   208  
   209  	decoded := mustUnmarshalJSON(w)
   210  	assert.Equal(t, map[string]interface{}{
   211  		"trace_id":       "000102030405060708090a0b0c0d0e0f",
   212  		"id":             "0001020304050607",
   213  		"transaction_id": "0001020304050607",
   214  		"parent_id":      "0001020304050607",
   215  		"name":           "SELECT FROM bar",
   216  		"timestamp":      float64(123000000),
   217  		"duration":       float64(3),
   218  		"type":           "db.postgresql.query",
   219  		"context": map[string]interface{}{
   220  			"http": map[string]interface{}{
   221  				"status_code": 200.0,
   222  			},
   223  		},
   224  		"otel": map[string]interface{}{
   225  			"span_kind": "MESSAGING",
   226  			"attributes": map[string]interface{}{
   227  				"messaging.system": "messaging",
   228  			},
   229  		},
   230  	}, decoded)
   231  }
   232  
   233  func TestMarshalMetrics(t *testing.T) {
   234  	metrics := fakeMetrics()
   235  
   236  	var w fastjson.Writer
   237  	metrics.MarshalFastJSON(&w)
   238  
   239  	decoded := mustUnmarshalJSON(w)
   240  	expect := map[string]interface{}{
   241  		"timestamp": float64(123000000),
   242  		"tags": map[string]interface{}{
   243  			"foo": "bar",
   244  		},
   245  		"samples": map[string]interface{}{
   246  			"metric_one": map[string]interface{}{
   247  				"value": float64(1024),
   248  			},
   249  			"metric_two": map[string]interface{}{
   250  				"value": float64(-66.6),
   251  			},
   252  		},
   253  	}
   254  	assert.Equal(t, expect, decoded)
   255  }
   256  
   257  func TestMarshalError(t *testing.T) {
   258  	var e model.Error
   259  	time, err := time.Parse("2006-01-02T15:04:05.999Z", "1970-01-01T00:02:03Z")
   260  	assert.NoError(t, err)
   261  	e.Timestamp = model.Time(time)
   262  
   263  	// The primary error ID is required, all other IDs are optional
   264  	var w fastjson.Writer
   265  	e.MarshalFastJSON(&w)
   266  	assert.Equal(t, `{"id":"00000000000000000000000000000000","timestamp":123000000}`, string(w.Bytes()))
   267  
   268  	e.ID = model.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
   269  	e.TransactionID = model.SpanID{1, 2, 3, 4, 5, 6, 7, 8}
   270  	e.TraceID = model.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   271  	e.ParentID = model.SpanID{1, 2, 3, 4, 5, 6, 7, 8}
   272  	w.Reset()
   273  	e.MarshalFastJSON(&w)
   274  	assert.Equal(t,
   275  		`{"id":"000102030405060708090a0b0c0d0e0f","timestamp":123000000,"parent_id":"0102030405060708","trace_id":"0102030405060708090a0b0c0d0e0f10","transaction_id":"0102030405060708"}`,
   276  		string(w.Bytes()),
   277  	)
   278  }
   279  
   280  func TestMarshalErrorTransactionUnsampled(t *testing.T) {
   281  	var e model.Error
   282  	time, err := time.Parse("2006-01-02T15:04:05.999Z", "1970-01-01T00:02:03Z")
   283  	assert.NoError(t, err)
   284  	e.Timestamp = model.Time(time)
   285  	e.Transaction.Sampled = new(bool)
   286  	e.Transaction.Type = "foo"
   287  
   288  	var w fastjson.Writer
   289  	e.MarshalFastJSON(&w)
   290  	assert.Equal(t, `{"id":"00000000000000000000000000000000","timestamp":123000000,"transaction":{"sampled":false,"type":"foo"}}`, string(w.Bytes()))
   291  }
   292  
   293  func TestMarshalCookies(t *testing.T) {
   294  	cookies := model.Cookies{
   295  		{Name: "foo", Value: "!"}, // eclipsed
   296  		{Name: "baz", Value: "qux"},
   297  		{Name: "foo", Value: "bar"},
   298  	}
   299  	var w fastjson.Writer
   300  	cookies.MarshalFastJSON(&w)
   301  	assert.Equal(t, `{"foo":"bar","baz":"qux"}`, string(w.Bytes()))
   302  }
   303  
   304  func TestMarshalRequestBody(t *testing.T) {
   305  	body := model.RequestBody{
   306  		Raw: "rawr",
   307  	}
   308  	var w fastjson.Writer
   309  	body.MarshalFastJSON(&w)
   310  	assert.Equal(t, `"rawr"`, string(w.Bytes()))
   311  
   312  	body.Form = url.Values{
   313  		"first":    []string{"jackie"},
   314  		"last":     []string{"brown"},
   315  		"keywords": []string{"rum", "punch"},
   316  	}
   317  	w.Reset()
   318  	body.MarshalFastJSON(&w)
   319  
   320  	decoded := mustUnmarshalJSON(w)
   321  	expect := map[string]interface{}{
   322  		"first":    "jackie",
   323  		"last":     "brown",
   324  		"keywords": []interface{}{"rum", "punch"},
   325  	}
   326  	assert.Equal(t, expect, decoded)
   327  }
   328  
   329  func TestMarshalLog(t *testing.T) {
   330  	log := model.Log{
   331  		Message:      "foo",
   332  		Level:        "bar",
   333  		LoggerName:   "baz",
   334  		ParamMessage: "%s",
   335  	}
   336  	var w fastjson.Writer
   337  	log.MarshalFastJSON(&w)
   338  
   339  	assert.Equal(t, `{"message":"foo","level":"bar","logger_name":"baz","param_message":"%s"}`, string(w.Bytes()))
   340  
   341  	log = model.Log{
   342  		Message:    "foo",
   343  		LoggerName: "bar",
   344  	}
   345  	w.Reset()
   346  	log.MarshalFastJSON(&w)
   347  	assert.Equal(t, `{"message":"foo","logger_name":"bar"}`, string(w.Bytes()))
   348  }
   349  
   350  func TestMarshalException(t *testing.T) {
   351  	x := model.Exception{
   352  		Message: "foo",
   353  		Type:    "bar",
   354  		Module:  "baz",
   355  		Attributes: map[string]interface{}{
   356  			"qux": map[string]interface{}{
   357  				"quux": "corge",
   358  			},
   359  		},
   360  		Handled: true,
   361  	}
   362  	var w fastjson.Writer
   363  	x.MarshalFastJSON(&w)
   364  
   365  	assert.Equal(t,
   366  		`{"handled":true,"message":"foo","attributes":{"qux":{"quux":"corge"}},"module":"baz","type":"bar"}`,
   367  		string(w.Bytes()),
   368  	)
   369  }
   370  
   371  func TestMarshalExceptionCode(t *testing.T) {
   372  	code := model.ExceptionCode{
   373  		String: "boom",
   374  		Number: 123,
   375  	}
   376  	var w fastjson.Writer
   377  	code.MarshalFastJSON(&w)
   378  	assert.Equal(t, `"boom"`, string(w.Bytes()))
   379  
   380  	w.Reset()
   381  	code.String = ""
   382  	code.MarshalFastJSON(&w)
   383  	assert.Equal(t, `123`, string(w.Bytes()))
   384  }
   385  
   386  func TestMarshalUser(t *testing.T) {
   387  	user := model.User{
   388  		Email:    "foo@example.com",
   389  		ID:       "123",
   390  		Username: "bar",
   391  	}
   392  	var w fastjson.Writer
   393  	user.MarshalFastJSON(&w)
   394  	assert.Equal(t, `{"email":"foo@example.com","id":"123","username":"bar"}`, string(w.Bytes()))
   395  }
   396  
   397  func TestMarshalStacktraceFrame(t *testing.T) {
   398  	f := model.StacktraceFrame{
   399  		File:         "file.go",
   400  		Line:         123,
   401  		AbsolutePath: "fabulous",
   402  		Function:     "wonderment",
   403  	}
   404  	var w fastjson.Writer
   405  	f.MarshalFastJSON(&w)
   406  
   407  	assert.Equal(t,
   408  		`{"filename":"file.go","lineno":123,"abs_path":"fabulous","function":"wonderment"}`,
   409  		string(w.Bytes()),
   410  	)
   411  
   412  	f = model.StacktraceFrame{
   413  		File:         "file.go",
   414  		Line:         123,
   415  		LibraryFrame: true,
   416  		ContextLine:  "0",
   417  		PreContext:   []string{"-2", "-1"},
   418  		PostContext:  []string{"+1", "+2"},
   419  		Vars: map[string]interface{}{
   420  			"foo": []string{"bar", "baz"},
   421  		},
   422  	}
   423  	w.Reset()
   424  	f.MarshalFastJSON(&w)
   425  	assert.Equal(t,
   426  		`{"filename":"file.go","lineno":123,"context_line":"0","library_frame":true,"post_context":["+1","+2"],"pre_context":["-2","-1"],"vars":{"foo":["bar","baz"]}}`,
   427  		string(w.Bytes()),
   428  	)
   429  }
   430  
   431  func TestMarshalResponse(t *testing.T) {
   432  	finished := true
   433  	headersSent := true
   434  	response := model.Response{
   435  		Finished: &finished,
   436  		Headers: model.Headers{{
   437  			Key:    "Content-Type",
   438  			Values: []string{"text/plain"},
   439  		}},
   440  		HeadersSent: &headersSent,
   441  		StatusCode:  200,
   442  	}
   443  	var w fastjson.Writer
   444  	response.MarshalFastJSON(&w)
   445  	assert.Equal(t,
   446  		`{"finished":true,"headers":{"Content-Type":"text/plain"},"headers_sent":true,"status_code":200}`,
   447  		string(w.Bytes()),
   448  	)
   449  }
   450  
   451  func TestMarshalURL(t *testing.T) {
   452  	in := model.URL{
   453  		Path:     "/",
   454  		Search:   "abc=def",
   455  		Hash:     strings.Repeat("x", 1000), // exceed "full" URL length
   456  		Hostname: "testing.invalid",
   457  		Port:     "999",
   458  		Protocol: "http",
   459  	}
   460  
   461  	var w fastjson.Writer
   462  	in.MarshalFastJSON(&w)
   463  
   464  	var out model.URL
   465  	err := json.Unmarshal(w.Bytes(), &out)
   466  	require.NoError(t, err)
   467  
   468  	// The full URL should have been truncated to avoid a validation error.
   469  	assert.Equal(t, "http://testing.invalid:999/?abc=def#"+strings.Repeat("x", 988), out.Full)
   470  	out.Full = ""
   471  
   472  	assert.Equal(t, in, out)
   473  }
   474  
   475  func TestMarshalURLFullTruncated(t *testing.T) {
   476  	t.Run("escape", func(t *testing.T) {
   477  		testMarshalURLFullTruncated(t, '&')
   478  	})
   479  	t.Run("unicode", func(t *testing.T) {
   480  		testMarshalURLFullTruncated(t, 'δΈ–')
   481  	})
   482  }
   483  
   484  func testMarshalURLFullTruncated(t *testing.T, r rune) {
   485  	const maxRunes = 1024
   486  	in := model.URL{
   487  		Hostname: "example.com",
   488  		Path:     "/",
   489  		Protocol: "http",
   490  		Search:   strings.Repeat(string(r), maxRunes), // should be truncated
   491  	}
   492  
   493  	var w fastjson.Writer
   494  	in.MarshalFastJSON(&w)
   495  
   496  	var out model.URL
   497  	err := json.Unmarshal(w.Bytes(), &out)
   498  	require.NoError(t, err)
   499  
   500  	assert.Equal(t, maxRunes, utf8.RuneCountInString(out.Full))
   501  
   502  	const prefix = "http://example.com/?"
   503  	assert.Equal(t, prefix+strings.Repeat(string(r), maxRunes-len(prefix)), out.Full)
   504  }
   505  
   506  func TestMarshalURLPathEmpty(t *testing.T) {
   507  	in := model.URL{
   508  		Hostname: "example.com",
   509  		Path:     "",
   510  		Protocol: "http",
   511  	}
   512  
   513  	var w fastjson.Writer
   514  	in.MarshalFastJSON(&w)
   515  
   516  	var out model.URL
   517  	err := json.Unmarshal(w.Bytes(), &out)
   518  	require.NoError(t, err)
   519  	assert.Equal(t, "http://example.com", out.Full)
   520  }
   521  
   522  func TestMarshalURLPathLeadingSlashMissing(t *testing.T) {
   523  	in := model.URL{
   524  		Path:     "foo",
   525  		Search:   "abc=def",
   526  		Hostname: "testing.invalid",
   527  		Port:     "999",
   528  		Protocol: "http",
   529  	}
   530  
   531  	var w fastjson.Writer
   532  	in.MarshalFastJSON(&w)
   533  
   534  	var out model.URL
   535  	err := json.Unmarshal(w.Bytes(), &out)
   536  	require.NoError(t, err)
   537  
   538  	assert.Equal(t, "http://testing.invalid:999/foo?abc=def", out.Full)
   539  	out.Full = ""
   540  
   541  	// Leading slash should have been added during marshalling. We do it
   542  	// here rather than when building the model to avoid allocation.
   543  	in.Path = "/foo"
   544  
   545  	assert.Equal(t, in, out)
   546  }
   547  
   548  func TestMarshalHTTPSpanContextURLPathLeadingSlashMissing(t *testing.T) {
   549  	httpSpanContext := model.HTTPSpanContext{
   550  		URL: mustParseURL("http://testing.invalid:8000/path?query#fragment"),
   551  	}
   552  	httpSpanContext.URL.Path = "path"
   553  
   554  	var w fastjson.Writer
   555  	httpSpanContext.MarshalFastJSON(&w)
   556  
   557  	var out model.HTTPSpanContext
   558  	err := json.Unmarshal(w.Bytes(), &out)
   559  	require.NoError(t, err)
   560  
   561  	// Leading slash should have been added during marshalling.
   562  	httpSpanContext.URL.Path = "/path"
   563  	assert.Equal(t, httpSpanContext.URL, out.URL)
   564  }
   565  
   566  func TestTransactionUnmarshalJSON(t *testing.T) {
   567  	tx := fakeTransaction()
   568  	var w fastjson.Writer
   569  	tx.MarshalFastJSON(&w)
   570  
   571  	var out model.Transaction
   572  	err := json.Unmarshal(w.Bytes(), &out)
   573  	require.NoError(t, err)
   574  	assert.Equal(t, tx, out)
   575  }
   576  
   577  func TestMarshalCloud(t *testing.T) {
   578  	cloud := fakeCloud()
   579  
   580  	var w fastjson.Writer
   581  	cloud.MarshalFastJSON(&w)
   582  
   583  	decoded := mustUnmarshalJSON(w)
   584  	expect := map[string]interface{}{
   585  		"provider":          "zeus",
   586  		"region":            "troposphere",
   587  		"availability_zone": "torrid",
   588  		"instance": map[string]interface{}{
   589  			"id":   "instance_id",
   590  			"name": "instance_name",
   591  		},
   592  		"machine": map[string]interface{}{
   593  			"type": "machine_type",
   594  		},
   595  		"account": map[string]interface{}{
   596  			"id":   "account_id",
   597  			"name": "account_name",
   598  		},
   599  		"project": map[string]interface{}{
   600  			"id":   "project_id",
   601  			"name": "project_name",
   602  		},
   603  	}
   604  	assert.Equal(t, expect, decoded)
   605  }
   606  
   607  func TestMarshalMetric(t *testing.T) {
   608  	histogram := &model.Metric{
   609  		Type:   "histogram",
   610  		Values: []float64{0.05, 0.1, 0.5, 1, 5},
   611  		Counts: []uint64{1, 1, 5, 10, 5},
   612  	}
   613  
   614  	var w fastjson.Writer
   615  	histogram.MarshalFastJSON(&w)
   616  	expect := `{"values":[0.05,0.1,0.5,1,5],"counts":[1,1,5,10,5],"type":"histogram"}`
   617  
   618  	assert.Equal(t, expect, string(w.Bytes()))
   619  
   620  	m := &model.Metric{Value: 1}
   621  
   622  	w.Reset()
   623  	m.MarshalFastJSON(&w)
   624  	expect = `{"value":1}`
   625  
   626  	assert.Equal(t, expect, string(w.Bytes()))
   627  }
   628  
   629  func fakeTransaction() model.Transaction {
   630  	return model.Transaction{
   631  		TraceID:   model.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
   632  		ID:        model.SpanID{1, 2, 3, 4, 5, 6, 7, 8},
   633  		ParentID:  model.SpanID{0, 1, 2, 3, 4, 5, 6, 7},
   634  		Name:      "GET /foo/bar",
   635  		Type:      "request",
   636  		Timestamp: model.Time(time.Unix(123, 0).UTC()),
   637  		Duration:  123.456,
   638  		Result:    "418",
   639  		Context: &model.Context{
   640  			Request: &model.Request{
   641  				URL: model.URL{
   642  					Full:     "https://testing.invalid/foo/bar?baz#qux",
   643  					Hostname: "testing.invalid",
   644  					Protocol: "https",
   645  					Path:     "/foo/bar",
   646  					Search:   "baz",
   647  					Hash:     "qux",
   648  				},
   649  				Method: "GET",
   650  				Headers: model.Headers{{
   651  					Key: "Cookie", Values: []string{"monster=yumyum; random=junk"},
   652  				}, {
   653  					Key: "User-Agent", Values: []string{"Mosaic/0.2 (Windows 3.1)"},
   654  				}},
   655  				Body: &model.RequestBody{
   656  					Raw: "ahoj",
   657  				},
   658  				HTTPVersion: "1.1",
   659  				Cookies: []*http.Cookie{
   660  					{Name: "monster", Value: "yumyum"},
   661  					{Name: "random", Value: "junk"},
   662  				},
   663  				Socket: &model.RequestSocket{
   664  					RemoteAddress: "[::1]",
   665  				},
   666  			},
   667  			Response: &model.Response{
   668  				StatusCode: 418,
   669  				Headers: model.Headers{{
   670  					Key: "Content-Type", Values: []string{"text/html"},
   671  				}},
   672  			},
   673  			Custom: model.IfaceMap{
   674  				{Key: "bar", Value: true},
   675  				{Key: "baz", Value: 3.45},
   676  				{Key: "foo", Value: "one"},
   677  				{Key: "qux", Value: map[string]interface{}{"quux": float64(6)}},
   678  			},
   679  			User: &model.User{
   680  				Username: "wanda",
   681  			},
   682  			Tags: model.IfaceMap{{
   683  				Key: "tag", Value: "urit",
   684  			}},
   685  			Service: &model.Service{
   686  				Framework: &model.Framework{
   687  					Name:    "framework-name",
   688  					Version: "framework-version",
   689  				},
   690  			},
   691  		},
   692  		SpanCount: model.SpanCount{
   693  			Started: 99,
   694  			Dropped: 4,
   695  		},
   696  		DroppedSpansStats: []model.DroppedSpansStats{
   697  			{
   698  				DestinationServiceResource: "http://elasticsearch:9200",
   699  				Outcome:                    "success",
   700  				Duration: model.AggregateDuration{
   701  					Count: 4,
   702  					Sum:   model.DurationSum{Us: int64(time.Millisecond) / 1e3},
   703  				},
   704  			},
   705  		},
   706  		OTel: &model.OTel{
   707  			SpanKind: "MESSAGING",
   708  			Attributes: map[string]interface{}{
   709  				"messaging.system": "messaging",
   710  			},
   711  		},
   712  	}
   713  }
   714  
   715  func fakeSpan() model.Span {
   716  	return model.Span{
   717  		Name:          "SELECT FROM bar",
   718  		ID:            model.SpanID{0, 1, 2, 3, 4, 5, 6, 7},
   719  		ParentID:      model.SpanID{0, 1, 2, 3, 4, 5, 6, 7},
   720  		TransactionID: model.SpanID{0, 1, 2, 3, 4, 5, 6, 7},
   721  		TraceID:       model.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
   722  		Timestamp:     model.Time(time.Unix(123, 0).UTC()),
   723  		Duration:      3,
   724  		Type:          "db.postgresql.query",
   725  		Context:       fakeDatabaseSpanContext(),
   726  		OTel: &model.OTel{
   727  			SpanKind: "MESSAGING",
   728  			Attributes: map[string]interface{}{
   729  				"messaging.system": "messaging",
   730  			},
   731  		},
   732  	}
   733  }
   734  
   735  func fakeDatabaseSpanContext() *model.SpanContext {
   736  	return &model.SpanContext{
   737  		Database: &model.DatabaseSpanContext{
   738  			Instance:  "wat",
   739  			Statement: `SELECT foo FROM bar WHERE baz LIKE 'qu%x'`,
   740  			Type:      "sql",
   741  			User:      "barb",
   742  		},
   743  	}
   744  }
   745  
   746  func fakeHTTPSpanContext() *model.SpanContext {
   747  	return &model.SpanContext{
   748  		HTTP: &model.HTTPSpanContext{
   749  			URL: mustParseURL("http://testing.invalid:8000/path?query#fragment"),
   750  		},
   751  	}
   752  }
   753  
   754  func fakeMetrics() *model.Metrics {
   755  	return &model.Metrics{
   756  		Timestamp: model.Time(time.Unix(123, 0).UTC()),
   757  		Labels:    model.StringMap{{Key: "foo", Value: "bar"}},
   758  		Samples: map[string]model.Metric{
   759  			"metric_one": {Value: 1024},
   760  			"metric_two": {Value: -66.6},
   761  		},
   762  	}
   763  }
   764  
   765  func fakeService() *model.Service {
   766  	return &model.Service{
   767  		Name:        "fake-service",
   768  		Version:     "1.0.0-rc1",
   769  		Environment: "dev",
   770  		Agent: &model.Agent{
   771  			Name:    "go",
   772  			Version: "0.1.0",
   773  		},
   774  		Framework: &model.Framework{
   775  			Name:    "gin",
   776  			Version: "1.0",
   777  		},
   778  		Language: &model.Language{
   779  			Name:    "go",
   780  			Version: "1.10",
   781  		},
   782  		Runtime: &model.Runtime{
   783  			Name:    "go",
   784  			Version: "gc 1.10",
   785  		},
   786  	}
   787  }
   788  
   789  func fakeSystem() *model.System {
   790  	return &model.System{
   791  		Architecture: "x86_64",
   792  		Hostname:     "host.example",
   793  		Platform:     "linux",
   794  	}
   795  }
   796  
   797  func fakeProcess() *model.Process {
   798  	ppid := 1
   799  	return &model.Process{
   800  		Pid:   1234,
   801  		Ppid:  &ppid,
   802  		Title: "my-fake-service",
   803  		Argv:  []string{"my-fake-service", "-f", "config.yml"},
   804  	}
   805  }
   806  
   807  func fakeCloud() *model.Cloud {
   808  	return &model.Cloud{
   809  		Provider:         "zeus",
   810  		Region:           "troposphere",
   811  		AvailabilityZone: "torrid",
   812  		Instance: &model.CloudInstance{
   813  			ID:   "instance_id",
   814  			Name: "instance_name",
   815  		},
   816  		Machine: &model.CloudMachine{
   817  			Type: "machine_type",
   818  		},
   819  		Account: &model.CloudAccount{
   820  			ID:   "account_id",
   821  			Name: "account_name",
   822  		},
   823  		Project: &model.CloudProject{
   824  			ID:   "project_id",
   825  			Name: "project_name",
   826  		},
   827  	}
   828  }
   829  
   830  func mustParseURL(s string) *url.URL {
   831  	u, err := url.Parse(s)
   832  	if err != nil {
   833  		panic(err)
   834  	}
   835  	return u
   836  }
   837  
   838  func newUint64(v uint64) *uint64 {
   839  	return &v
   840  }
   841  
   842  func newFloat64(v float64) *float64 {
   843  	return &v
   844  }
   845  
   846  func mustUnmarshalJSON(w fastjson.Writer) interface{} {
   847  	var out interface{}
   848  	err := json.Unmarshal(w.Bytes(), &out)
   849  	if err != nil {
   850  		panic(err)
   851  	}
   852  	return out
   853  }