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