// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #if NETFRAMEWORK using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using Moq; using Newtonsoft.Json; using OpenTelemetry.Instrumentation.Http.Implementation; using OpenTelemetry.Tests; using OpenTelemetry.Trace; using Xunit; namespace OpenTelemetry.Instrumentation.Http.Tests { public partial class HttpWebRequestTests { public static IEnumerable TestData => HttpTestData.ReadTestCases(); [Theory] [MemberData(nameof(TestData))] public void HttpOutCallsAreCollectedSuccessfullyAsync(HttpTestData.HttpOutTestCase tc) { using var serverLifeTime = TestHttpServer.RunServer( (ctx) => { ctx.Response.StatusCode = tc.ResponseCode == 0 ? 200 : tc.ResponseCode; ctx.Response.OutputStream.Close(); }, out var host, out var port); var expectedResource = Resources.Resources.CreateServiceResource("test-service"); var activityProcessor = new Mock>(); using var shutdownSignal = Sdk.CreateTracerProviderBuilder() .SetResource(expectedResource) .AddProcessor(activityProcessor.Object) .AddHttpWebRequestInstrumentation(options => options.SetHttpFlavor = tc.SetHttpFlavor) .Build(); tc.Url = HttpTestData.NormalizeValues(tc.Url, host, port); try { var request = (HttpWebRequest)WebRequest.Create(tc.Url); request.Method = tc.Method; if (tc.Headers != null) { foreach (var header in tc.Headers) { request.Headers.Add(header.Key, header.Value); } } request.ContentLength = 0; using var response = (HttpWebResponse)request.GetResponse(); new StreamReader(response.GetResponseStream()).ReadToEnd(); } catch (Exception) { // test case can intentionally send request that will result in exception tc.ResponseExpected = false; } Assert.Equal(2, activityProcessor.Invocations.Count); // begin and end was called var activity = (Activity)activityProcessor.Invocations[1].Arguments[0]; ValidateHttpWebRequestActivity(activity, expectedResource, tc.ResponseExpected); Assert.Equal(tc.SpanName, activity.DisplayName); var d = new Dictionary() { { StatusCanonicalCode.Ok.ToString(), "OK" }, { StatusCanonicalCode.Cancelled.ToString(), "CANCELLED" }, { StatusCanonicalCode.Unknown.ToString(), "UNKNOWN" }, { StatusCanonicalCode.InvalidArgument.ToString(), "INVALID_ARGUMENT" }, { StatusCanonicalCode.DeadlineExceeded.ToString(), "DEADLINE_EXCEEDED" }, { StatusCanonicalCode.NotFound.ToString(), "NOT_FOUND" }, { StatusCanonicalCode.AlreadyExists.ToString(), "ALREADY_EXISTS" }, { StatusCanonicalCode.PermissionDenied.ToString(), "PERMISSION_DENIED" }, { StatusCanonicalCode.ResourceExhausted.ToString(), "RESOURCE_EXHAUSTED" }, { StatusCanonicalCode.FailedPrecondition.ToString(), "FAILED_PRECONDITION" }, { StatusCanonicalCode.Aborted.ToString(), "ABORTED" }, { StatusCanonicalCode.OutOfRange.ToString(), "OUT_OF_RANGE" }, { StatusCanonicalCode.Unimplemented.ToString(), "UNIMPLEMENTED" }, { StatusCanonicalCode.Internal.ToString(), "INTERNAL" }, { StatusCanonicalCode.Unavailable.ToString(), "UNAVAILABLE" }, { StatusCanonicalCode.DataLoss.ToString(), "DATA_LOSS" }, { StatusCanonicalCode.Unauthenticated.ToString(), "UNAUTHENTICATED" }, }; tc.SpanAttributes = tc.SpanAttributes.ToDictionary( x => x.Key, x => { if (x.Key == "http.flavor" && x.Value == "2.0") { return "1.1"; } return HttpTestData.NormalizeValues(x.Value, host, port); }); foreach (KeyValuePair tag in activity.TagObjects) { var tagValue = tag.Value.ToString(); if (!tc.SpanAttributes.TryGetValue(tag.Key, out string value)) { if (tag.Key == SpanAttributeConstants.StatusCodeKey) { Assert.Equal(tc.SpanStatus, d[tagValue]); continue; } if (tag.Key == SpanAttributeConstants.StatusDescriptionKey) { if (tc.SpanStatusHasDescription.HasValue) { Assert.Equal(tc.SpanStatusHasDescription.Value, !string.IsNullOrEmpty(tagValue)); } continue; } Assert.True(false, $"Tag {tag.Key} was not found in test data."); } Assert.Equal(value, tagValue); } } [Fact] public void DebugIndividualTestAsync() { var serializer = new JsonSerializer(); var input = serializer.Deserialize(new JsonTextReader(new StringReader(@" { ""name"": ""Http version attribute populated"", ""method"": ""GET"", ""url"": ""http://{host}:{port}/"", ""responseCode"": 200, ""spanName"": ""HTTP GET"", ""spanStatus"": ""OK"", ""spanKind"": ""Client"", ""setHttpFlavor"": true, ""spanAttributes"": { ""http.method"": ""GET"", ""http.host"": ""{host}:{port}"", ""http.flavor"": ""2.0"", ""http.status_code"": 200, ""http.url"": ""http://{host}:{port}/"" } } "))); this.HttpOutCallsAreCollectedSuccessfullyAsync(input); } private static void ValidateHttpWebRequestActivity(Activity activityToValidate, Resources.Resource expectedResource, bool responseExpected) { Assert.Equal(ActivityKind.Client, activityToValidate.Kind); Assert.Equal(expectedResource, activityToValidate.GetResource()); var request = activityToValidate.GetCustomProperty(HttpWebRequestActivitySource.RequestCustomPropertyName); Assert.NotNull(request); Assert.True(request is HttpWebRequest); if (responseExpected) { var response = activityToValidate.GetCustomProperty(HttpWebRequestActivitySource.ResponseCustomPropertyName); Assert.NotNull(response); Assert.True(response is HttpWebResponse); } } } } #endif