From 1d2b22545b422dceb218ffe81a65a5d41ef70141 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 10 Feb 2026 07:39:06 +0100 Subject: [PATCH] Add tests --- Directory.Build.props | 1 + .../Types/HostingScheme.cs | 10 ++- .../Owin/AspNetCoreSelfHost.cs | 52 +++++++++--- .../Owin/HostUrlOptions.cs | 50 +++++++---- src/WireMock.Net.Minimal/Util/PortUtils.cs | 13 +-- .../WebSockets/WebSocketBuilder.cs | 6 +- .../WebSockets/WebSocketIntegrationTests.cs | 83 ++++++++++--------- 7 files changed, 133 insertions(+), 82 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 86955e73..1df62db2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,6 +15,7 @@ PackageReadme.md 12 enable + enable diff --git a/src/WireMock.Net.Abstractions/Types/HostingScheme.cs b/src/WireMock.Net.Abstractions/Types/HostingScheme.cs index beaaa11f..6ee89b9f 100644 --- a/src/WireMock.Net.Abstractions/Types/HostingScheme.cs +++ b/src/WireMock.Net.Abstractions/Types/HostingScheme.cs @@ -1,7 +1,5 @@ // Copyright © WireMock.Net -using System; - namespace WireMock.Types; [Flags] @@ -13,5 +11,11 @@ public enum HostingScheme Https = 0x2, - HttpAndHttps = Http | Https + HttpAndHttps = Http | Https, + + Ws = 0x4, + + Wss = 0x8, + + WsAndWss = Ws | Wss } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs index bb696c2d..ae5b1c6f 100644 --- a/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net.Minimal/Owin/AspNetCoreSelfHost.cs @@ -1,10 +1,5 @@ // Copyright © WireMock.Net -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -27,9 +22,9 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost public bool IsStarted { get; private set; } - public List Urls { get; } = new(); + public List Urls { get; } = []; - public List Ports { get; } = new(); + public List Ports { get; } = []; public Exception? RunningException { get; private set; } @@ -120,14 +115,42 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost { var addresses = _host.ServerFeatures .Get()! - .Addresses; + .Addresses + .ToArray(); - foreach (var address in addresses) + if (_urlOptions.Urls == null) { - Urls.Add(address.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost")); + foreach (var address in addresses) + { + PortUtils.TryExtract(address, out _, out _, out var scheme, out var host, out var port); - PortUtils.TryExtract(address, out _, out _, out _, out _, out var port); - Ports.Add(port); + var replacedHost = ReplaceHostWithLocalhost(host!); + var newUrl = $"{scheme}://{replacedHost}:{port}"; + Urls.Add(newUrl); + Ports.Add(port); + } + } + else + { + var urlOptions = _urlOptions?.Urls?.ToArray() ?? []; + + for (int i = 0; i < urlOptions.Length; i++) + { + PortUtils.TryExtract(urlOptions[i], out _, out _, out var originalScheme, out _, out _); + if (originalScheme!.StartsWith("grpc", StringComparison.OrdinalIgnoreCase)) + { + // Always replace "grpc" with "http" in the scheme because GrpcChannel needs http or https. + originalScheme = originalScheme.Replace("grpc", "http", StringComparison.OrdinalIgnoreCase); + } + + PortUtils.TryExtract(addresses[i], out _, out _, out _, out var realHost, out var realPort); + + var replacedHost = ReplaceHostWithLocalhost(realHost!); + var newUrl = $"{originalScheme}://{replacedHost}:{realPort}"; + + Urls.Add(newUrl); + Ports.Add(realPort); + } } IsStarted = true; @@ -159,4 +182,9 @@ internal partial class AspNetCoreSelfHost : IOwinSelfHost IsStarted = false; return _host.StopAsync(); } + + private static string ReplaceHostWithLocalhost(string host) + { + return host.Replace("0.0.0.0", "localhost").Replace("[::]", "localhost"); + } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs b/src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs index 055e14d5..6e06aaa3 100644 --- a/src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs +++ b/src/WireMock.Net.Minimal/Owin/HostUrlOptions.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System.Collections.Generic; using WireMock.Types; using WireMock.Util; @@ -23,20 +22,34 @@ internal class HostUrlOptions var list = new List(); if (Urls == null) { - if (HostingScheme is HostingScheme.Http or HostingScheme.Https) + if (HostingScheme is not HostingScheme.None) { - var port = Port > 0 ? Port.Value : FindFreeTcpPort(); - var scheme = HostingScheme == HostingScheme.Https ? "https" : "http"; - list.Add(new HostUrlDetails { IsHttps = HostingScheme == HostingScheme.Https, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port }); + var scheme = GetSchemeAsString(HostingScheme); + var port = Port > 0 ? Port.Value : 0; + var isHttps = HostingScheme == HostingScheme.Https || HostingScheme == HostingScheme.Wss; + list.Add(new HostUrlDetails { IsHttps = isHttps, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port }); } if (HostingScheme == HostingScheme.HttpAndHttps) { - var httpPort = Port > 0 ? Port.Value : FindFreeTcpPort(); - list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"http://{Star}:{httpPort}", Scheme = "http", Host = Star, Port = httpPort }); + var port = Port > 0 ? Port.Value : 0; + var scheme = GetSchemeAsString(HostingScheme.Http); + list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port }); - var httpsPort = FindFreeTcpPort(); // In this scenario, always get a free port for https. - list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"https://{Star}:{httpsPort}", Scheme = "https", Host = Star, Port = httpsPort }); + var securePort = 0; // In this scenario, always get a free port for https. + var secureScheme = GetSchemeAsString(HostingScheme.Https); + list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"{secureScheme}://{Star}:{securePort}", Scheme = secureScheme, Host = Star, Port = securePort }); + } + + if (HostingScheme == HostingScheme.WsAndWss) + { + var port = Port > 0 ? Port.Value : 0; + var scheme = GetSchemeAsString(HostingScheme.Ws); + list.Add(new HostUrlDetails { IsHttps = false, IsHttp2 = UseHttp2 == true, Url = $"{scheme}://{Star}:{port}", Scheme = scheme, Host = Star, Port = port }); + + var securePort = 0; // In this scenario, always get a free port for https. + var secureScheme = GetSchemeAsString(HostingScheme.Wss); + list.Add(new HostUrlDetails { IsHttps = true, IsHttp2 = UseHttp2 == true, Url = $"{secureScheme}://{Star}:{securePort}", Scheme = secureScheme, Host = Star, Port = securePort }); } } else @@ -53,12 +66,19 @@ internal class HostUrlOptions return list; } - private static int FindFreeTcpPort() + private string GetSchemeAsString(HostingScheme scheme) { -//#if USE_ASPNETCORE || NETSTANDARD2_0 || NETSTANDARD2_1 - return 0; -//#else - //return PortUtils.FindFreeTcpPort(); -//#endif + return scheme switch + { + HostingScheme.Http => "http", + HostingScheme.Https => "https", + HostingScheme.HttpAndHttps => "http", // Default to http when both are specified, since the https URL will be added separately with a free port. + + HostingScheme.Ws => "ws", + HostingScheme.Wss => "wss", + HostingScheme.WsAndWss => "ws", // Default to ws when both are specified, since the wss URL will be added separately with a free port. + + _ => throw new NotSupportedException($"Unsupported hosting scheme: {HostingScheme}") + }; } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Util/PortUtils.cs b/src/WireMock.Net.Minimal/Util/PortUtils.cs index a4465725..ef8e3560 100644 --- a/src/WireMock.Net.Minimal/Util/PortUtils.cs +++ b/src/WireMock.Net.Minimal/Util/PortUtils.cs @@ -1,9 +1,6 @@ // Copyright © WireMock.Net -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Net; using System.Net.Sockets; using System.Text.RegularExpressions; @@ -16,7 +13,7 @@ namespace WireMock.Util; /// internal static class PortUtils { - private static readonly Regex UrlDetailsRegex = new(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled, RegexConstants.DefaultTimeout); + private static readonly Regex UrlDetailsRegex = new(@"^((?\w+)://)(?[^/]+?):(?\d+)\/?$", RegexOptions.Compiled, RegexConstants.DefaultTimeout); /// /// Finds a random, free port to be listened on. @@ -37,9 +34,7 @@ internal static class PortUtils } finally { -//#if !NETSTANDARD1_3 portSocket.Close(); -//#endif portSocket.Dispose(); } } @@ -75,9 +70,7 @@ internal static class PortUtils { foreach (var socket in sockets) { -//#if !NETSTANDARD1_3 socket.Close(); -//#endif socket.Dispose(); } } @@ -97,8 +90,8 @@ internal static class PortUtils var match = UrlDetailsRegex.Match(url); if (match.Success) { - scheme = match.Groups["proto"].Value; - isHttps = scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase) || scheme.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase); + scheme = match.Groups["scheme"].Value; + isHttps = scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase) || scheme.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase) || scheme.StartsWith("wss", StringComparison.OrdinalIgnoreCase); isHttp2 = scheme.StartsWith("grpc", StringComparison.OrdinalIgnoreCase); host = match.Groups["host"].Value; diff --git a/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs b/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs index 6f24ca80..7dc8a8e8 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs @@ -64,16 +64,14 @@ internal class WebSocketBuilder : IWebSocketBuilder return this; } - public IWebSocketBuilder WithMessageHandler( - Func handler) + public IWebSocketBuilder WithMessageHandler(Func handler) { MessageHandler = Guard.NotNull(handler); IsEcho = false; // Disable echo if custom handler is set return this; } - public IWebSocketBuilder WithMessageSequence( - Action configure) + public IWebSocketBuilder WithMessageSequence(Action configure) { var sequenceBuilder = new WebSocketMessageSequenceBuilder(); configure(sequenceBuilder); diff --git a/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs b/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs index 273e0507..cbb2448a 100644 --- a/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs +++ b/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs @@ -1,12 +1,7 @@ // Copyright © WireMock.Net -using System; -using System.Collections.Generic; -using System.Linq; using System.Net.WebSockets; using System.Text; -using System.Threading; -using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json.Linq; using WireMock.Net.Xunit; @@ -14,7 +9,6 @@ using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; using WireMock.Settings; -using Xunit; using Xunit.Abstractions; namespace WireMock.Net.Tests.WebSockets; @@ -34,7 +28,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -43,14 +38,13 @@ public class WebSocketIntegrationTests .WithWebSocketUpgrade() ) .RespondWith(Response.Create() - .WithHeader("x", "y") .WithWebSocket(ws => ws .WithEcho() ) ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/echo"); + var uri = new Uri($"{server.Url!}/ws/echo"); // Act await client.ConnectAsync(uri, CancellationToken.None); @@ -78,7 +72,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -91,7 +86,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/echo"); + var uri = new Uri($"{server.Url!}/ws/echo"); await client.ConnectAsync(uri, CancellationToken.None); var testMessages = new[] { "Hello", "World", "WebSocket", "Test" }; @@ -118,7 +113,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -131,7 +127,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/echo"); + var uri = new Uri($"{server.Url!}/ws/echo"); await client.ConnectAsync(uri, CancellationToken.None); var testData = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; @@ -157,7 +153,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -170,7 +167,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/echo"); + var uri = new Uri($"{server.Url!}/ws/echo"); await client.ConnectAsync(uri, CancellationToken.None); // Act @@ -192,7 +189,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -218,7 +216,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/chat"); + var uri = new Uri($"{server.Url!}/ws/chat"); await client.ConnectAsync(uri, CancellationToken.None); // Act @@ -244,7 +242,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -288,7 +287,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/chat"); + var uri = new Uri($"{server.Url!}/ws/chat"); await client.ConnectAsync(uri, CancellationToken.None); var commands = new (string, Action)[] @@ -322,7 +321,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -348,7 +348,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/json"); + var uri = new Uri($"{server.Url!}/ws/json"); await client.ConnectAsync(uri, CancellationToken.None); // Act @@ -362,7 +362,7 @@ public class WebSocketIntegrationTests // Assert result.MessageType.Should().Be(WebSocketMessageType.Text); - + var json = JObject.Parse(received); json["message"]!.ToString().Should().Be(testMessage); json["length"]!.Value().Should().Be(testMessage.Length); @@ -378,7 +378,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -404,7 +405,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/json"); + var uri = new Uri($"{server.Url!}/ws/json"); await client.ConnectAsync(uri, CancellationToken.None); var testMessages = new[] { "First", "Second", "Third" }; @@ -433,7 +434,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); server @@ -470,7 +472,7 @@ public class WebSocketIntegrationTests ); using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/json"); + var uri = new Uri($"{server.Url!}/ws/json"); await client.ConnectAsync(uri, CancellationToken.None); // Act @@ -499,7 +501,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); var broadcastMappingGuid = Guid.NewGuid(); @@ -533,7 +536,7 @@ public class WebSocketIntegrationTests using var client2 = new ClientWebSocket(); using var client3 = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/broadcast"); + var uri = new Uri($"{server.Url!}/ws/broadcast"); await client1.ConnectAsync(uri, CancellationToken.None); await client2.ConnectAsync(uri, CancellationToken.None); @@ -579,7 +582,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); var broadcastMappingGuid = Guid.NewGuid(); @@ -606,7 +610,7 @@ public class WebSocketIntegrationTests using var client1 = new ClientWebSocket(); using var client2 = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/broadcast"); + var uri = new Uri($"{server.Url!}/ws/broadcast"); await client1.ConnectAsync(uri, CancellationToken.None); await client2.ConnectAsync(uri, CancellationToken.None); @@ -639,7 +643,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); var broadcastMappingGuid = Guid.NewGuid(); @@ -673,7 +678,7 @@ public class WebSocketIntegrationTests using var client1 = new ClientWebSocket(); using var client2 = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/broadcast-json"); + var uri = new Uri($"{server.Url!}/ws/broadcast-json"); await client1.ConnectAsync(uri, CancellationToken.None); await client2.ConnectAsync(uri, CancellationToken.None); @@ -717,7 +722,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); var broadcastMappingGuid = Guid.NewGuid(); @@ -746,7 +752,7 @@ public class WebSocketIntegrationTests using var client1 = new ClientWebSocket(); using var client2 = new ClientWebSocket(); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/broadcast"); + var uri = new Uri($"{server.Url!}/ws/broadcast"); await client1.ConnectAsync(uri, CancellationToken.None); await client2.ConnectAsync(uri, CancellationToken.None); @@ -784,7 +790,8 @@ public class WebSocketIntegrationTests // Arrange using var server = WireMockServer.Start(new WireMockServerSettings { - Logger = new TestOutputHelperWireMockLogger(_output) + Logger = new TestOutputHelperWireMockLogger(_output), + Urls = ["ws://localhost:0"] }); var broadcastMappingGuid = Guid.NewGuid(); @@ -808,8 +815,8 @@ public class WebSocketIntegrationTests ) ); - var uri = new Uri($"{server.Urls[0].Replace("http://", "ws://")}/ws/broadcast"); - const int clientCount = 5; + var uri = new Uri($"{server.Url!}/ws/broadcast"); + const int clientCount = 3; var clients = new List(); try @@ -822,7 +829,7 @@ public class WebSocketIntegrationTests clients.Add(client); } - await Task.Delay(200); // Give time for all connections to register + await Task.Delay(100); // Give time for all connections to register // Act - Send message from first client var testMessage = "Mass broadcast";