package org.jooby.internal.jetty; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import javax.servlet.AsyncContext; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; 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; @RunWith(PowerMockRunner.class) @PrepareForTest({JettySse.class, Executors.class }) public class JettySseTest { private Block httpOutput = unit -> { Response rsp = unit.get(Response.class); expect(rsp.getHttpOutput()).andReturn(unit.get(HttpOutput.class)); }; @Test public void defaults() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .run(unit -> { new JettySse(unit.get(Request.class), unit.get(Response.class)); }); } @Test public void send() throws Exception { byte[] bytes = {0 }; CountDownLatch latch = new CountDownLatch(1); new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .expect(write(bytes)) .run(unit -> { new JettySse(unit.get(Request.class), unit.get(Response.class)) .send(Optional.of("1"), bytes).future().onSuccess(id -> { assertEquals("1", id.get()); latch.countDown(); }); latch.await(); }); } @Test public void sendFailure() throws Exception { byte[] bytes = {0 }; IOException cause = new IOException("intentional error"); CountDownLatch latch = new CountDownLatch(1); new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .expect(unit -> { HttpOutput output = unit.get(HttpOutput.class); output.write(bytes); expectLastCall().andThrow(cause); }) .run(unit -> { new JettySse(unit.get(Request.class), unit.get(Response.class)) .send(Optional.of("1"), bytes).future().onFailure(ex -> { assertEquals(cause, ex); latch.countDown(); }); latch.await(); }); } @Test public void handshake() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class, Runnable.class, AsyncContext.class, HttpChannel.class, Connector.class, Executor.class) .expect(httpOutput) .expect(unit -> { AsyncContext async = unit.get(AsyncContext.class); async.setTimeout(0L); Request req = unit.get(Request.class); expect(req.getAsyncContext()).andReturn(async); }) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.setStatus(200); rsp.setHeader("Connection", "Close"); rsp.setContentType("text/event-stream; charset=utf-8"); rsp.flushBuffer(); HttpChannel channel = unit.get(HttpChannel.class); expect(rsp.getHttpChannel()).andReturn(channel); Connector connector = unit.get(Connector.class); expect(channel.getConnector()).andReturn(connector); Executor executor = unit.get(Executor.class); expect(connector.getExecutor()).andReturn(executor); executor.execute(unit.get(Runnable.class)); }) .run(unit -> { new JettySse(unit.get(Request.class), unit.get(Response.class)) .handshake(unit.get(Runnable.class)); }); } @SuppressWarnings("resource") @Test public void shouldCloseEof() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .run(unit -> { JettySse sse = new JettySse(unit.get(Request.class), unit.get(Response.class)); assertEquals(true, sse.shouldClose(new EofException())); }); } @SuppressWarnings("resource") @Test public void shouldCloseBrokenPipe() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .run(unit -> { JettySse sse = new JettySse(unit.get(Request.class), unit.get(Response.class)); assertEquals(true, sse.shouldClose(new IOException("broken pipe"))); }); } @Test public void close() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.closeOutput(); }) .run(unit -> { JettySse sse = new JettySse(unit.get(Request.class), unit.get(Response.class)); sse.close(); }); } @Test public void ignoreClosedStream() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.closeOutput(); }) .run(unit -> { JettySse sse = new JettySse(unit.get(Request.class), unit.get(Response.class)); sse.close(); sse.close(); }); } @Test public void closeFailure() throws Exception { new MockUnit(Request.class, Response.class, HttpOutput.class) .expect(httpOutput) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.closeOutput(); expectLastCall().andThrow(new EofException("intentional err")); }) .run(unit -> { JettySse sse = new JettySse(unit.get(Request.class), unit.get(Response.class)); sse.close(); }); } private Block write(final byte[] bytes) { return unit -> { HttpOutput output = unit.get(HttpOutput.class); output.write(bytes); output.flush(); }; } }