package org.jooby.internal.netty;

import static io.netty.channel.ChannelFutureListener.CLOSE;
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 java.nio.ByteBuffer;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.jooby.WebSocket;
import org.jooby.test.MockUnit;
import org.jooby.test.MockUnit.Block;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.util.Attribute;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

@RunWith(PowerMockRunner.class)
@PrepareForTest({NettyWebSocket.class, CloseWebSocketFrame.class, Unpooled.class,
    CountDownLatch.class, Thread.class })
public class NettyWebSocketTest {

  private Block channel = unit -> {
    Channel channel = unit.mock(Channel.class);
    unit.registerMock(Channel.class, channel);

    ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
    expect(ctx.channel()).andReturn(channel).times(1, 2);
  };

  private MockUnit.Block close = unit -> {
    Channel ch = unit.get(Channel.class);

    CloseWebSocketFrame frame = unit.mockConstructor(
        CloseWebSocketFrame.class,
        new Class[]{int.class, String.class },
        1001, "normal");

    ChannelFuture future = unit.mock(ChannelFuture.class);
    expect(future.addListener(CLOSE)).andReturn(future);

    WebSocketServerHandshaker handshaker = unit.get(WebSocketServerHandshaker.class);
    expect(handshaker.close(ch, frame)).andReturn(future);
  };

