[System] Replace websocket echo test server with corefx one (#2902)
[mono.git] / mcs / class / System / Test / System.Net.WebSockets / ClientWebSocketTest.cs
1 #if NET_4_5
2 using System;
3 using System.Net;
4 using System.Threading;
5 using System.Threading.Tasks;
6 using System.Collections.Generic;
7 using System.Net.WebSockets;
8 using System.Reflection;
9 using System.Text;
10
11 using NUnit.Framework;
12
13 using MonoTests.Helpers;
14
15 namespace MonoTests.System.Net.WebSockets
16 {
17         [TestFixture]
18         public class ClientWebSocketTest
19         {
20                 const string EchoServerUrl = "ws://corefx-net.cloudapp.net/WebSocket/EchoWebSocket.ashx";
21                 int Port = NetworkHelpers.FindFreePort ();
22                 HttpListener listener;
23                 ClientWebSocket socket;
24                 MethodInfo headerSetMethod;
25
26                 [SetUp]
27                 public void Setup ()
28                 {
29                         listener = new HttpListener ();
30                         listener.Prefixes.Add ("http://localhost:" + Port + "/");
31                         listener.Start ();
32                         socket = new ClientWebSocket ();
33                 }
34
35                 [TearDown]
36                 public void Teardown ()
37                 {
38                         if (listener != null) {
39                                 listener.Stop ();
40                                 listener = null;
41                         }
42                         if (socket != null) {
43                                 if (socket.State == WebSocketState.Open)
44                                         socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
45                                 socket.Dispose ();
46                                 socket = null;
47                         }
48                 }
49
50                 [Test]
51                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
52                 public void ServerHandshakeReturnCrapStatusCodeTest ()
53                 {
54                         // On purpose, 
55                         #pragma warning disable 4014
56                         HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
57                         #pragma warning restore 4014
58                         try {
59                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
60                         } catch (AggregateException e) {
61                                 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
62                                 return;
63                         }
64                         Assert.Fail ("Should have thrown");
65                 }
66
67                 [Test]
68                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
69                 public void ServerHandshakeReturnWrongUpgradeHeader ()
70                 {
71                         #pragma warning disable 4014
72                         HandleHttpRequestAsync ((req, resp) => {
73                                         resp.StatusCode = 101;
74                                         resp.Headers["Upgrade"] = "gtfo";
75                                 });
76                         #pragma warning restore 4014
77                         try {
78                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
79                         } catch (AggregateException e) {
80                                 AssertWebSocketException (e, WebSocketError.Success);
81                                 return;
82                         }
83                         Assert.Fail ("Should have thrown");
84                 }
85
86                 [Test]
87                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
88                 public void ServerHandshakeReturnWrongConnectionHeader ()
89                 {
90                         #pragma warning disable 4014
91                         HandleHttpRequestAsync ((req, resp) => {
92                                         resp.StatusCode = 101;
93                                         resp.Headers["Upgrade"] = "websocket";
94                                         // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
95                                         //ForceSetHeader (resp.Headers, "Connection", "Foo");
96                                 });
97                         #pragma warning restore 4014
98                         try {
99                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
100                         } catch (AggregateException e) {
101                                 AssertWebSocketException (e, WebSocketError.Success);
102                                 return;
103                         }
104                         Assert.Fail ("Should have thrown");
105                 }
106
107                 [Test]
108                 [Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
109                 public void EchoTest ()
110                 {
111                         const string Payload = "This is a websocket test";
112
113                         Assert.AreEqual (WebSocketState.None, socket.State);
114                         socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
115                         Assert.AreEqual (WebSocketState.Open, socket.State);
116
117                         var sendBuffer = Encoding.ASCII.GetBytes (Payload);
118                         Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
119
120                         var receiveBuffer = new byte[Payload.Length];
121                         var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
122
123                         Assert.AreEqual (Payload.Length, resp.Count);
124                         Assert.IsTrue (resp.EndOfMessage);
125                         Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
126                         Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
127
128                         Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
129                         Assert.AreEqual (WebSocketState.Closed, socket.State);
130                 }
131
132                 [Test]
133                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
134                 public void CloseOutputAsyncTest ()
135                 {
136                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
137                         Assert.AreEqual (WebSocketState.Open, socket.State);
138
139                         Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
140                         Assert.AreEqual (WebSocketState.CloseSent, socket.State);
141
142                         var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
143                         Assert.AreEqual (WebSocketState.Closed, socket.State);
144                         Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
145                         Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
146                         Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
147                 }
148
149                 [Test]
150                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
151                 public void CloseAsyncTest ()
152                 {
153                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
154                         Assert.AreEqual (WebSocketState.Open, socket.State);
155
156                         Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
157                         Assert.AreEqual (WebSocketState.Closed, socket.State);
158                 }
159
160                 [Test, ExpectedException (typeof (InvalidOperationException))]
161                 public void SendAsyncArgTest_NotConnected ()
162                 {
163                         socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
164                 }
165
166                 [Test, ExpectedException (typeof (ArgumentNullException))]
167                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
168                 public void SendAsyncArgTest_NoArray ()
169                 {
170                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
171                         socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
172                 }
173
174                 [Test, ExpectedException (typeof (InvalidOperationException))]
175                 public void ReceiveAsyncArgTest_NotConnected ()
176                 {
177                         socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
178                 }
179
180                 [Test, ExpectedException (typeof (ArgumentNullException))]
181                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
182                 public void ReceiveAsyncArgTest_NoArray ()
183                 {
184                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
185                         socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
186                 }
187
188                 [Test]
189                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
190                 public void ReceiveAsyncWrongState_Closed ()
191                 {
192                         try {
193                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
194                                 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
195                                 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
196                         } catch (AggregateException e) {
197                                 AssertWebSocketException (e, WebSocketError.Success);
198                                 return;
199                         }
200                         Assert.Fail ("Should have thrown");
201                 }
202
203                 [Test]
204                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
205                 public void SendAsyncWrongState_Closed ()
206                 {
207                         try {
208                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
209                                 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
210                                 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
211                         } catch (AggregateException e) {
212                                 AssertWebSocketException (e, WebSocketError.Success);
213                                 return;
214                         }
215                         Assert.Fail ("Should have thrown");
216                 }
217
218                 [Test]
219                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
220                 public void SendAsyncWrongState_CloseSent ()
221                 {
222                         try {
223                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
224                                 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
225                                 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
226                         } catch (AggregateException e) {
227                                 AssertWebSocketException (e, WebSocketError.Success);
228                                 return;
229                         }
230                         Assert.Fail ("Should have thrown");
231                 }
232
233                 [Test]
234                 [Category ("NotWorking")]  // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
235                 public void SendAsyncEndOfMessageTest ()
236                 {
237                         var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
238                         SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
239                         SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
240                         SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
241                         SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
242                 }
243
244                 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
245                 {
246                         using (var client = new ClientWebSocket ()) {
247                                 // Configure the listener.
248                                 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
249
250                                 // Connect to the listener and make the request.
251                                 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
252                                 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
253
254                                 // Wait for the listener to handle the request and return its result.
255                                 var result = await serverReceive;
256
257                                 // Cleanup and check results.
258                                 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
259                                 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
260                         }
261                 }
262
263                 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
264                 {
265                         var ctx = await this.listener.GetContextAsync ();
266                         var wsContext = await ctx.AcceptWebSocketAsync (null);
267                         var result = await action (wsContext.WebSocket);
268                         await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
269                         return result;
270                 }
271
272                 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
273                 {
274                         var ctx = await listener.GetContextAsync ();
275                         handler (ctx.Request, ctx.Response);
276                         ctx.Response.Close ();
277                 }
278
279                 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
280                 {
281                         var wsEx = e.InnerException as WebSocketException;
282                         Console.WriteLine (e.InnerException.ToString ());
283                         Assert.IsNotNull (wsEx, "Not a websocketexception");
284                         Assert.AreEqual (error, wsEx.WebSocketErrorCode);
285                         if (inner != null) {
286                                 Assert.IsNotNull (wsEx.InnerException);
287                                 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
288                         }
289                 }
290
291                 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
292                 {
293                         if (headerSetMethod == null)
294                                 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
295                         headerSetMethod.Invoke (headers, new[] { name, value });
296                 }
297         }
298 }
299
300 #endif