@@ -13,7 +13,7 @@ mod test {
1313 use rand:: Rng ;
1414 use tokio:: { io:: { AsyncReadExt , AsyncWriteExt } , join, net:: TcpStream , select} ;
1515 use tokio_util:: sync:: CancellationToken ;
16- use tokio_websockets:: { ClientBuilder , Message } ;
16+ use tokio_websockets:: { ClientBuilder , CloseCode , Message } ;
1717
1818 use crate :: { config:: test:: default_config, server:: server:: server_with_real_deps, util:: test:: { compare_bufs, get_file, setup_logging, sleep_ms} } ;
1919
@@ -89,6 +89,32 @@ mod test {
8989 }
9090 }
9191
92+ async fn websocket_writer_end_with_normal_closure ( port : u16 , header : & [ u8 ] , file : Vec < u8 > ) {
93+ let uri = format ! ( "ws://127.0.0.1:{}" , port) . parse :: < Uri > ( ) . unwrap ( ) ;
94+ let ( mut client, _) = ClientBuilder :: from_uri ( uri) . connect ( ) . await . unwrap ( ) ;
95+
96+ random_sleep ( ) . await ;
97+ client. send ( Message :: binary ( header. to_owned ( ) ) ) . await . unwrap ( ) ;
98+ random_sleep ( ) . await ;
99+ for data in file. chunks ( 1000 ) {
100+ client. send ( Message :: binary ( data. to_owned ( ) ) ) . await . unwrap ( ) ;
101+ random_sleep ( ) . await ;
102+ }
103+ // Add a ping and pong we should ignore for good measure.
104+ client. send ( Message :: ping ( "foo" ) ) . await . unwrap ( ) ;
105+ client. send ( Message :: pong ( "bar" ) ) . await . unwrap ( ) ;
106+ client. send ( Message :: close ( Some ( CloseCode :: NORMAL_CLOSURE ) , "" ) ) . await . unwrap ( ) ;
107+ }
108+
109+ async fn websocket_writer_abnormal_closure ( port : u16 , header : & [ u8 ] ) {
110+ let uri = format ! ( "ws://127.0.0.1:{}" , port) . parse :: < Uri > ( ) . unwrap ( ) ;
111+ let ( mut client, _) = ClientBuilder :: from_uri ( uri) . connect ( ) . await . unwrap ( ) ;
112+ random_sleep ( ) . await ;
113+ client. send ( Message :: binary ( header. to_owned ( ) ) ) . await . unwrap ( ) ;
114+ random_sleep ( ) . await ;
115+ client. send ( Message :: close ( Some ( CloseCode :: GOING_AWAY ) , "" ) ) . await . unwrap ( ) ;
116+ }
117+
92118 async fn websocket_reader ( port : u16 , header : & [ u8 ] ) -> Vec < u8 > {
93119 let uri = format ! ( "ws://127.0.0.1:{}" , port) . parse :: < Uri > ( ) . unwrap ( ) ;
94120 let ( mut client, _) = ClientBuilder :: from_uri ( uri) . connect ( ) . await . unwrap ( ) ;
@@ -216,4 +242,100 @@ mod test {
216242
217243 // TODO: verify stats saved to the database.
218244 }
245+
246+ // See issue #13.
247+ #[ cfg_attr( not( feature = "local_db_tests" ) , ignore) ]
248+ #[ tokio:: test( flavor = "multi_thread" ) ]
249+ async fn integration_test_server_websocket_normal_closure_message ( ) {
250+
251+ setup_logging ( ) ;
252+
253+ let replay_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
254+ let mut config = default_config ( ) ;
255+ config. server . port = None ;
256+ config. server . websocket_port = Some ( 0 ) ;
257+ config. database . host = std:: env:: var ( "DB_HOST" ) . unwrap ( ) ;
258+ config. database . port = str:: parse :: < u16 > ( & std:: env:: var ( "DB_PORT" ) . unwrap ( ) ) . unwrap ( ) ;
259+ config. storage . vault_path = replay_dir. path ( ) . to_str ( ) . unwrap ( ) . into ( ) ;
260+ config. server . connection_accept_timeout_s = Duration :: from_millis ( 50 ) ;
261+
262+ let token = CancellationToken :: new ( ) ;
263+ let ( server, port_info) = server_with_real_deps ( Arc :: new ( config) , token. child_token ( ) ) . await ;
264+
265+ let sent_replay = get_file ( "example" ) ;
266+ let replay_writer = websocket_writer_end_with_normal_closure ( port_info. websocket . unwrap ( ) , b"P/2001/foo\0 " , sent_replay. clone ( ) ) ;
267+
268+ let exit_server = async move || {
269+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
270+ token. cancel ( ) ;
271+ } ;
272+
273+ let run = async move || {
274+ let ( _, _, _) = join ! {
275+ server. run( ) ,
276+ exit_server( ) ,
277+ replay_writer,
278+ } ;
279+ } ;
280+ tokio:: time:: timeout ( Duration :: from_secs ( 2 ) , run ( ) ) . await . unwrap ( ) ;
281+
282+ let mut replay_path: PathBuf = replay_dir. path ( ) . to_owned ( ) ;
283+ replay_path. push ( "0" ) ;
284+ replay_path. push ( "0" ) ;
285+ replay_path. push ( "0" ) ;
286+ replay_path. push ( "20" ) ;
287+ replay_path. push ( "2001.fafreplay" ) ;
288+ let saved_replay = std:: fs:: read ( & replay_path) . unwrap ( ) ;
289+
290+ // This file was copied from this test and manually verified to be correct.
291+ // Zstd is deterministic for the same data + params + version, let's rely on that.
292+ let reference_replay = get_file ( "integration/game_2001.fafreplay" ) ;
293+ compare_bufs ( saved_replay, reference_replay) ;
294+ }
295+
296+ #[ cfg_attr( not( feature = "local_db_tests" ) , ignore) ]
297+ #[ tokio:: test( flavor = "multi_thread" ) ]
298+ async fn integration_test_server_websocket_abnormal_closure ( ) {
299+
300+ setup_logging ( ) ;
301+
302+ let replay_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
303+ let mut config = default_config ( ) ;
304+ config. server . port = None ;
305+ config. server . websocket_port = Some ( 0 ) ;
306+ config. database . host = std:: env:: var ( "DB_HOST" ) . unwrap ( ) ;
307+ config. database . port = str:: parse :: < u16 > ( & std:: env:: var ( "DB_PORT" ) . unwrap ( ) ) . unwrap ( ) ;
308+ config. storage . vault_path = replay_dir. path ( ) . to_str ( ) . unwrap ( ) . into ( ) ;
309+ config. server . connection_accept_timeout_s = Duration :: from_millis ( 50 ) ;
310+
311+ let token = CancellationToken :: new ( ) ;
312+ let ( server, port_info) = server_with_real_deps ( Arc :: new ( config) , token. child_token ( ) ) . await ;
313+
314+ let replay_writer = websocket_writer_abnormal_closure ( port_info. websocket . unwrap ( ) , b"P/2001/foo\0 " ) ;
315+
316+ let exit_server = async move || {
317+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
318+ token. cancel ( ) ;
319+ } ;
320+
321+ let run = async move || {
322+ let ( _, _, _) = join ! {
323+ server. run( ) ,
324+ exit_server( ) ,
325+ replay_writer,
326+ } ;
327+ } ;
328+ tokio:: time:: timeout ( Duration :: from_secs ( 2 ) , run ( ) ) . await . unwrap ( ) ;
329+
330+ // The abnormal closure should've been interpreted as an error and not data.
331+ // FIXME this doesn't check that the connection ended with an error...
332+ // FIXME a test in replay.rs might be better.
333+ let mut replay_path: PathBuf = replay_dir. path ( ) . to_owned ( ) ;
334+ replay_path. push ( "0" ) ;
335+ replay_path. push ( "0" ) ;
336+ replay_path. push ( "0" ) ;
337+ replay_path. push ( "20" ) ;
338+ replay_path. push ( "2001.fafreplay" ) ;
339+ let _saved_replay = std:: fs:: read ( & replay_path) . expect_err ( "Replay should have been empty and not saved." ) ;
340+ }
219341}
0 commit comments