  @SuppressWarnings("unchecked")
  @Test
  public void close() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(channel)
        .expect(close)
        .expect(unit -> {
          Attribute<NettyWebSocket> attr = unit.mock(Attribute.class);
          attr.set(null);

          Channel ctx = unit.get(Channel.class);
          expect(ctx.attr(NettyWebSocket.KEY)).andReturn(attr);
        })
        .run(unit -> {
          new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).close(1001, "normal");
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void closeNoAttr() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(channel)
        .expect(close)
        .expect(unit -> {
          Channel ctx = unit.get(Channel.class);
          expect(ctx.attr(NettyWebSocket.KEY)).andReturn(null);
        })
        .run(unit -> {
          new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).close(1001, "normal");
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void resume() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          ChannelConfig chconf = unit.mock(ChannelConfig.class);
          expect(chconf.isAutoRead()).andReturn(false);
          expect(chconf.setAutoRead(true)).andReturn(chconf);

          Channel ch = unit.mock(Channel.class);
          expect(ch.config()).andReturn(chconf);

          ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
          expect(ctx.channel()).andReturn(ch);
        })
        .run(unit -> {
          new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).resume();
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void resumeIgnored() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          ChannelConfig chconf = unit.mock(ChannelConfig.class);
          expect(chconf.isAutoRead()).andReturn(true);

          Channel ch = unit.mock(Channel.class);
          expect(ch.config()).andReturn(chconf);

          ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
          expect(ctx.channel()).andReturn(ch);
        })
        .run(unit -> {
          new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).resume();
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void pause() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          ChannelConfig chconf = unit.mock(ChannelConfig.class);
          expect(chconf.isAutoRead()).andReturn(true);
          expect(chconf.setAutoRead(false)).andReturn(chconf);

          Channel ch = unit.mock(Channel.class);
          expect(ch.config()).andReturn(chconf);

          ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
          expect(ctx.channel()).andReturn(ch);
        })
        .run(unit -> {
          new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).pause();
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void pauseIgnored() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          ChannelConfig chconf = unit.mock(ChannelConfig.class);
          expect(chconf.isAutoRead()).andReturn(false);

          Channel ch = unit.mock(Channel.class);
          expect(ch.config()).andReturn(chconf);

          ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
          expect(ctx.channel()).andReturn(ch);
        })
        .run(unit -> {
          new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).pause();
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void terminate() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        BiConsumer.class)
            .expect(unit -> {
              BiConsumer<Integer, Optional<String>> callback = unit.get(BiConsumer.class);
              callback.accept(1006, Optional.of("Harsh disconnect"));

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(CLOSE)).andReturn(future);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.disconnect()).andReturn(future);
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.onCloseMessage(unit.get(BiConsumer.class));
              ws.terminate();
            });
  }

  @SuppressWarnings({"unchecked", "rawtypes" })
  @Test
  public void sendBytes() throws Exception {
    ByteBuffer buffer = ByteBuffer.wrap(new byte[]{'a', 'b', 'c' });
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        WebSocket.SuccessCallback.class, WebSocket.OnError.class, Future.class)
            .expect(unit -> {
              ByteBuf byteBuf = unit.mock(ByteBuf.class);

              unit.mockStatic(Unpooled.class);
              expect(Unpooled.wrappedBuffer(buffer)).andReturn(byteBuf);

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(unit.capture(GenericFutureListener.class)))
                  .andReturn(future);

              BinaryWebSocketFrame frame = unit.mockConstructor(BinaryWebSocketFrame.class,
                  new Class[]{ByteBuf.class }, byteBuf);
              Channel ch = unit.mock(Channel.class);
              expect(ch.writeAndFlush(frame)).andReturn(future);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.channel()).andReturn(ch);
            })
            .expect(unit -> {
              Future future = unit.get(Future.class);
              expect(future.isSuccess()).andReturn(true);
              WebSocket.SuccessCallback success = unit.get(WebSocket.SuccessCallback.class);
              success.invoke();
            })
            .run(
                unit -> {
                  NettyWebSocket ws = new NettyWebSocket(
                      unit.get(ChannelHandlerContext.class),
                      unit.get(WebSocketServerHandshaker.class),
                      unit.get(Consumer.class));
                  ws.sendBytes(buffer, unit.get(WebSocket.SuccessCallback.class),
                      unit.get(WebSocket.OnError.class));
                },
                unit -> {
                  GenericFutureListener listener = unit.captured(GenericFutureListener.class)
                      .iterator().next();
                  listener.operationComplete(unit.get(Future.class));
                });
  }

  @SuppressWarnings({"unchecked", "rawtypes" })
  @Test
  public void sendString() throws Exception {
    String data = "abc";
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        WebSocket.SuccessCallback.class, WebSocket.OnError.class, Future.class)
            .expect(unit -> {

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(unit.capture(GenericFutureListener.class)))
                  .andReturn(future);

              TextWebSocketFrame frame = unit.mockConstructor(TextWebSocketFrame.class,
                  new Class[]{String.class }, data);
              Channel ch = unit.mock(Channel.class);
              expect(ch.writeAndFlush(frame)).andReturn(future);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.channel()).andReturn(ch);
            })
            .expect(unit -> {
              Future future = unit.get(Future.class);
              expect(future.isSuccess()).andReturn(true);
              WebSocket.SuccessCallback success = unit.get(WebSocket.SuccessCallback.class);
              success.invoke();
            })
            .run(
                unit -> {
                  NettyWebSocket ws = new NettyWebSocket(
                      unit.get(ChannelHandlerContext.class),
                      unit.get(WebSocketServerHandshaker.class),
                      unit.get(Consumer.class));
                  ws.sendText(data, unit.get(WebSocket.SuccessCallback.class),
                      unit.get(WebSocket.OnError.class));
                },
                unit -> {
                  GenericFutureListener listener = unit.captured(GenericFutureListener.class)
                      .iterator().next();
                  listener.operationComplete(unit.get(Future.class));
                });
  }

  @SuppressWarnings({"unchecked", "rawtypes" })
  @Test
  public void sendBytesFailure() throws Exception {
    ByteBuffer buffer = ByteBuffer.wrap(new byte[]{'a', 'b', 'c' });
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        WebSocket.SuccessCallback.class, WebSocket.OnError.class, Future.class)
            .expect(unit -> {
              ByteBuf byteBuf = unit.mock(ByteBuf.class);

              unit.mockStatic(Unpooled.class);
              expect(Unpooled.wrappedBuffer(buffer)).andReturn(byteBuf);

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(unit.capture(GenericFutureListener.class)))
                  .andReturn(future);

              BinaryWebSocketFrame frame = unit.mockConstructor(BinaryWebSocketFrame.class,
                  new Class[]{ByteBuf.class }, byteBuf);
              Channel ch = unit.mock(Channel.class);
              expect(ch.writeAndFlush(frame)).andReturn(future);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.channel()).andReturn(ch);
            })
            .expect(unit -> {
              Throwable cause = new NullPointerException();
              Future future = unit.get(Future.class);
              expect(future.isSuccess()).andReturn(false);
              expect(future.cause()).andReturn(cause);
              WebSocket.OnError err = unit.get(WebSocket.OnError.class);
              err.onError(cause);
            })
            .run(
                unit -> {
                  NettyWebSocket ws = new NettyWebSocket(
                      unit.get(ChannelHandlerContext.class),
                      unit.get(WebSocketServerHandshaker.class),
                      unit.get(Consumer.class));
                  ws.sendBytes(buffer, unit.get(WebSocket.SuccessCallback.class),
                      unit.get(WebSocket.OnError.class));
                },
                unit -> {
                  GenericFutureListener listener = unit.captured(GenericFutureListener.class)
                      .iterator().next();
                  listener.operationComplete(unit.get(Future.class));
                });
  }

  @SuppressWarnings({"unchecked", "rawtypes" })
  @Test
  public void sendStringFailure() throws Exception {
    String data = "abc";
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        WebSocket.SuccessCallback.class, WebSocket.OnError.class, Future.class)
            .expect(unit -> {

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(unit.capture(GenericFutureListener.class)))
                  .andReturn(future);

              TextWebSocketFrame frame = unit.mockConstructor(TextWebSocketFrame.class,
                  new Class[]{String.class }, data);
              Channel ch = unit.mock(Channel.class);
              expect(ch.writeAndFlush(frame)).andReturn(future);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.channel()).andReturn(ch);
            })
            .expect(unit -> {
              Throwable cause = new NullPointerException();
              Future future = unit.get(Future.class);
              expect(future.isSuccess()).andReturn(false);
              expect(future.cause()).andReturn(cause);
              WebSocket.OnError err = unit.get(WebSocket.OnError.class);
              err.onError(cause);
            })
            .run(
                unit -> {
                  NettyWebSocket ws = new NettyWebSocket(
                      unit.get(ChannelHandlerContext.class),
                      unit.get(WebSocketServerHandshaker.class),
                      unit.get(Consumer.class));
                  ws.sendText(data, unit.get(WebSocket.SuccessCallback.class),
                      unit.get(WebSocket.OnError.class));
                },
                unit -> {
                  GenericFutureListener listener = unit.captured(GenericFutureListener.class)
                      .iterator().next();
                  listener.operationComplete(unit.get(Future.class));
                });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void isOpen() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          Channel ch = unit.mock(Channel.class);
          expect(ch.isOpen()).andReturn(true);

          ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
          expect(ctx.channel()).andReturn(ch);
        })
        .run(unit -> {
          assertEquals(true, new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).isOpen());
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void isNoOpen() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          Channel ch = unit.mock(Channel.class);
          expect(ch.isOpen()).andReturn(false);

          ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
          expect(ctx.channel()).andReturn(ch);
        })
        .run(unit -> {
          assertEquals(false, new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class)).isOpen());
        });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void connect() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        CountDownLatch.class, Runnable.class)
            .expect(unit -> {
              CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
                  new Class[]{int.class }, 1);
              ready.countDown();

              unit.get(Runnable.class).run();
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.onConnect(unit.get(Runnable.class));
              ws.connect();
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void hankshake() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class,
        CountDownLatch.class, Consumer.class)
            .expect(unit -> {

              unit.get(Consumer.class).accept(isA(NettyWebSocket.class));
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.hankshake();
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void handleTextFrame() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        TextWebSocketFrame.class)
            .expect(unit -> {
              CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
                  new Class[]{int.class }, 1);
              ready.await();

              TextWebSocketFrame frame = unit.get(TextWebSocketFrame.class);
              expect(frame.isFinalFragment()).andReturn(true);
              expect(frame.text()).andReturn("text");

              Consumer<String> callback = unit.get(Consumer.class);
              callback.accept("text");
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.onTextMessage(unit.get(Consumer.class));
              ws.handle(unit.get(TextWebSocketFrame.class));
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void handleBinaryFrame() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        BinaryWebSocketFrame.class)
            .expect(unit -> {
              CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
                  new Class[]{int.class }, 1);
              ready.await();

              ByteBuffer nioBuff = ByteBuffer.wrap(new byte[0]);

              ByteBuf buff = unit.mock(ByteBuf.class);
              expect(buff.nioBuffer()).andReturn(nioBuff);

              BinaryWebSocketFrame frame = unit.get(BinaryWebSocketFrame.class);
              expect(frame.isFinalFragment()).andReturn(true);
              expect(frame.content()).andReturn(buff);

              Consumer<ByteBuffer> callback = unit.get(Consumer.class);
              callback.accept(nioBuff);
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.onBinaryMessage(unit.get(Consumer.class));
              ws.handle(unit.get(BinaryWebSocketFrame.class));
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void handleInterruped() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        WebSocketFrame.class)
            .expect(unit -> {
              CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
                  new Class[]{int.class }, 1);
              ready.await();
              expectLastCall().andThrow(new InterruptedException("intentional err"));

              Thread thread = unit.mock(Thread.class);
              thread.interrupt();

              unit.mockStatic(Thread.class);
              expect(Thread.currentThread()).andReturn(thread);
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.handle(unit.get(WebSocketFrame.class));
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void handleCloseFrame() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        CloseWebSocketFrame.class, BiConsumer.class)
            .expect(unit -> {
              CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
                  new Class[]{int.class }, 1);
              ready.await();

              CloseWebSocketFrame retain = unit.get(CloseWebSocketFrame.class);
              expect(retain.statusCode()).andReturn(-1);
              expect(retain.reasonText()).andReturn(null);

              CloseWebSocketFrame frame = unit.get(CloseWebSocketFrame.class);
              expect(frame.retain()).andReturn(retain);

              BiConsumer<Integer, Optional<String>> callback = unit.get(BiConsumer.class);
              callback.accept(1000, Optional.empty());

              Channel ch = unit.mock(Channel.class);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.channel()).andReturn(ch);

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(CLOSE)).andReturn(future);

              WebSocketServerHandshaker handshaker = unit.get(WebSocketServerHandshaker.class);
              expect(handshaker.close(ch, retain)).andReturn(future);
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.onCloseMessage(unit.get(BiConsumer.class));
              ws.handle(unit.get(CloseWebSocketFrame.class));
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void handleCloseWithStatusFrame() throws Exception {
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class,
        CloseWebSocketFrame.class, BiConsumer.class)
            .expect(unit -> {
              CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
                  new Class[]{int.class }, 1);
              ready.await();

              CloseWebSocketFrame retain = unit.get(CloseWebSocketFrame.class);
              expect(retain.statusCode()).andReturn(1001);
              expect(retain.reasonText()).andReturn("normal");

              CloseWebSocketFrame frame = unit.get(CloseWebSocketFrame.class);
              expect(frame.retain()).andReturn(retain);

              BiConsumer<Integer, Optional<String>> callback = unit.get(BiConsumer.class);
              callback.accept(1001, Optional.of("normal"));

              Channel ch = unit.mock(Channel.class);

              ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
              expect(ctx.channel()).andReturn(ch);

              ChannelFuture future = unit.mock(ChannelFuture.class);
              expect(future.addListener(CLOSE)).andReturn(future);

              WebSocketServerHandshaker handshaker = unit.get(WebSocketServerHandshaker.class);
              expect(handshaker.close(ch, retain)).andReturn(future);
            })
            .run(unit -> {
              NettyWebSocket ws = new NettyWebSocket(
                  unit.get(ChannelHandlerContext.class),
                  unit.get(WebSocketServerHandshaker.class),
                  unit.get(Consumer.class));
              ws.onCloseMessage(unit.get(BiConsumer.class));
              ws.handle(unit.get(CloseWebSocketFrame.class));
            });
  }

  @SuppressWarnings("unchecked")
  @Test
  public void handleException() throws Exception {
    Throwable cause = new NullPointerException("intentional err");
    new MockUnit(ChannelHandlerContext.class, WebSocketServerHandshaker.class, Consumer.class)
        .expect(unit -> {
          CountDownLatch ready = unit.mockConstructor(CountDownLatch.class,
              new Class[]{int.class }, 1);
          ready.await();

          Consumer<Throwable> callback = unit.get(Consumer.class);
          callback.accept(cause);
        })
        .run(unit -> {
          NettyWebSocket ws = new NettyWebSocket(
              unit.get(ChannelHandlerContext.class),
              unit.get(WebSocketServerHandshaker.class),
              unit.get(Consumer.class));
          ws.onErrorMessage(unit.get(Consumer.class));
          ws.handle(cause);
        });
  }

}
