package org.jooby; 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.assertNotNull; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import org.jooby.internal.SseRenderer; 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 com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import javaslang.concurrent.Promise; @RunWith(PowerMockRunner.class) @PrepareForTest({Sse.class, Deferred.class, Executors.class, SseRenderer.class }) public class SseTest { private Block handshake = unit -> { Request request = unit.get(Request.class); Injector injector = unit.get(Injector.class); Route route = unit.get(Route.class); Mutant lastEventId = unit.mock(Mutant.class); expect(route.produces()).andReturn(MediaType.ALL); expect(request.require(Injector.class)).andReturn(injector); expect(request.route()).andReturn(route); expect(request.attributes()).andReturn(ImmutableMap.of()); expect(request.header("Last-Event-ID")).andReturn(lastEventId); expect(injector.getInstance(Renderer.KEY)).andReturn(Sets.newHashSet()); }; private Block locale = unit -> { Request req = unit.get(Request.class); expect(req.locale()).andReturn(Locale.CANADA); }; @Test public void sseId() throws Exception { Sse sse = new Sse() { @Override protected void closeInternal() { } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; assertNotNull(sse.id()); UUID.fromString(sse.id()); sse.close(); } @Test public void handshake() throws Exception { new MockUnit(Request.class, Injector.class, Runnable.class, Route.class) .expect(handshake) .expect(locale) .expect(unit -> { Injector injector = unit.get(Injector.class); expect(injector.getInstance(Key.get(Object.class))).andReturn(null).times(2); expect(injector.getInstance(Key.get(TypeLiteral.get(Object.class)))).andReturn(null); expect(injector.getInstance(Key.get(Object.class, Names.named("n")))).andReturn(null); }) .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.handshake(unit.get(Request.class), unit.get(Runnable.class)); sse.require(Object.class); sse.require(Key.get(Object.class)); sse.require(TypeLiteral.get(Object.class)); sse.require("n", Object.class); sse.close(); }); } @Test public void ifCloseClosedChannel() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.onClose(() -> sse.close()); sse.ifClose(new ClosedChannelException()); latch.await(); }); } @Test public void ifCloseBrokenPipe() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.onClose(() -> sse.close()); sse.ifClose(new IOException("Broken pipe")); latch.await(); }); } @SuppressWarnings("resource") @Test public void ifCloseErrorOnFireClose() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.onClose(() -> { throw new IllegalStateException("intentional err"); }); sse.ifClose(new IOException("Broken pipe")); latch.await(); }); } @Test public void ifCloseFailure() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.onClose(() -> sse.close()); sse.ifClose(new IOException("Broken pipe")); latch.await(); }); } @Test(expected = IllegalStateException.class) public void closeFailure() throws Exception { new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { throw new IllegalStateException("intentional err"); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.close(); }); } @Test public void ifCloseIgnoreIO() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.onClose(() -> sse.close()); sse.ifClose(new IOException("Ignored")); assertEquals(1, latch.getCount()); }); } @Test public void ifCloseIgnoreEx() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { return null; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.onClose(() -> sse.close()); sse.ifClose(new IllegalArgumentException("Ignored")); assertEquals(1, latch.getCount()); }); } @Test public void sseHandlerSuccess() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit(Request.class, Response.class, Route.Chain.class, Sse.class) .expect(unit -> { Request req = unit.get(Request.class); Sse sse = unit.get(Sse.class); sse.handshake(eq(unit.get(Request.class)), unit.capture(Runnable.class)); expect(req.require(Sse.class)).andReturn(sse); expect(req.path()).andReturn("/sse"); }) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.send(unit.capture(Deferred.class)); }) .run(unit -> { Sse.Handler handler = (req, sse) -> { latch.countDown(); }; handler.handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); }, unit -> { Deferred deferred = unit.captured(Deferred.class).iterator().next(); deferred.handler(null, (value, ex) -> { }); unit.captured(Runnable.class).iterator().next().run(); latch.await(); }); } @Test public void sseHandlerFailure() throws Exception { new MockUnit(Request.class, Response.class, Sse.class, Route.Chain.class) .expect(unit -> { Request req = unit.get(Request.class); Sse sse = unit.get(Sse.class); sse.handshake(eq(unit.get(Request.class)), unit.capture(Runnable.class)); expect(req.require(Sse.class)).andReturn(sse); expect(req.path()).andReturn("/sse"); }) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.send(unit.capture(Deferred.class)); }) .run(unit -> { Sse.Handler handler = (req, sse) -> { throw new IllegalStateException("intentional err"); }; handler.handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); }, unit -> { Deferred deferred = unit.captured(Deferred.class).iterator().next(); deferred.handler(null, (value, ex) -> { }); unit.captured(Runnable.class).iterator().next().run(); }); } @Test public void sseHandlerHandshakeFailure() throws Exception { new MockUnit(Request.class, Response.class, Sse.class, Route.Chain.class) .expect(unit -> { Request req = unit.get(Request.class); Sse sse = unit.get(Sse.class); sse.handshake(eq(unit.get(Request.class)), unit.capture(Runnable.class)); expectLastCall().andThrow(new IllegalStateException("intentional error")); expect(req.require(Sse.class)).andReturn(sse); expect(req.path()).andReturn("/sse"); }) .expect(unit -> { Response rsp = unit.get(Response.class); rsp.send(unit.capture(Deferred.class)); }) .run(unit -> { Sse.Handler handler = (req, sse) -> { }; handler.handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); }, unit -> { Deferred deferred = unit.captured(Deferred.class).iterator().next(); deferred.handler(null, (value, ex) -> { }); }); } @Test public void sseKeepAlive() throws Exception { CountDownLatch latch = new CountDownLatch(1); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { Promise<Optional<Object>> promise = Promise .make(MoreExecutors.newDirectExecutorService()); promise.success(id); return promise; } @Override public Sse keepAlive(final long millis) { assertEquals(100, millis); latch.countDown(); return this; } @Override protected void handshake(final Runnable handler) throws Exception { } }; new Sse.KeepAlive(sse, 100).run(); latch.await(); }); } @SuppressWarnings("resource") @Test public void renderFailure() throws Exception { CountDownLatch latch = new CountDownLatch(1); Object data = new Object(); new MockUnit(Request.class, Route.class, Injector.class, Runnable.class) .expect(handshake) .expect(locale) .expect(unit -> { SseRenderer renderer = unit.constructor(SseRenderer.class) .args(List.class, List.class, Charset.class, Locale.class, Map.class) .build(isA(List.class), isA(List.class), eq(StandardCharsets.UTF_8), eq(Locale.CANADA), isA(Map.class)); expect(renderer.format(isA(Sse.Event.class))).andThrow(new IOException("failure")); }) .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { } @Override protected void fireCloseEvent() { } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { Promise<Optional<Object>> promise = Promise .make(MoreExecutors.newDirectExecutorService()); promise.failure(new IOException("intentional err")); return promise; } @Override public Sse keepAlive(final long millis) { return this; } @Override protected void handshake(final Runnable handler) throws Exception { } }; sse.handshake(unit.get(Request.class), unit.get(Runnable.class)); sse.event(data).type(MediaType.all).send().onFailure(cause -> latch.countDown()); latch.await(); }); } @Test public void sseKeepAliveFailure() throws Exception { CountDownLatch latch = new CountDownLatch(2); new MockUnit() .run(unit -> { Sse sse = new Sse() { @Override protected void closeInternal() { latch.countDown(); } @Override protected void fireCloseEvent() { latch.countDown(); } @Override protected Promise<Optional<Object>> send(final Optional<Object> id, final byte[] data) { Promise<Optional<Object>> promise = Promise .make(MoreExecutors.newDirectExecutorService()); promise.failure(new IOException("intentional err")); return promise; } @Override public Sse keepAlive(final long millis) { return this; } @Override protected void handshake(final Runnable handler) throws Exception { } }; new Sse.KeepAlive(sse, 100).run(); latch.await(); }); } }