package org.jooby.internal;

import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.lang.reflect.Field;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.jooby.Err;
import org.jooby.MediaType;
import org.jooby.Mutant;
import org.jooby.Renderer;
import org.jooby.Request;
import org.jooby.WebSocket;
import org.jooby.WebSocket.CloseStatus;
import org.jooby.WebSocket.OnClose;
import org.jooby.WebSocket.OnMessage;
import org.jooby.internal.parser.ParserExecutor;
import org.jooby.spi.NativeWebSocket;
import org.jooby.test.MockUnit;
import org.jooby.test.MockUnit.Block;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;

@RunWith(PowerMockRunner.class)
@PrepareForTest({WebSocketImpl.class, WebSocketRendererContext.class })
public class WebSocketImplTest {

  private Block connect = unit -> {
    WebSocket.OnOpen1 handler = unit.get(WebSocket.OnOpen1.class);
    handler.onOpen(eq(unit.get(Request.class)), isA(WebSocketImpl.class));

    Injector injector = unit.get(Injector.class);

    expect(injector.getInstance(Key.get(new TypeLiteral<Set<Renderer>>() {
    }))).andReturn(Collections.emptySet());

  };

  @SuppressWarnings("unchecked")
  private Block callbacks = unit -> {
    NativeWebSocket nws = unit.get(NativeWebSocket.class);
    nws.onBinaryMessage(isA(Consumer.class));
    nws.onTextMessage(isA(Consumer.class));
    nws.onErrorMessage(isA(Consumer.class));
    nws.onCloseMessage(isA(BiConsumer.class));
  };

  private Block locale = unit -> {
    Request req = unit.get(Request.class);
    expect(req.locale()).andReturn(Locale.CANADA);
  };

  @SuppressWarnings({"resource" })
  @Test
  public void sendString() throws Exception {
    Object data = "String";
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    new MockUnit(WebSocket.OnOpen1.class, WebSocket.SuccessCallback.class,
        WebSocket.OnError.class, Injector.class, Request.class, NativeWebSocket.class)
            .expect(connect)
            .expect(callbacks)
            .expect(locale)
            .expect(unit -> {
              List<Renderer> renderers = Collections.emptyList();

              NativeWebSocket ws = unit.get(NativeWebSocket.class);
              expect(ws.isOpen()).andReturn(true);

              WebSocketRendererContext ctx = unit.mockConstructor(WebSocketRendererContext.class,
                  new Class[]{List.class, NativeWebSocket.class, MediaType.class, Charset.class,
                      Locale.class,
                      WebSocket.SuccessCallback.class,
                      WebSocket.OnError.class },
                  renderers, ws,
                  produces, StandardCharsets.UTF_8,
                  Locale.CANADA,
                  unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
              ctx.render(data);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));

              ws.send(data, unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
            });
  }

  @SuppressWarnings("unchecked")
  @Before
  @After
  public void resetSessions() throws Exception {
    Field field = WebSocketImpl.class.getDeclaredField("sessions");
    field.setAccessible(true);
    Queue<WebSocket> sessions = (Queue<WebSocket>) field.get(null);
    sessions.clear();
  }

  @SuppressWarnings({"resource" })
  @Test
  public void sendBroadcast() throws Exception {
    Object data = "String";
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    new MockUnit(WebSocket.OnOpen1.class, WebSocket.SuccessCallback.class,
        WebSocket.OnError.class, Injector.class, Request.class, NativeWebSocket.class)
            .expect(connect)
            .expect(callbacks)
            .expect(locale)
            .expect(unit -> {
              List<Renderer> renderers = Collections.emptyList();

              NativeWebSocket ws = unit.get(NativeWebSocket.class);
              expect(ws.isOpen()).andReturn(true);

              WebSocketRendererContext ctx = unit.mockConstructor(WebSocketRendererContext.class,
                  new Class[]{List.class, NativeWebSocket.class, MediaType.class, Charset.class,
                      Locale.class,
                      WebSocket.SuccessCallback.class,
                      WebSocket.OnError.class },
                  renderers, ws,
                  produces, StandardCharsets.UTF_8,
                  Locale.CANADA,
                  unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
              ctx.render(data);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));

              ws.broadcast(data, unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
            });
  }

