// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/httpconvtest_test.go.tmpl

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package semconv_test

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/metric"
	"go.opentelemetry.io/otel/sdk/instrumentation"
	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/metric/metricdata"
	"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)

func TestNewTraceRequest(t *testing.T) {
	serv := semconv.NewHTTPServer(nil)
	want := func(req testServerReq) []attribute.KeyValue {
		return []attribute.KeyValue{
			attribute.String("http.request.method", "GET"),
			attribute.String("url.scheme", "http"),
			attribute.String("server.address", req.hostname),
			attribute.Int("server.port", req.serverPort),
			attribute.String("network.peer.address", req.peerAddr),
			attribute.Int("network.peer.port", req.peerPort),
			attribute.String("user_agent.original", "Go-http-client/1.1"),
			attribute.String("client.address", req.clientIP),
			attribute.String("network.protocol.version", "1.1"),
			attribute.String("url.path", "/"),
		}
	}
	testTraceRequest(t, serv, want)
}

func TestNewServerRecordMetrics(t *testing.T) {
	oldAttrs := attribute.NewSet(
		attribute.String("http.scheme", "http"),
		attribute.String("http.method", "POST"),
		attribute.Int64("http.status_code", 301),
		attribute.String("key", "value"),
		attribute.String("net.host.name", "stuff"),
		attribute.String("net.protocol.name", "http"),
		attribute.String("net.protocol.version", "1.1"),
	)

	currAttrs := attribute.NewSet(
		attribute.String("http.request.method", "POST"),
		attribute.Int64("http.response.status_code", 301),
		attribute.String("key", "value"),
		attribute.String("network.protocol.name", "http"),
		attribute.String("network.protocol.version", "1.1"),
		attribute.String("server.address", "stuff"),
		attribute.String("url.scheme", "http"),
	)

	// the HTTPServer version
	expectedCurrentScopeMetric := metricdata.ScopeMetrics{
		Scope: instrumentation.Scope{
			Name: "test",
		},
		Metrics: []metricdata.Metrics{
			{
				Name:        "http.server.request.body.size",
				Description: "Size of HTTP server request bodies.",
				Unit:        "By",
				Data: metricdata.Histogram[int64]{
					Temporality: metricdata.CumulativeTemporality,
					DataPoints: []metricdata.HistogramDataPoint[int64]{
						{
							Attributes: currAttrs,
						},
					},
				},
			},
			{
				Name:        "http.server.response.body.size",
				Description: "Size of HTTP server response bodies.",
				Unit:        "By",
				Data: metricdata.Histogram[int64]{
					Temporality: metricdata.CumulativeTemporality,
					DataPoints: []metricdata.HistogramDataPoint[int64]{
						{
							Attributes: currAttrs,
						},
					},
				},
			},
			{
				Name:        "http.server.request.duration",
				Description: "Duration of HTTP server requests.",
				Unit:        "s",
				Data: metricdata.Histogram[float64]{
					Temporality: metricdata.CumulativeTemporality,
					DataPoints: []metricdata.HistogramDataPoint[float64]{
						{
							Attributes: currAttrs,
						},
					},
				},
			},
		},
	}

	// The OldHTTPServer version
	expectedOldScopeMetric := expectedCurrentScopeMetric
	expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{
		{
			Name:        "http.server.request.size",
			Description: "Measures the size of HTTP request messages.",
			Unit:        "By",
			Data: metricdata.Sum[int64]{
				Temporality: metricdata.CumulativeTemporality,
				IsMonotonic: true,
				DataPoints: []metricdata.DataPoint[int64]{
					{
						Attributes: oldAttrs,
					},
				},
			},
		},
		{
			Name:        "http.server.response.size",
			Description: "Measures the size of HTTP response messages.",
			Unit:        "By",
			Data: metricdata.Sum[int64]{
				Temporality: metricdata.CumulativeTemporality,
				IsMonotonic: true,
				DataPoints: []metricdata.DataPoint[int64]{
					{
						Attributes: oldAttrs,
					},
				},
			},
		},
		{
			Name:        "http.server.duration",
			Description: "Measures the duration of inbound HTTP requests.",
			Unit:        "ms",
			Data: metricdata.Histogram[float64]{
				Temporality: metricdata.CumulativeTemporality,
				DataPoints: []metricdata.HistogramDataPoint[float64]{
					{
						Attributes: oldAttrs,
					},
				},
			},
		},
	}...)

	tests := []struct {
		name       string
		serverFunc func(metric.MeterProvider) semconv.HTTPServer
		wantFunc   func(t *testing.T, rm metricdata.ResourceMetrics)
	}{
		{
			name: "No Meter",
			serverFunc: func(metric.MeterProvider) semconv.HTTPServer {
				return semconv.NewHTTPServer(nil)
			},
			wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) {
				assert.Empty(t, rm.ScopeMetrics)
			},
		},
		{
			name: "With Meter",
			serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer {
				return semconv.NewHTTPServer(mp.Meter("test"))
			},
			wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) {
				require.Len(t, rm.ScopeMetrics, 1)

				// because of OldHTTPServer
				require.Len(t, rm.ScopeMetrics[0].Metrics, 3)
				metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reader := sdkmetric.NewManualReader()
			mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))

			server := tt.serverFunc(mp)
			req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "http://example.com", http.NoBody)
			assert.NoError(t, err)

			server.RecordMetrics(t.Context(), semconv.ServerMetricData{
				ServerName:   "stuff",
				ResponseSize: 200,
				MetricAttributes: semconv.MetricAttributes{
					Req:        req,
					StatusCode: 301,
					AdditionalAttributes: []attribute.KeyValue{
						attribute.String("key", "value"),
					},
				},
				MetricData: semconv.MetricData{
					RequestSize:     100,
					RequestDuration: 300 * time.Millisecond,
				},
			})

			rm := metricdata.ResourceMetrics{}
			require.NoError(t, reader.Collect(t.Context(), &rm))
			tt.wantFunc(t, rm)
		})
	}
}

