/* * Copyright 2012 Jason Miller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jj.http.server.websocket; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; import static jj.http.server.PipelineStages.*; import static jj.server.ServerLocation.Virtual; import java.util.HashSet; import java.util.Set; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import jj.AnswerWithSelf; import jj.document.DocumentScriptEnvironment; import jj.http.server.HttpServerResponse; import jj.http.server.uri.URIMatch; import jj.resource.ResourceFinder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; /** * @author jason * */ @RunWith(MockitoJUnitRunner.class) public class WebSocketConnectionMakerTest { @Mock WebSocketFrameHandlerCreator handlerCreator; @Mock DocumentScriptEnvironment scriptEnvironment; @Mock ResourceFinder resourceFinder; @Mock Channel channel; @Mock(answer = Answers.RETURNS_DEEP_STUBS) ChannelHandlerContext ctx; @Mock ChannelFuture channelFuture; @Captor ArgumentCaptor<GenericFutureListener<Future<Void>>> futureListenerCaptor; @Mock FullHttpRequest request; HttpServerResponse response; @Mock WebSocketServerHandshakerFactory handshakerFactory; @Mock WebSocketServerHandshaker handshaker; @Mock WebSocketFrameHandler frameHandler; @Captor ArgumentCaptor<WebSocketFrameHandler> channelHandler; @Captor ArgumentCaptor<TextWebSocketFrame> textFrameCaptor; @Captor ArgumentCaptor<CloseWebSocketFrame> closeFrameCaptor; Set<Class<? extends WebSocketConnectionHost>> webSocketConnectionHosts; WebSocketConnectionMaker wscm; public interface TestWebSocketConnectionHost extends WebSocketConnectionHost {} @Before public void before() { response = mock(HttpServerResponse.class, AnswerWithSelf.ANSWER_WITH_SELF); webSocketConnectionHosts = new HashSet<>(); webSocketConnectionHosts.add(TestWebSocketConnectionHost.class); webSocketConnectionHosts.add(DocumentScriptEnvironment.class); wscm = new WebSocketConnectionMaker(handlerCreator, resourceFinder, ctx, request, response, handshakerFactory, webSocketConnectionHosts); } @Test public void testValidConnection() throws Exception { // given given(ctx.channel()).willReturn(channel); given(handshakerFactory.newHandshaker(request)).willReturn(handshaker); given(handshaker.handshake(channel, request)).willReturn(channelFuture); // when wscm.handshakeWebsocket(); // then verify(channelFuture).addListener(futureListenerCaptor.capture()); // given String sha = "1234567890123456789012345678901234567890"; String uri = "/" + sha + "/somethign.socket"; given(scriptEnvironment.sha1()).willReturn(sha); given(request.uri()).willReturn(uri); given(resourceFinder.findResource(DocumentScriptEnvironment.class, Virtual, new URIMatch(uri).name)).willReturn(scriptEnvironment); given(channelFuture.isSuccess()).willReturn(true); // when futureListenerCaptor.getValue().operationComplete(channelFuture); // then verify(handlerCreator).createHandler(handshaker, scriptEnvironment); verify(ctx.pipeline()).replace(eq(JJEngine.toString()), eq(JJWebsocketHandler.toString()), channelHandler.capture()); } @Test public void testBadWebSocketRequest() throws Exception { // given given(handshakerFactory.newHandshaker(request)).willReturn(null); // when wscm.handshakeWebsocket(); // then verify(response).header(HttpHeaderNames.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue()); verify(response).sendError(HttpResponseStatus.UPGRADE_REQUIRED); } @Test public void testObseleteWebSocketConnectionHost() throws Exception { // given given(ctx.channel()).willReturn(channel); given(handshakerFactory.newHandshaker(request)).willReturn(handshaker); given(handshaker.handshake(channel, request)).willReturn(channelFuture); // when wscm.handshakeWebsocket(); // then verify(channelFuture).addListener(futureListenerCaptor.capture()); // given String uri = "/1234567890123456789012345678901234567890/uri.socket"; given(request.uri()).willReturn(uri); given(scriptEnvironment.sha1()).willReturn("ABCDEF"); given(resourceFinder.findResource(eq(DocumentScriptEnvironment.class), eq(Virtual), anyString())).willReturn(scriptEnvironment); given(channelFuture.isSuccess()).willReturn(true); // when futureListenerCaptor.getValue().operationComplete(channelFuture); // then verify(ctx).writeAndFlush(textFrameCaptor.capture()); assertThat(textFrameCaptor.getValue().text(), is("jj-reload")); verify(ctx.writeAndFlush(textFrameCaptor.getValue())).addListener(futureListenerCaptor.capture()); // resetting ctx here to eliminate the earlier verifications we've already done // so our close frame capture works reset(ctx); // when futureListenerCaptor.getValue().operationComplete(channelFuture); // then verify(ctx).writeAndFlush(closeFrameCaptor.capture()); assertThat(closeFrameCaptor.getValue().statusCode(), is(1001)); verify(ctx.writeAndFlush(closeFrameCaptor.getValue())).addListener(ChannelFutureListener.CLOSE); } @Test public void testFailedConnection() throws Exception { // given given(ctx.channel()).willReturn(channel); given(handshakerFactory.newHandshaker(request)).willReturn(handshaker); given(handshaker.handshake(channel, request)).willReturn(channelFuture); // when wscm.handshakeWebsocket(); // then verify(channelFuture).addListener(futureListenerCaptor.capture()); // given given(channelFuture.isSuccess()).willReturn(false); // when futureListenerCaptor.getValue().operationComplete(channelFuture); // then verify(ctx).close(); } }