  @SuppressWarnings({"resource" })
  @Test
  public void sendBroadcastErr() throws Exception {
    Object data = "String";
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    new MockUnit(WebSocket.OnOpen1.class, WebSocket.SuccessCallback.class,
        WebSocket.OnError.class, Injector.class, Request.class, NativeWebSocket.class)
            .expect(connect)
            .expect(callbacks)
            .expect(locale)
            .expect(unit -> {
              List<Renderer> renderers = Collections.emptyList();

              NativeWebSocket ws = unit.get(NativeWebSocket.class);
              expect(ws.isOpen()).andReturn(true);

              WebSocketRendererContext ctx = unit.mockConstructor(WebSocketRendererContext.class,
                  new Class[]{List.class, NativeWebSocket.class, MediaType.class, Charset.class,
                      Locale.class,
                      WebSocket.SuccessCallback.class,
                      WebSocket.OnError.class },
                  renderers, ws,
                  produces, StandardCharsets.UTF_8,
                  Locale.CANADA,
                  unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
              ctx.render(data);
              IllegalStateException x = new IllegalStateException("intentional err");
              expectLastCall().andThrow(x);
              unit.get(WebSocket.OnError.class).onError(x);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));

              ws.broadcast(data, unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
            });
  }

  @SuppressWarnings({"resource" })
  @Test(expected = Err.class)
  public void sendClose() throws Exception {
    Object data = "String";
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    new MockUnit(WebSocket.OnOpen1.class, WebSocket.SuccessCallback.class,
        WebSocket.OnError.class, Injector.class, Request.class, NativeWebSocket.class)
            .expect(connect)
            .expect(callbacks)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket ws = unit.get(NativeWebSocket.class);
              expect(ws.isOpen()).andReturn(false);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));

              ws.send(data, unit.get(WebSocket.SuccessCallback.class),
                  unit.get(WebSocket.OnError.class));
            });
  }

  @SuppressWarnings("resource")
  @Test
  public void toStr() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;

    new MockUnit(WebSocket.OnOpen1.class)
        .run(unit -> {
          WebSocketImpl ws = new WebSocketImpl(
              unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
          assertEquals("WS /\n" +
              "  pattern: /pattern\n" +
              "  vars: {}\n" +
              "  consumes: */*\n" +
              "  produces: */*\n" +
              "", ws.toString());
        });
  }

  @SuppressWarnings({"resource" })
  @Test
  public void isOpen() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    new MockUnit(WebSocket.OnOpen1.class, WebSocket.SuccessCallback.class,
        WebSocket.OnError.class, Injector.class, Request.class, NativeWebSocket.class)
            .expect(connect)
            .expect(callbacks)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket ws = unit.get(NativeWebSocket.class);
              expect(ws.isOpen()).andReturn(true);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));

              assertTrue(ws.isOpen());
            });
  }

  @SuppressWarnings("resource")
  @Test
  public void pauseAndResume() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class)
        .expect(connect)
        .expect(callbacks)
        .expect(locale)
        .expect(unit -> {
          NativeWebSocket channel = unit.get(NativeWebSocket.class);
          channel.pause();

          channel.resume();
        })
        .run(unit -> {
          WebSocketImpl ws = new WebSocketImpl(
              unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
          ws.connect(unit.get(Injector.class), unit.get(Request.class),
              unit.get(NativeWebSocket.class));
          ws.pause();

          ws.pause();

          ws.resume();

          ws.resume();
        });
  }

  @Test
  public void close() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class)
        .expect(connect)
        .expect(callbacks)
        .expect(locale)
        .expect(unit -> {
          NativeWebSocket ws = unit.get(NativeWebSocket.class);
          ws.close(WebSocket.NORMAL.code(), WebSocket.NORMAL.reason());
        }).run(unit -> {
          WebSocketImpl ws = new WebSocketImpl(
              unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
          ws.connect(unit.get(Injector.class), unit.get(Request.class),
              unit.get(NativeWebSocket.class));
          ws.close(WebSocket.NORMAL);
        });
  }

  @SuppressWarnings("resource")
  @Test
  public void terminate() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class)
        .expect(connect)
        .expect(callbacks)
        .expect(locale)
        .expect(unit -> {
          NativeWebSocket ws = unit.get(NativeWebSocket.class);
          ws.terminate();
        })
        .run(unit -> {
          WebSocketImpl ws = new WebSocketImpl(
              unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
          ws.connect(unit.get(Injector.class), unit.get(Request.class),
              unit.get(NativeWebSocket.class));
          ws.terminate();
        });
  }

  @SuppressWarnings("resource")
  @Test
  public void props() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;

    new MockUnit(WebSocket.OnOpen1.class)
        .run(unit -> {
          WebSocketImpl ws = new WebSocketImpl(
              unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
          assertEquals(pattern, ws.pattern());
          assertEquals(path, ws.path());
          assertEquals(consumes, ws.consumes());
          assertEquals(produces, ws.produces());
        });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void require() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    Object instance = new Object();

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class)
        .expect(connect)
        .expect(locale)
        .expect(unit -> {
          NativeWebSocket nws = unit.get(NativeWebSocket.class);
          nws.onBinaryMessage(isA(Consumer.class));
          nws.onTextMessage(isA(Consumer.class));
          nws.onErrorMessage(isA(Consumer.class));
          nws.onCloseMessage(isA(BiConsumer.class));
        })
        .expect(unit -> {
          Injector injector = unit.get(Injector.class);
          expect(injector.getInstance(Key.get(Object.class))).andReturn(instance);
        })
        .run(unit -> {
          WebSocketImpl ws = new WebSocketImpl(
              unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
          ws.connect(unit.get(Injector.class), unit.get(Request.class),
              unit.get(NativeWebSocket.class));
          assertEquals(instance, ws.require(Object.class));
        });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onMessage() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, OnMessage.class, Request.class,
        NativeWebSocket.class,
        Mutant.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(unit.capture(Consumer.class));
              nws.onErrorMessage(isA(Consumer.class));
              nws.onCloseMessage(isA(BiConsumer.class));
            })
            .expect(unit -> {
              OnMessage<Mutant> callback = unit.get(OnMessage.class);
              callback.onMessage(isA(Mutant.class));
            })
            .expect(unit -> {
              Injector injector = unit.get(Injector.class);
              expect(injector.getInstance(ParserExecutor.class)).andReturn(
                  unit.mock(ParserExecutor.class));
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onMessage(unit.get(OnMessage.class));
            }, unit -> {
              unit.captured(Consumer.class).iterator().next().accept("something");
            });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onErr() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    Exception ex = new Exception();

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class,
        WebSocket.OnError.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(isA(Consumer.class));
              nws.onErrorMessage(unit.capture(Consumer.class));
              nws.onCloseMessage(isA(BiConsumer.class));

              expect(nws.isOpen()).andReturn(false);
            })
            .expect(unit -> {
              WebSocket.OnError callback = unit.get(WebSocket.OnError.class);
              callback.onError(ex);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onError(unit.get(WebSocket.OnError.class));
            }, unit -> {
              unit.captured(Consumer.class).iterator().next().accept(ex);
            });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onSilentErr() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    Exception ex = new ClosedChannelException();

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class,
        WebSocket.OnError.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(isA(Consumer.class));
              nws.onErrorMessage(unit.capture(Consumer.class));
              nws.onCloseMessage(isA(BiConsumer.class));

              expect(nws.isOpen()).andReturn(false);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onError(unit.get(WebSocket.OnError.class));
            }, unit -> {
              unit.captured(Consumer.class).iterator().next().accept(ex);
            });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onErrAndWsOpen() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    Exception ex = new Exception();

    new MockUnit(WebSocket.OnOpen1.class, Injector.class, Request.class, NativeWebSocket.class,
        WebSocket.OnError.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(isA(Consumer.class));
              nws.onErrorMessage(unit.capture(Consumer.class));
              nws.onCloseMessage(isA(BiConsumer.class));

              expect(nws.isOpen()).andReturn(true);
              nws.close(1011, "Server error");
            })
            .expect(unit -> {
              WebSocket.OnError callback = unit.get(WebSocket.OnError.class);
              callback.onError(ex);
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onError(unit.get(WebSocket.OnError.class));
            }, unit -> {
              unit.captured(Consumer.class).iterator().next().accept(ex);
            });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onClose() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    WebSocket.CloseStatus status = WebSocket.NORMAL;

    new MockUnit(WebSocket.OnOpen1.class, OnMessage.class, OnClose.class, Request.class,
        NativeWebSocket.class, Injector.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(isA(Consumer.class));
              nws.onErrorMessage(isA(Consumer.class));
              nws.onCloseMessage(unit.capture(BiConsumer.class));
            })
            .expect(unit -> {
              OnClose callback = unit.get(OnClose.class);
              callback.onClose(unit.capture(WebSocket.CloseStatus.class));
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onClose(unit.get(WebSocket.OnClose.class));
            }, unit -> {
              unit.captured(BiConsumer.class).iterator().next()
                  .accept(status.code(), Optional.of(status.reason()));
            }, unit -> {
              CloseStatus captured = unit.captured(WebSocket.CloseStatus.class).iterator().next();
              assertEquals(status.code(), captured.code());
              assertEquals(status.reason(), captured.reason());
            });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onCloseNullReason() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    WebSocket.CloseStatus status = WebSocket.CloseStatus.of(1000);

    new MockUnit(WebSocket.OnOpen1.class, OnMessage.class, OnClose.class, NativeWebSocket.class,
        Request.class, Injector.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(isA(Consumer.class));
              nws.onErrorMessage(isA(Consumer.class));
              nws.onCloseMessage(unit.capture(BiConsumer.class));
            })
            .expect(unit -> {
              OnClose callback = unit.get(OnClose.class);
              callback.onClose(unit.capture(WebSocket.CloseStatus.class));
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onClose(unit.get(OnClose.class));
            }, unit -> {
              unit.captured(BiConsumer.class).iterator().next()
                  .accept(status.code(), Optional.empty());
            }, unit -> {
              CloseStatus captured = unit.captured(WebSocket.CloseStatus.class).iterator().next();
              assertEquals(status.code(), captured.code());
              assertEquals(null, captured.reason());
            });
  }

  @SuppressWarnings({"resource", "unchecked" })
  @Test
  public void onCloseEmptyReason() throws Exception {
    String path = "/";
    String pattern = "/pattern";
    Map<Object, String> vars = new HashMap<>();
    MediaType consumes = MediaType.all;
    MediaType produces = MediaType.all;
    WebSocket.CloseStatus status = WebSocket.CloseStatus.of(1000, "");

    new MockUnit(WebSocket.OnOpen1.class, OnMessage.class, NativeWebSocket.class, Request.class,
        Injector.class, OnClose.class)
            .expect(connect)
            .expect(locale)
            .expect(unit -> {
              NativeWebSocket nws = unit.get(NativeWebSocket.class);
              nws.onBinaryMessage(isA(Consumer.class));
              nws.onTextMessage(isA(Consumer.class));
              nws.onErrorMessage(isA(Consumer.class));
              nws.onCloseMessage(unit.capture(BiConsumer.class));
            })
            .expect(unit -> {
              OnClose callback = unit.get(OnClose.class);
              callback.onClose(unit.capture(WebSocket.CloseStatus.class));
            })
            .run(unit -> {
              WebSocketImpl ws = new WebSocketImpl(
                  unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces);
              ws.connect(unit.get(Injector.class), unit.get(Request.class),
                  unit.get(NativeWebSocket.class));
              ws.onClose(unit.get(OnClose.class));
            }, unit -> {
              unit.captured(BiConsumer.class).iterator().next()
                  .accept(status.code(), Optional.of(""));
            }, unit -> {
              CloseStatus captured = unit.captured(WebSocket.CloseStatus.class).iterator().next();
              assertEquals(status.code(), captured.code());
              assertEquals(null, captured.reason());
            });
  }

}
