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