From 37201bd65c2e589763ecebae901b5804b8926d75 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 12 Feb 2026 18:22:59 +0100 Subject: [PATCH] ... --- .../WireMock.Net.WebSocketExamples/Program.cs | 45 +----- .../Response.WithWebSocket.cs | 5 +- .../WebSockets/WebSocketBuilder.cs | 143 +++++++----------- .../WebSockets/WebSocketConnectionRegistry.cs | 10 -- .../WebSockets/WebSocketMessageBuilder.cs | 31 +--- .../WebSockets/WebSocketMessagesBuilder.cs | 6 +- .../WebSockets/WireMockWebSocketContext.cs | 21 +-- .../Extensions/DictionaryExtensions.cs | 16 +- .../WebSockets/IWebSocketBuilder.cs | 9 -- .../WebSockets/IWebSocketContext.cs | 10 -- .../WebSockets/IWebSocketMessageBuilder.cs | 9 +- .../WebSockets/WebSocketIntegrationTests.cs | 114 +------------- 12 files changed, 93 insertions(+), 326 deletions(-) diff --git a/examples/WireMock.Net.WebSocketExamples/Program.cs b/examples/WireMock.Net.WebSocketExamples/Program.cs index 74083e9f..afb7ff43 100644 --- a/examples/WireMock.Net.WebSocketExamples/Program.cs +++ b/examples/WireMock.Net.WebSocketExamples/Program.cs @@ -20,7 +20,7 @@ public static class Program Console.WriteLine("Choose an example to run:"); Console.WriteLine("1. Echo Server"); Console.WriteLine("2. Custom Message Handler"); - Console.WriteLine("3. Broadcast Server"); + Console.WriteLine("3. ..."); Console.WriteLine("4. Scenario/State Machine"); Console.WriteLine("5. WebSocket Proxy"); Console.WriteLine("6. Multiple WebSocket Endpoints"); @@ -419,28 +419,6 @@ public static class Program ) ); - // Endpoint 3: JSON service - server - .Given(Request.Create() - .WithPath("/ws/json") - .WithWebSocketUpgrade() - ) - .RespondWith(Response.Create() - .WithWebSocket(ws => ws - .WithMessageHandler(async (msg, ctx) => - { - var response = new - { - timestamp = DateTime.UtcNow, - message = msg.Text, - length = msg.Text?.Length ?? 0, - type = msg.MessageType.ToString() - }; - await ctx.SendAsJsonAsync(response); - }) - ) - ); - // Endpoint 4: Protocol-specific server .Given(Request.Create() @@ -586,27 +564,6 @@ public static class Program }) ) ); - - // JSON endpoint - server - .Given(Request.Create() - .WithPath("/ws/json") - .WithWebSocketUpgrade() - ) - .RespondWith(Response.Create() - .WithWebSocket(ws => ws - .WithMessageHandler(async (msg, ctx) => - { - var response = new - { - timestamp = DateTime.UtcNow, - message = msg.Text, - connectionId = ctx.ConnectionId - }; - await ctx.SendAsJsonAsync(response); - }) - ) - ); } private static void SetupGameScenario(WireMockServer server) diff --git a/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithWebSocket.cs b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithWebSocket.cs index 93c14714..0f30eb27 100644 --- a/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithWebSocket.cs +++ b/src/WireMock.Net.Minimal/ResponseBuilders/Response.WithWebSocket.cs @@ -1,6 +1,5 @@ // Copyright © WireMock.Net -using System; using WireMock.Settings; using WireMock.WebSockets; @@ -18,7 +17,7 @@ public partial class Response /// public IResponseBuilder WithWebSocket(Action configure) { - var builder = new WebSocketBuilder(); + var builder = new WebSocketBuilder(this); configure(builder); WebSocketBuilder = builder; @@ -39,7 +38,7 @@ public partial class Response /// public IResponseBuilder WithWebSocketProxy(ProxyAndRecordSettings settings) { - var builder = new WebSocketBuilder(); + var builder = new WebSocketBuilder(this); builder.WithProxy(settings); WebSocketBuilder = builder; diff --git a/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs b/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs index 9dd03d15..2ab7ea09 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WebSocketBuilder.cs @@ -1,16 +1,15 @@ // Copyright © WireMock.Net using System.Net.WebSockets; -using Newtonsoft.Json; using Stef.Validation; using WireMock.Matchers; +using WireMock.ResponseBuilders; using WireMock.Settings; using WireMock.Transformers; -using WireMock.Types; namespace WireMock.WebSockets; -internal class WebSocketBuilder : IWebSocketBuilder +internal class WebSocketBuilder(Response response) : IWebSocketBuilder { private readonly List<(IMatcher matcher, List messages)> _conditionalMessages = []; @@ -42,17 +41,6 @@ internal class WebSocketBuilder : IWebSocketBuilder public TimeSpan? KeepAliveIntervalSeconds { get; private set; } /// - public bool UseTransformer { get; private set; } - - /// - public TransformerType TransformerType { get; private set; } - - /// - public bool UseTransformerForBodyAsFile { get; private set; } - - /// - public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; } - public IWebSocketBuilder WithAcceptProtocol(string protocol) { AcceptProtocol = Guard.NotNull(protocol); @@ -70,7 +58,7 @@ internal class WebSocketBuilder : IWebSocketBuilder Guard.NotNull(configure); var messageBuilder = new WebSocketMessageBuilder(); configure(messageBuilder); - + return WithMessageHandler(async (message, context) => { if (messageBuilder.Delay.HasValue) @@ -78,7 +66,7 @@ internal class WebSocketBuilder : IWebSocketBuilder await Task.Delay(messageBuilder.Delay.Value); } - await SendMessageAsync(this, context, messageBuilder, message); + await SendMessageAsync(context, messageBuilder, message); }); } @@ -97,7 +85,7 @@ internal class WebSocketBuilder : IWebSocketBuilder await Task.Delay(messageBuilder.Delay.Value); } - await SendMessageAsync(this, context, messageBuilder, message); + await SendMessageAsync(context, messageBuilder, message); } }); } @@ -166,18 +154,6 @@ internal class WebSocketBuilder : IWebSocketBuilder return this; } - public IWebSocketBuilder WithTransformer( - TransformerType transformerType = TransformerType.Handlebars, - bool useTransformerForBodyAsFile = false, - ReplaceNodeOptions transformerReplaceNodeOptions = ReplaceNodeOptions.EvaluateAndTryToConvert) - { - UseTransformer = true; - TransformerType = transformerType; - UseTransformerForBodyAsFile = useTransformerForBodyAsFile; - TransformerReplaceNodeOptions = transformerReplaceNodeOptions; - return this; - } - internal IWebSocketBuilder AddConditionalMessage(IMatcher matcher, WebSocketMessageBuilder messageBuilder) { _conditionalMessages.Add((matcher, new List { messageBuilder })); @@ -205,7 +181,7 @@ internal class WebSocketBuilder : IWebSocketBuilder foreach (var (matcher, messages) in _conditionalMessages) { // Try to match the message - if (await MatchMessageAsync(message, matcher) ) + if (await MatchMessageAsync(message, matcher)) { // Execute the corresponding messages foreach (var messageBuilder in messages) @@ -215,7 +191,7 @@ internal class WebSocketBuilder : IWebSocketBuilder await Task.Delay(messageBuilder.Delay.Value); } - await SendMessageAsync(this, context, messageBuilder, message); + await SendMessageAsync(context, messageBuilder, message); // If this message should close the connection, do it after sending if (messageBuilder.ShouldClose) @@ -237,6 +213,54 @@ internal class WebSocketBuilder : IWebSocketBuilder }); } + private async Task SendMessageAsync(IWebSocketContext context, WebSocketMessageBuilder messageBuilder, WebSocketMessage incomingMessage) + { + switch (messageBuilder.Type) + { + case WebSocketMessageType.Text: + var text = messageBuilder.MessageText!; + if (response.UseTransformer) + { + text = ApplyTransformer(context, incomingMessage, text); + } + await context.SendAsync(text); + break; + + case WebSocketMessageType.Binary: + await context.SendAsync(messageBuilder.MessageBytes!); + break; + } + } + + private string ApplyTransformer(IWebSocketContext context, WebSocketMessage incomingMessage, string text) + { + try + { + if (incomingMessage == null) + { + // No incoming message, can't apply transformer + return text; + } + + var transformer = TransformerFactory.Create(response.TransformerType, context.Mapping.Settings); + + var model = new WebSocketTransformModel + { + Mapping = context.Mapping, + Request = context.RequestMessage, + Message = incomingMessage, + Data = incomingMessage.MessageType == WebSocketMessageType.Text ? incomingMessage.Text : null + }; + + return transformer.Transform(text, model); + } + catch + { + // If transformation fails, return original text + return text; + } + } + private static async Task MatchMessageAsync(WebSocketMessage message, IMatcher matcher) { if (message.MessageType == WebSocketMessageType.Text) @@ -263,61 +287,4 @@ internal class WebSocketBuilder : IWebSocketBuilder return false; } - - private static async Task SendMessageAsync(WebSocketBuilder builder, IWebSocketContext context, WebSocketMessageBuilder messageBuilder, WebSocketMessage incomingMessage) - { - switch (messageBuilder.Type) - { - case WebSocketMessageBuilder.MessageType.Text: - var text = messageBuilder.MessageText!; - if (builder.UseTransformer) - { - text = ApplyTransformer(builder, context, incomingMessage, text); - } - await context.SendAsync(text); - break; - case WebSocketMessageBuilder.MessageType.Bytes: - await context.SendAsync(messageBuilder.MessageBytes!); - break; - case WebSocketMessageBuilder.MessageType.Json: - var jsonData = messageBuilder.MessageData!; - if (builder.UseTransformer) - { - var jsonString = JsonConvert.SerializeObject(jsonData); - jsonString = ApplyTransformer(builder, context, incomingMessage, jsonString); - jsonData = JsonConvert.DeserializeObject(jsonString) ?? jsonData; - } - await context.SendAsJsonAsync(jsonData); - break; - } - } - - private static string ApplyTransformer(WebSocketBuilder builder, IWebSocketContext context, WebSocketMessage incomingMessage, string text) - { - try - { - if (incomingMessage == null) - { - // No incoming message, can't apply transformer - return text; - } - - var transformer = TransformerFactory.Create(builder.TransformerType, context.Mapping.Settings); - - var model = new WebSocketTransformModel - { - Mapping = context.Mapping, - Request = context.RequestMessage, - Message = incomingMessage, - Data = incomingMessage.MessageType == WebSocketMessageType.Text ? incomingMessage.Text : null - }; - - return transformer.Transform(text, model); - } - catch - { - // If transformation fails, return original text - return text; - } - } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/WebSockets/WebSocketConnectionRegistry.cs b/src/WireMock.Net.Minimal/WebSockets/WebSocketConnectionRegistry.cs index fd3d20dd..e8ec025c 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WebSocketConnectionRegistry.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WebSocketConnectionRegistry.cs @@ -3,7 +3,6 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Net.WebSockets; -using Newtonsoft.Json; namespace WireMock.WebSockets; @@ -57,13 +56,4 @@ internal class WebSocketConnectionRegistry await Task.WhenAll(tasks); } - - /// - /// Broadcast JSON to all connections - /// - public async Task BroadcastJsonAsync(object data, CancellationToken cancellationToken = default) - { - var json = JsonConvert.SerializeObject(data); - await BroadcastTextAsync(json, cancellationToken); - } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/WebSockets/WebSocketMessageBuilder.cs b/src/WireMock.Net.Minimal/WebSockets/WebSocketMessageBuilder.cs index 81487690..69545cdd 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WebSocketMessageBuilder.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WebSocketMessageBuilder.cs @@ -1,5 +1,6 @@ // Copyright © WireMock.Net +using System.Net.WebSockets; using Stef.Validation; namespace WireMock.WebSockets; @@ -14,28 +15,21 @@ internal class WebSocketMessageBuilder : IWebSocketMessageBuilder public TimeSpan? Delay { get; private set; } - public MessageType Type { get; private set; } + public WebSocketMessageType Type { get; private set; } public bool ShouldClose { get; private set; } public IWebSocketMessageBuilder WithText(string text) { MessageText = Guard.NotNull(text); - Type = MessageType.Text; + Type = WebSocketMessageType.Text; return this; } - public IWebSocketMessageBuilder WithBytes(byte[] bytes) + public IWebSocketMessageBuilder WithBinary(byte[] bytes) { MessageBytes = Guard.NotNull(bytes); - Type = MessageType.Bytes; - return this; - } - - public IWebSocketMessageBuilder WithJson(object data) - { - MessageData = Guard.NotNull(data); - Type = MessageType.Json; + Type = WebSocketMessageType.Binary; return this; } @@ -52,22 +46,11 @@ internal class WebSocketMessageBuilder : IWebSocketMessageBuilder return this; } - public IWebSocketMessageBuilder AndClose() - { - ShouldClose = true; - return this; - } - public IWebSocketMessageBuilder Close() { ShouldClose = true; return this; } - internal enum MessageType - { - Text, - Bytes, - Json - } -} + public IWebSocketMessageBuilder AndClose() => Close(); +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/WebSockets/WebSocketMessagesBuilder.cs b/src/WireMock.Net.Minimal/WebSockets/WebSocketMessagesBuilder.cs index 58bebba1..8be588ef 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WebSocketMessagesBuilder.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WebSocketMessagesBuilder.cs @@ -1,12 +1,10 @@ // Copyright © WireMock.Net -using System.Collections.Generic; - namespace WireMock.WebSockets; internal class WebSocketMessagesBuilder : IWebSocketMessagesBuilder { - internal List Messages { get; } = new(); + internal List Messages { get; } = []; public IWebSocketMessagesBuilder AddMessage(Action configure) { @@ -15,4 +13,4 @@ internal class WebSocketMessagesBuilder : IWebSocketMessagesBuilder Messages.Add(messageBuilder); return this; } -} +} \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs b/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs index 32ed23fb..875d9463 100644 --- a/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs +++ b/src/WireMock.Net.Minimal/WebSockets/WireMockWebSocketContext.cs @@ -5,6 +5,7 @@ using System.Text; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Stef.Validation; +using WireMock.Extensions; using WireMock.Owin; namespace WireMock.WebSockets; @@ -54,9 +55,9 @@ public class WireMockWebSocketContext : IWebSocketContext Builder = Guard.NotNull(builder); // Get options from HttpContext - if (httpContext.Items.TryGetValue(nameof(WireMockMiddlewareOptions), out var options)) + if (httpContext.Items.TryGetValue(nameof(WireMockMiddlewareOptions), out var options)) { - _options = (IWireMockMiddlewareOptions)options!; + _options = options; } else { @@ -87,13 +88,6 @@ public class WireMockWebSocketContext : IWebSocketContext ); } - /// - public Task SendAsJsonAsync(object data, CancellationToken cancellationToken = default) - { - var json = JsonConvert.SerializeObject(data); - return SendAsync(json, cancellationToken); - } - /// public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription) { @@ -181,13 +175,4 @@ public class WireMockWebSocketContext : IWebSocketContext await Registry.BroadcastTextAsync(text, cancellationToken); } } - - /// - public async Task BroadcastJsonAsync(object data, CancellationToken cancellationToken = default) - { - if (Registry != null) - { - await Registry.BroadcastJsonAsync(data, cancellationToken); - } - } } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/Extensions/DictionaryExtensions.cs b/src/WireMock.Net.Shared/Extensions/DictionaryExtensions.cs index 95310ff0..4955d31e 100644 --- a/src/WireMock.Net.Shared/Extensions/DictionaryExtensions.cs +++ b/src/WireMock.Net.Shared/Extensions/DictionaryExtensions.cs @@ -28,4 +28,18 @@ internal static class DictionaryExtensions value = default; return false; } -} + + public static bool TryGetValue(this IDictionary dictionary, string key, [NotNullWhen(true)] out T? value) + { + Guard.NotNull(dictionary); + + if (dictionary[key] is T typedValue) + { + value = typedValue; + return true; + } + + value = default; + return false; + } +} \ No newline at end of file diff --git a/src/WireMock.Net.Shared/WebSockets/IWebSocketBuilder.cs b/src/WireMock.Net.Shared/WebSockets/IWebSocketBuilder.cs index 29b29d11..b2ec07e9 100644 --- a/src/WireMock.Net.Shared/WebSockets/IWebSocketBuilder.cs +++ b/src/WireMock.Net.Shared/WebSockets/IWebSocketBuilder.cs @@ -100,13 +100,4 @@ public interface IWebSocketBuilder /// [PublicAPI] IWebSocketBuilder WithKeepAliveInterval(TimeSpan interval); - - /// - /// Enable transformer support (Handlebars/Scriban) - /// - [PublicAPI] - IWebSocketBuilder WithTransformer( - TransformerType transformerType = TransformerType.Handlebars, - bool useTransformerForBodyAsFile = false, - ReplaceNodeOptions transformerReplaceNodeOptions = ReplaceNodeOptions.EvaluateAndTryToConvert); } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/WebSockets/IWebSocketContext.cs b/src/WireMock.Net.Shared/WebSockets/IWebSocketContext.cs index 5942b421..aca75940 100644 --- a/src/WireMock.Net.Shared/WebSockets/IWebSocketContext.cs +++ b/src/WireMock.Net.Shared/WebSockets/IWebSocketContext.cs @@ -45,11 +45,6 @@ public interface IWebSocketContext /// Task SendAsync(byte[] bytes, CancellationToken cancellationToken = default); - /// - /// Send JSON message to the client - /// - Task SendAsJsonAsync(object data, CancellationToken cancellationToken = default); - /// /// Close the WebSocket connection /// @@ -74,9 +69,4 @@ public interface IWebSocketContext /// Broadcast text message to all connections in this mapping /// Task BroadcastTextAsync(string text, CancellationToken cancellationToken = default); - - /// - /// Broadcast JSON message to all connections in this mapping - /// - Task BroadcastJsonAsync(object data, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/WireMock.Net.Shared/WebSockets/IWebSocketMessageBuilder.cs b/src/WireMock.Net.Shared/WebSockets/IWebSocketMessageBuilder.cs index 118fd3cb..6cd8cfd9 100644 --- a/src/WireMock.Net.Shared/WebSockets/IWebSocketMessageBuilder.cs +++ b/src/WireMock.Net.Shared/WebSockets/IWebSocketMessageBuilder.cs @@ -21,14 +21,7 @@ public interface IWebSocketMessageBuilder /// /// The binary data to send [PublicAPI] - IWebSocketMessageBuilder WithBytes(byte[] bytes); - - /// - /// Send a JSON object - /// - /// The object to serialize and send as JSON - [PublicAPI] - IWebSocketMessageBuilder WithJson(object data); + IWebSocketMessageBuilder WithBinary(byte[] bytes); /// /// Set a delay before sending the message (using TimeSpan) diff --git a/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs b/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs index c2de596e..a6d6f6db 100644 --- a/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs +++ b/test/WireMock.Net.Tests/WebSockets/WebSocketIntegrationTests.cs @@ -1,10 +1,7 @@ // Copyright © WireMock.Net -using System.Net; -using System.Net.Http; using System.Net.WebSockets; using FluentAssertions; -using Newtonsoft.Json.Linq; using WireMock.Matchers; using WireMock.Net.Xunit; using WireMock.RequestBuilders; @@ -137,7 +134,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) } [Fact] - public async Task WithBytes_Should_Send_Configured_Bytes() + public async Task WithBinary_Should_Send_Configured_Bytes() { // Arrange using var server = WireMockServer.Start(new WireMockServerSettings @@ -155,7 +152,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) ) .RespondWith(Response.Create() .WithWebSocket(ws => ws - .SendMessage(m => m.WithBytes(responseBytes)) + .SendMessage(m => m.WithBinary(responseBytes)) ) ); @@ -177,7 +174,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) } [Fact] - public async Task WithBytes_Should_Send_Same_Bytes_For_Multiple_Messages() + public async Task WithBinary_Should_Send_Same_Bytes_For_Multiple_Messages() { // Arrange using var server = WireMockServer.Start(new WireMockServerSettings @@ -195,7 +192,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) ) .RespondWith(Response.Create() .WithWebSocket(ws => ws - .SendMessage(m => m.WithBytes(responseBytes)) + .SendMessage(m => m.WithBinary(responseBytes)) ) ); @@ -216,103 +213,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None); } - - [Fact] - public async Task WithJson_Should_Send_Configured_Json() - { - // Arrange - using var server = WireMockServer.Start(new WireMockServerSettings - { - Logger = new TestOutputHelperWireMockLogger(output), - Urls = ["ws://localhost:0"] - }); - - var responseData = new - { - status = "ok", - message = "This is a predefined JSON response", - timestamp = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc) - }; - - server - .Given(Request.Create() - .WithPath("/ws/json") - .WithWebSocketUpgrade() - ) - .RespondWith(Response.Create() - .WithWebSocket(ws => ws - .SendMessage(m => m.WithJson(responseData)) - ) - ); - - using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Url!}/ws/json"); - - // Act - await client.ConnectAsync(uri, CancellationToken.None); - client.State.Should().Be(WebSocketState.Open); - - var testMessage = "Any message from client"; - await client.SendAsync(testMessage); - - // Assert - var received = await client.ReceiveAsTextAsync(); - - var json = JObject.Parse(received); - json["status"]!.ToString().Should().Be("ok"); - json["message"]!.ToString().Should().Be("This is a predefined JSON response"); - json["timestamp"].Should().NotBeNull(); - - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None); - } - - [Fact] - public async Task WithJson_Should_Send_Same_Json_For_Multiple_Messages() - { - // Arrange - using var server = WireMockServer.Start(new WireMockServerSettings - { - Logger = new TestOutputHelperWireMockLogger(output), - Urls = ["ws://localhost:0"] - }); - - var responseData = new - { - id = 42, - name = "Fixed JSON Response" - }; - - server - .Given(Request.Create() - .WithPath("/ws/json") - .WithWebSocketUpgrade() - ) - .RespondWith(Response.Create() - .WithWebSocket(ws => ws - .SendMessage(m => m.WithJson(responseData)) - ) - ); - - using var client = new ClientWebSocket(); - var uri = new Uri($"{server.Url!}/ws/json"); - await client.ConnectAsync(uri, CancellationToken.None); - - var testMessages = new[] { "First", "Second", "Third" }; - - // Act & Assert - foreach (var testMessage in testMessages) - { - await client.SendAsync(testMessage); - - var received = await client.ReceiveAsTextAsync(); - - var json = JObject.Parse(received); - json["id"]!.Value().Should().Be(42); - json["name"]!.ToString().Should().Be("Fixed JSON Response", $"should always return the fixed JSON regardless of input message '{testMessage}'"); - } - - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None); - } + [Fact] public async Task EchoServer_Should_Echo_Multiple_Messages() @@ -676,8 +577,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) ) .RespondWith(Response.Create() .WithWebSocket(ws => ws - .WithTransformer() - .SendMessage(m => m.WithText("{{[String.Lowercase] message.Text}}")) + .SendMessage(m => m.WithText("{{request.Path}} {{[String.Lowercase] message.Text}}")) ) .WithTransformer() ); @@ -694,7 +594,7 @@ public class WebSocketIntegrationTests(ITestOutputHelper output) // Assert var received = await client.ReceiveAsTextAsync(); - received.Should().Be("hello"); + received.Should().Be("/ws/transform hello"); await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None); }