func TestNewTraceResponse(t *testing.T) {
	testCases := []struct {
		name string
		resp semconv.ResponseTelemetry
		want []attribute.KeyValue
	}{
		{
			name: "empty",
			resp: semconv.ResponseTelemetry{},
			want: nil,
		},
		{
			name: "no errors",
			resp: semconv.ResponseTelemetry{
				StatusCode: 200,
				ReadBytes:  701,
				WriteBytes: 802,
			},
			want: []attribute.KeyValue{
				attribute.Int("http.request.body.size", 701),
				attribute.Int("http.response.body.size", 802),
				attribute.Int("http.response.status_code", 200),
			},
		},
		{
			name: "with errors",
			resp: semconv.ResponseTelemetry{
				StatusCode: 200,
				ReadBytes:  701,
				ReadError:  fmt.Errorf("read error"),
				WriteBytes: 802,
				WriteError: fmt.Errorf("write error"),
			},
			want: []attribute.KeyValue{
				attribute.Int("http.request.body.size", 701),
				attribute.Int("http.response.body.size", 802),
				attribute.Int("http.response.status_code", 200),
			},
		},
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			got := semconv.HTTPServer{}.ResponseTraceAttrs(tt.resp)
			assert.ElementsMatch(t, tt.want, got)
		})
	}
}

func TestNewTraceRequest_Client(t *testing.T) {
	body := strings.NewReader("Hello, world!")
	url := "https://example.com:8888/foo/bar?stuff=morestuff"
	req := httptest.NewRequestWithContext(t.Context(), "pOST", url, body)
	req.Header.Set("User-Agent", "go-test-agent")

	want := []attribute.KeyValue{
		attribute.String("http.request.method", "POST"),
		attribute.String("http.request.method_original", "pOST"),
		attribute.String("url.full", url),
		attribute.String("server.address", "example.com"),
		attribute.Int("server.port", 8888),
		attribute.String("network.protocol.version", "1.1"),
	}
	client := semconv.NewHTTPClient(nil)
	assert.ElementsMatch(t, want, client.RequestTraceAttrs(req))
}

