// Copyright © WireMock.Net using System.Diagnostics; using System.Net.WebSockets; using Moq; using WireMock.Logging; using WireMock.Matchers.Request; using WireMock.Models; using WireMock.Owin.ActivityTracing; using WireMock.Settings; using WireMock.Util; using WireMock.WebSockets; namespace WireMock.Net.Tests.Owin.ActivityTracing; public class WireMockActivitySourceTests : IDisposable { private readonly ActivityListener _activityListener; public WireMockActivitySourceTests() { // Set up ActivityListener for tests _activityListener = new ActivityListener { ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData }; ActivitySource.AddActivityListener(_activityListener); } public void Dispose() { _activityListener?.Dispose(); } [Fact] public void EnrichWithRequest_ShouldSetRequestTagsAndBody_WhenEnabled() { // Arrange using var activity = new Activity("test").Start(); var request = new RequestMessage( new UrlDetails("http://localhost/api/orders"), "POST", "127.0.0.1", new BodyData { BodyAsString = "payload" }); var options = new ActivityTracingOptions { RecordRequestBody = true }; // Act WireMockActivitySource.EnrichWithRequest(activity, request, options); // Assert activity.GetTagItem(WireMockSemanticConventions.HttpMethod).Should().Be("POST"); activity.GetTagItem(WireMockSemanticConventions.HttpUrl).Should().Be("http://localhost/api/orders"); activity.GetTagItem(WireMockSemanticConventions.HttpPath).Should().Be("/api/orders"); activity.GetTagItem(WireMockSemanticConventions.HttpHost).Should().Be("localhost"); activity.GetTagItem(WireMockSemanticConventions.ClientAddress).Should().Be("127.0.0.1"); activity.GetTagItem(WireMockSemanticConventions.RequestBody).Should().Be("payload"); } [Fact] public void EnrichWithResponse_ShouldSetStatusAndBody_WhenEnabled() { // Arrange using var activity = new Activity("test").Start(); var response = new ResponseMessage { StatusCode = 200, BodyData = new BodyData { BodyAsString = "ok" } }; var options = new ActivityTracingOptions { RecordResponseBody = true }; // Act WireMockActivitySource.EnrichWithResponse(activity, response, options); // Assert activity.GetTagItem(WireMockSemanticConventions.HttpStatusCode).Should().Be(200); activity.GetTagItem(WireMockSemanticConventions.OtelStatusCode).Should().Be("OK"); activity.GetTagItem(WireMockSemanticConventions.ResponseBody).Should().Be("ok"); } [Fact] public void EnrichWithResponse_ShouldSetErrorStatus_ForNonSuccess() { // Arrange using var activity = new Activity("test").Start(); var response = new ResponseMessage { StatusCode = 500 }; // Act WireMockActivitySource.EnrichWithResponse(activity, response, new ActivityTracingOptions()); // Assert activity.GetTagItem(WireMockSemanticConventions.HttpStatusCode).Should().Be(500); activity.GetTagItem(WireMockSemanticConventions.OtelStatusCode).Should().Be("ERROR"); } [Fact] public void EnrichWithRequest_ShouldNotRecordBody_WhenDisabled() { // Arrange using var activity = new Activity("test").Start(); var request = new RequestMessage( new UrlDetails("http://localhost/api/orders"), "POST", "127.0.0.1", new BodyData { BodyAsString = "payload" }); var options = new ActivityTracingOptions { RecordRequestBody = false }; // Act WireMockActivitySource.EnrichWithRequest(activity, request, options); // Assert activity.GetTagItem(WireMockSemanticConventions.RequestBody).Should().BeNull(); } [Fact] public void EnrichWithResponse_ShouldNotRecordBody_WhenDisabled() { // Arrange using var activity = new Activity("test").Start(); var response = new ResponseMessage { StatusCode = 200, BodyData = new BodyData { BodyAsString = "ok" } }; var options = new ActivityTracingOptions { RecordResponseBody = false }; // Act WireMockActivitySource.EnrichWithResponse(activity, response, options); // Assert activity.GetTagItem(WireMockSemanticConventions.ResponseBody).Should().BeNull(); } [Fact] public void EnrichWithLogEntry_ShouldSkipMatchDetails_WhenDisabled() { // Arrange using var activity = new Activity("test").Start(); var request = new RequestMessage( new UrlDetails("http://localhost/api/orders"), "GET", "127.0.0.1"); var response = new ResponseMessage { StatusCode = 200 }; var matchResult = new Mock(); matchResult.SetupGet(r => r.IsPerfectMatch).Returns(true); matchResult.SetupGet(r => r.TotalScore).Returns(1.0); var logEntry = new LogEntry { Guid = Guid.NewGuid(), RequestMessage = request, ResponseMessage = response, RequestMatchResult = matchResult.Object, MappingGuid = Guid.NewGuid(), MappingTitle = "test-mapping" }; var options = new ActivityTracingOptions { RecordMatchDetails = false }; // Act WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, options); // Assert activity.GetTagItem(WireMockSemanticConventions.RequestGuid).Should().Be(logEntry.Guid.ToString()); activity.Tags.Should().NotContain(tag => tag.Key == WireMockSemanticConventions.MappingGuid); activity.Tags.Should().NotContain(tag => tag.Key == WireMockSemanticConventions.MappingTitle); activity.Tags.Should().NotContain(tag => tag.Key == WireMockSemanticConventions.MatchScore); } [Fact] public void RecordException_ShouldSetExceptionTags() { // Arrange using var activity = new Activity("test").Start(); var exception = new InvalidOperationException("Yes, Rico; Kaboom."); // Act WireMockActivitySource.RecordException(activity, exception); // Assert activity.GetTagItem(WireMockSemanticConventions.OtelStatusCode).Should().Be("ERROR"); activity.GetTagItem("otel.status_description").Should().Be("Yes, Rico; Kaboom."); activity.GetTagItem("exception.type").Should().Be(typeof(InvalidOperationException).FullName); activity.GetTagItem("exception.message").Should().Be("Yes, Rico; Kaboom."); activity.GetTagItem("exception.stacktrace").Should().NotBeNull(); } [Fact] public void StartRequestActivity_ShouldCreateActivity_WithCorrectDisplayName() { // Arrange var requestMethod = "POST"; var requestPath = "/api/users"; // Act using var activity = WireMockActivitySource.StartRequestActivity(requestMethod, requestPath); // Assert activity.Should().NotBeNull(); activity.DisplayName.Should().Be("WireMock POST /api/users"); activity.Kind.Should().Be(ActivityKind.Server); } [Fact] public void StartWebSocketMessageActivity_ShouldCreateActivity_WithCorrectName() { // Arrange var mappingGuid = Guid.NewGuid(); var direction = WebSocketMessageDirection.Receive; // Act using var activity = WireMockActivitySource.StartWebSocketMessageActivity(direction, mappingGuid); // Assert activity.Should().NotBeNull(); activity.DisplayName.Should().Be("WireMock WebSocket receive"); activity.Kind.Should().Be(ActivityKind.Server); } [Fact] public void StartWebSocketMessageActivity_ShouldSetMappingGuidTag() { // Arrange var mappingGuid = Guid.NewGuid(); var direction = WebSocketMessageDirection.Send; // Act using var activity = WireMockActivitySource.StartWebSocketMessageActivity(direction, mappingGuid); // Assert activity.Should().NotBeNull(); activity.GetTagItem(WireMockSemanticConventions.MappingGuid).Should().Be(mappingGuid.ToString()); } [Fact] public void StartWebSocketMessageActivity_ShouldCreateActivityForSendDirection() { // Arrange var mappingGuid = Guid.NewGuid(); var direction = WebSocketMessageDirection.Send; // Act using var activity = WireMockActivitySource.StartWebSocketMessageActivity(direction, mappingGuid); // Assert activity.Should().NotBeNull(); activity.DisplayName.Should().Be("WireMock WebSocket send"); } [Fact] public void StartWebSocketMessageActivity_ShouldCreateActivityWithListenerConfigured() { // Arrange var mappingGuid = Guid.NewGuid(); var direction = WebSocketMessageDirection.Receive; // Act - ActivityListener is configured in test constructor using var activity = WireMockActivitySource.StartWebSocketMessageActivity(direction, mappingGuid); // Assert - activity should be created since listener is active activity.Should().NotBeNull(); activity.DisplayName.Should().Be("WireMock WebSocket receive"); activity.GetTagItem(WireMockSemanticConventions.MappingGuid).Should().Be(mappingGuid.ToString()); } [Fact] public void EnrichWithWebSocketMessage_ShouldSetMessageTypeTag() { // Arrange using var activity = new Activity("test").Start(); var messageType = WebSocketMessageType.Text; // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, messageType, messageSize: 100, endOfMessage: true ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageType).Should().Be("Text"); } [Fact] public void EnrichWithWebSocketMessage_ShouldSetMessageSizeTag() { // Arrange using var activity = new Activity("test").Start(); // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Binary, messageSize: 256, endOfMessage: true ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageSize).Should().Be(256); } [Fact] public void EnrichWithWebSocketMessage_ShouldSetEndOfMessageTag() { // Arrange using var activity = new Activity("test").Start(); // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Text, messageSize: 50, endOfMessage: false ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketEndOfMessage).Should().Be(false); } [Fact] public void EnrichWithWebSocketMessage_ShouldSetOkStatus() { // Arrange using var activity = new Activity("test").Start(); // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Text, messageSize: 100, endOfMessage: true ); // Assert activity.GetTagItem(WireMockSemanticConventions.OtelStatusCode).Should().Be("OK"); } [Fact] public void EnrichWithWebSocketMessage_ShouldRecordTextContent_WhenEnabled() { // Arrange using var activity = new Activity("test").Start(); var options = new ActivityTracingOptions { RecordRequestBody = true }; var textContent = "Hello WebSocket"; // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Text, messageSize: textContent.Length, endOfMessage: true, textContent: textContent, options: options ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageContent).Should().Be(textContent); } [Fact] public void EnrichWithWebSocketMessage_ShouldNotRecordTextContent_WhenDisabled() { // Arrange using var activity = new Activity("test").Start(); var options = new ActivityTracingOptions { RecordRequestBody = false }; // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Text, messageSize: 100, endOfMessage: true, textContent: "Hello WebSocket", options: options ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageContent).Should().BeNull(); } [Fact] public void EnrichWithWebSocketMessage_ShouldNotRecordBinaryContent() { // Arrange using var activity = new Activity("test").Start(); var options = new ActivityTracingOptions { RecordRequestBody = true }; // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Binary, messageSize: 100, endOfMessage: true, textContent: "should not record", options: options ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageContent).Should().BeNull(); } [Fact] public void EnrichWithWebSocketMessage_ShouldHandleNullActivity() { // Arrange & Act - should not throw WireMockActivitySource.EnrichWithWebSocketMessage( null, WebSocketMessageType.Text, messageSize: 100, endOfMessage: true ); // Assert - no exception thrown } [Fact] public void EnrichWithWebSocketMessage_ShouldHandleClosedMessageType() { // Arrange using var activity = new Activity("test").Start(); // Act WireMockActivitySource.EnrichWithWebSocketMessage( activity, WebSocketMessageType.Close, messageSize: 0, endOfMessage: true ); // Assert activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageType).Should().Be("Close"); activity.GetTagItem(WireMockSemanticConventions.WebSocketMessageSize).Should().Be(0); } }