func TestNewTraceResponse_Client(t *testing.T) {
	testcases := []struct {
		resp http.Response
		want []attribute.KeyValue
	}{
		{resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}},
		{resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}},
	}

	for _, tt := range testcases {
		client := semconv.NewHTTPClient(nil)
		assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp))
	}
}

func TestClientRequest(t *testing.T) {
	body := strings.NewReader("Hello, world!")
	url := "https://example.com:8888/foo/bar?stuff=morestuff"
	req := httptest.NewRequestWithContext(t.Context(), "pOST", url, body)
	req.Header.Set("User-Agent", "go-test-agent")

	want := []attribute.KeyValue{
		attribute.String("http.request.method", "POST"),
		attribute.String("http.request.method_original", "pOST"),
		attribute.String("url.full", url),
		attribute.String("server.address", "example.com"),
		attribute.Int("server.port", 8888),
		attribute.String("network.protocol.version", "1.1"),
	}
	got := semconv.HTTPClient{}.RequestTraceAttrs(req)
	assert.ElementsMatch(t, want, got)
}

func TestClientResponse(t *testing.T) {
	testcases := []struct {
		resp http.Response
		want []attribute.KeyValue
	}{
		{resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}},
		{resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}},
	}

	for _, tt := range testcases {
		got := semconv.HTTPClient{}.ResponseTraceAttrs(&tt.resp)
		assert.ElementsMatch(t, tt.want, got)
	}
}

func TestNewClientRecordMetrics(t *testing.T) {
	currAttrs := attribute.NewSet(
		attribute.String("http.request.method", "POST"),
		attribute.Int64("http.response.status_code", 301),
		attribute.String("network.protocol.name", "http"),
		attribute.String("network.protocol.version", "1.1"),
		attribute.String("server.address", "example.com"),
		attribute.String("url.scheme", "http"),
	)

	// the HTTPClient version
	expectedCurrentScopeMetric := metricdata.ScopeMetrics{
		Scope: instrumentation.Scope{
			Name: "test",
		},
		Metrics: []metricdata.Metrics{
			{
				Name:        "http.client.request.body.size",
				Description: "Size of HTTP client request bodies.",
				Unit:        "By",
				Data: metricdata.Histogram[int64]{
					Temporality: metricdata.CumulativeTemporality,
					DataPoints: []metricdata.HistogramDataPoint[int64]{
						{
							Attributes: currAttrs,
						},
					},
				},
			},
			{
				Name:        "http.client.request.duration",
				Description: "Duration of HTTP client requests.",
				Unit:        "s",
				Data: metricdata.Histogram[float64]{
					Temporality: metricdata.CumulativeTemporality,
					DataPoints: []metricdata.HistogramDataPoint[float64]{
						{
							Attributes: currAttrs,
						},
					},
				},
			},
		},
	}

	tests := []struct {
		name       string
		clientFunc func(metric.MeterProvider) semconv.HTTPClient
		wantFunc   func(t *testing.T, rm metricdata.ResourceMetrics)
	}{
		{
			name: "No environment variable set, and no Meter",
			clientFunc: func(metric.MeterProvider) semconv.HTTPClient {
				return semconv.NewHTTPClient(nil)
			},
			wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) {
				assert.Empty(t, rm.ScopeMetrics)
			},
		},
		{
			name: "With Meter",
			clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient {
				return semconv.NewHTTPClient(mp.Meter("test"))
			},
			wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) {
				require.Len(t, rm.ScopeMetrics, 1)

				require.Len(t, rm.ScopeMetrics[0].Metrics, 2)
				metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reader := sdkmetric.NewManualReader()
			mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))

			client := tt.clientFunc(mp)
			req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "http://example.com", http.NoBody)
			assert.NoError(t, err)

			client.RecordMetrics(t.Context(), semconv.MetricData{
				RequestSize:     100,
				RequestDuration: 300 * time.Millisecond,
			}, client.MetricOptions(semconv.MetricAttributes{
				Req:        req,
				StatusCode: 301,
			}))

			rm := metricdata.ResourceMetrics{}
			require.NoError(t, reader.Collect(t.Context(), &rm))
			tt.wantFunc(t, rm)
		})
	}
}

type customError struct{}

func (customError) Error() string {
	return "custom error"
}
