From 113b8e8dec7aeb1eb8a1c1f0944cc91af73a1e59 Mon Sep 17 00:00:00 2001 From: Fabricio Cabral Date: Thu, 3 Jan 2019 01:40:15 -0300 Subject: [PATCH 1/2] #866 ResponseOf unit tests --- .../servlet/HttpServletResponseFake.java | 320 ++++++++++++++++++ .../java/org/takes/servlet/ResponseOf.java | 3 - src/main/java/org/takes/servlet/RqFrom.java | 2 + .../takes/servlet/ServletOutputStreamTo.java | 75 ++++ .../servlet/HttpServletResponseFakeTest.java | 134 ++++++++ .../org/takes/servlet/ResponseOfTest.java | 82 +++++ .../java/org/takes/servlet/package-info.java | 30 ++ 7 files changed, 643 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/takes/servlet/HttpServletResponseFake.java create mode 100644 src/main/java/org/takes/servlet/ServletOutputStreamTo.java create mode 100644 src/test/java/org/takes/servlet/HttpServletResponseFakeTest.java create mode 100644 src/test/java/org/takes/servlet/ResponseOfTest.java create mode 100644 src/test/java/org/takes/servlet/package-info.java diff --git a/src/main/java/org/takes/servlet/HttpServletResponseFake.java b/src/main/java/org/takes/servlet/HttpServletResponseFake.java new file mode 100644 index 000000000..c86fbe2eb --- /dev/null +++ b/src/main/java/org/takes/servlet/HttpServletResponseFake.java @@ -0,0 +1,320 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.servlet; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.cactoos.io.InputOf; +import org.cactoos.io.LengthOf; +import org.cactoos.io.OutputTo; +import org.cactoos.io.TeeInput; +import org.cactoos.iterable.Filtered; +import org.cactoos.list.ListOf; +import org.takes.Response; +import org.takes.facets.cookies.RsWithCookie; +import org.takes.misc.EnglishLowerCase; +import org.takes.rs.RsWithHeader; +import org.takes.rs.RsWithStatus; +import org.takes.rs.RsWithoutHeader; + +/** + * Fake HttpServletResponse (for unit tests). + * + * @since 1.14 + * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) + */ +@SuppressWarnings("PMD.TooManyMethods") +public final class HttpServletResponseFake implements HttpServletResponse { + /** + * A Takes response. + */ + private final AtomicReference response; + + /** + * Ctor. + * @param resp A Takes Response object + */ + public HttpServletResponseFake(final Response resp) { + this.response = new AtomicReference<>(resp); + } + + @Override + public void addCookie(final Cookie cookie) { + this.response.set( + new RsWithCookie( + this.response.get(), + cookie.getName(), + cookie.getValue() + ) + ); + } + + @Override + public void setHeader(final String name, final String value) { + this.response.set( + new RsWithHeader( + new RsWithoutHeader( + this.response.get(), + name + ), + name, + value + ) + ); + } + + @Override + public void setStatus(final int code) { + this.response.set( + new RsWithStatus( + this.response.get(), + code + ) + ); + } + + @Override + public void sendError( + final int code, + final String reason + ) throws IOException { + this.response.set( + new RsWithStatus( + this.response.get(), + code, + reason + ) + ); + } + + @Override + public Collection getHeaders(final String header) { + final String prefix = String.format( + "%s:", new EnglishLowerCase(header) + ); + try { + return new ListOf<>( + new Filtered<>( + hdr -> !new EnglishLowerCase(hdr).string() + .startsWith(prefix), + this.response.get().head() + ) + ); + } catch (final IOException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new LengthOf( + new TeeInput( + new InputOf(this.response.get().body()), + new OutputTo(baos) + ) + ).intValue(); + return new ServletOutputStreamTo(baos); + } + + @Override + public String getHeader(final String header) { + throw new UnsupportedOperationException("#getHeaders()"); + } + + @Override + public boolean containsHeader(final String header) { + throw new UnsupportedOperationException("#containsHeader()"); + } + + @Override + public String encodeURL(final String url) { + throw new UnsupportedOperationException("#encodeURL()"); + } + + @Override + public String encodeRedirectURL(final String url) { + throw new UnsupportedOperationException("#encodeRedirectURL()"); + } + + /** + * Encode a URL. + * @deprecated It not should be used + * @param url URL to be encoded + * @return The encoded URL + */ + @Deprecated + public String encodeUrl(final String url) { + throw new UnsupportedOperationException("#encodeUrl()"); + } + + /** + * Encode a redirect URL. + * @deprecated It not should be used + * @param url URL to be encoded + * @return The encoded redirect URL + */ + @Deprecated + public String encodeRedirectUrl(final String url) { + throw new UnsupportedOperationException("#encodeRedirectUrl()"); + } + + @Override + public void sendError(final int code) throws IOException { + throw new UnsupportedOperationException("#sendError()"); + } + + @Override + public void sendRedirect(final String location) throws IOException { + throw new UnsupportedOperationException("#sendRedirect()"); + } + + @Override + public void setDateHeader(final String name, final long time) { + throw new UnsupportedOperationException("#setDateHeader()"); + } + + @Override + public void addDateHeader(final String name, final long date) { + throw new UnsupportedOperationException("#addDateHeader()"); + } + + @Override + public void addHeader(final String name, final String value) { + throw new UnsupportedOperationException("#addHeader()"); + } + + @Override + public void setIntHeader(final String name, final int value) { + throw new UnsupportedOperationException("#setIntHeader()"); + } + + @Override + public void addIntHeader(final String name, final int value) { + throw new UnsupportedOperationException("#addIntHeader()"); + } + + /** + * Set the response code status and reason. + * @deprecated It not should be used + * @param code The status code + * @param reason The reason originates this code + */ + @Deprecated + public void setStatus(final int code, final String reason) { + throw new UnsupportedOperationException("#setStatus()"); + } + + @Override + public int getStatus() { + throw new UnsupportedOperationException("#getStatus()"); + } + + @Override + public Collection getHeaderNames() { + throw new UnsupportedOperationException("#getHeaderNames()"); + } + + @Override + public String getCharacterEncoding() { + throw new UnsupportedOperationException("#getCharacterEncoding()"); + } + + @Override + public String getContentType() { + throw new UnsupportedOperationException("#getContentType()"); + } + + @Override + public PrintWriter getWriter() throws IOException { + throw new UnsupportedOperationException("#getWriter()"); + } + + @Override + public void setCharacterEncoding(final String encoding) { + throw new UnsupportedOperationException("#setCharacterEncoding()"); + } + + @Override + public void setContentLength(final int length) { + throw new UnsupportedOperationException("#setContentLength()"); + } + + @Override + public void setContentLengthLong(final long length) { + throw new UnsupportedOperationException("#setContentLengthLong()"); + } + + @Override + public void setContentType(final String type) { + throw new UnsupportedOperationException("#setContentType()"); + } + + @Override + public void setBufferSize(final int size) { + throw new UnsupportedOperationException("#setBufferSize()"); + } + + @Override + public int getBufferSize() { + throw new UnsupportedOperationException("#getBufferSize()"); + } + + @Override + public void flushBuffer() throws IOException { + throw new UnsupportedOperationException("#flushBuffer()"); + } + + @Override + public void resetBuffer() { + throw new UnsupportedOperationException("#resetBuffer()"); + } + + @Override + public boolean isCommitted() { + throw new UnsupportedOperationException("#isCommited()"); + } + + @Override + public void reset() { + throw new UnsupportedOperationException("#reset()"); + } + + @Override + public void setLocale(final Locale locale) { + throw new UnsupportedOperationException("#setLocale()"); + } + + @Override + public Locale getLocale() { + throw new UnsupportedOperationException("#getLocale()"); + } +} diff --git a/src/main/java/org/takes/servlet/ResponseOf.java b/src/main/java/org/takes/servlet/ResponseOf.java index 075cfcaf5..781ddf0fa 100644 --- a/src/main/java/org/takes/servlet/ResponseOf.java +++ b/src/main/java/org/takes/servlet/ResponseOf.java @@ -39,9 +39,6 @@ * Takes response as servlet response. * * @since 2.0 - * @todo #682:30min Servlet request and response adapters are not unit-tested. - * There should be tests for reading headers and body from servlet request - * and test for validating servlet response after applying takes request. */ final class ResponseOf { /** diff --git a/src/main/java/org/takes/servlet/RqFrom.java b/src/main/java/org/takes/servlet/RqFrom.java index f29615227..d3b12a24e 100644 --- a/src/main/java/org/takes/servlet/RqFrom.java +++ b/src/main/java/org/takes/servlet/RqFrom.java @@ -36,6 +36,8 @@ * Request from {@link HttpServletRequest}. * * @since 2.0 + * @todo #866:30min Servlet request adapter is not unit-tested. + * There should be tests for reading headers and body from servlet request. */ final class RqFrom implements Request { /** diff --git a/src/main/java/org/takes/servlet/ServletOutputStreamTo.java b/src/main/java/org/takes/servlet/ServletOutputStreamTo.java new file mode 100644 index 000000000..f22176059 --- /dev/null +++ b/src/main/java/org/takes/servlet/ServletOutputStreamTo.java @@ -0,0 +1,75 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.servlet; + +import java.io.IOException; +import java.io.OutputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +/** + * ServletOutputStreamTo. + * + * @since 1.14 + */ +public final class ServletOutputStreamTo extends ServletOutputStream { + /** + * Target. + */ + private final OutputStream target; + + /** + * Ctor. + * @param output The encapsulated OutputStream + */ + public ServletOutputStreamTo(final OutputStream output) { + super(); + this.target = output; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(final WriteListener listener) { + throw new UnsupportedOperationException("#setWriteListener()"); + } + + @Override + public void write(final int data) throws IOException { + this.target.write(data); + } + + @Override + public void flush() throws IOException { + this.target.flush(); + } + + @Override + public void close() throws IOException { + this.target.close(); + } +} diff --git a/src/test/java/org/takes/servlet/HttpServletResponseFakeTest.java b/src/test/java/org/takes/servlet/HttpServletResponseFakeTest.java new file mode 100644 index 000000000..9a171e043 --- /dev/null +++ b/src/test/java/org/takes/servlet/HttpServletResponseFakeTest.java @@ -0,0 +1,134 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.takes.servlet; + +import java.io.IOException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.cactoos.text.FormattedText; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.takes.rs.RsEmpty; +import org.takes.rs.RsWithHeader; + +/** + * Test case for {@link HttpServletResponseFake}. + * + * @since 1.14 + */ +@SuppressWarnings("PMD.AvoidDuplicateLiterals") +public final class HttpServletResponseFakeTest { + @Test + public void cookie() throws Exception { + final String name = "foo"; + final String value = "bar"; + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsEmpty() + ); + sresp.addCookie(new Cookie(name, value)); + MatcherAssert.assertThat( + "Can't add a cookie in servlet response", + sresp.getHeaders(name), + Matchers.hasItem( + new FormattedText( + "Set-Cookie: %s=%s;", + name, + value + ).asString() + ) + ); + } + + @Test + public void addHeader() throws Exception { + final String name = "oba"; + final String value = "abo"; + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsEmpty() + ); + sresp.setHeader(name, value); + MatcherAssert.assertThat( + "Can't add a new header in servlet response", + sresp.getHeaders(name), + Matchers.hasItem( + new FormattedText( + "%s: %s", + name, + value + ).asString() + ) + ); + } + + @Test + public void changeHeader() throws Exception { + final String name = "marco"; + final String value = "polo"; + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsWithHeader(name, value) + ); + sresp.setHeader(name, value); + MatcherAssert.assertThat( + "Can't change a header value in servlet response", + sresp.getHeaders(name), + Matchers.hasItem( + new FormattedText( + "%s: %s", + name, + value + ).asString() + ) + ); + } + + @Test + public void status() { + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsEmpty() + ); + // @checkstyle MagicNumber (1 line) + sresp.setStatus(502); + MatcherAssert.assertThat( + "Can't set a status in servlet response", + sresp.getHeaders("Status"), + Matchers.hasItem("HTTP/1.1 502 Bad Gateway") + ); + } + + @Test + public void sendError() throws IOException { + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsEmpty() + ); + // @checkstyle MagicNumber (1 line) + sresp.sendError(101, "Custom error message"); + MatcherAssert.assertThat( + "Can't send a error in servlet response", + sresp.getHeaders("Status"), + Matchers.hasItem("HTTP/1.1 101 Custom error message") + ); + } +} diff --git a/src/test/java/org/takes/servlet/ResponseOfTest.java b/src/test/java/org/takes/servlet/ResponseOfTest.java new file mode 100644 index 000000000..768b25415 --- /dev/null +++ b/src/test/java/org/takes/servlet/ResponseOfTest.java @@ -0,0 +1,82 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.servlet; + +import javax.servlet.http.HttpServletResponse; +import org.cactoos.text.FormattedText; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.takes.facets.cookies.RsWithCookie; +import org.takes.rs.RsEmpty; +import org.takes.rs.RsWithHeader; + +/** + * Test case for {@link ResponseOf}. + * + * @since 1.14 + */ +public final class ResponseOfTest { + @Test + public void header() throws Exception { + final String name = "fabricio"; + final String value = "cabral"; + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsEmpty() + ); + new ResponseOf(new RsWithHeader(name, value)).applyTo(sresp); + MatcherAssert.assertThat( + "Can't add a header to servlet response", + sresp.getHeaders(name), + Matchers.hasItem( + new FormattedText( + "%s: %s", + name, + value + ).asString() + ) + ); + } + + @Test + public void cookie() throws Exception { + final String name = "paulo"; + final String value = "damaso"; + final HttpServletResponse sresp = new HttpServletResponseFake( + new RsEmpty() + ); + new ResponseOf(new RsWithCookie(name, value)).applyTo(sresp); + MatcherAssert.assertThat( + "Can't add a cookie to servlet response", + sresp.getHeaders("set-cookie"), + Matchers.hasItem( + new FormattedText( + "Set-Cookie: %s=%s;", + name, + value + ).asString() + ) + ); + } +} diff --git a/src/test/java/org/takes/servlet/package-info.java b/src/test/java/org/takes/servlet/package-info.java new file mode 100644 index 000000000..7cd65e973 --- /dev/null +++ b/src/test/java/org/takes/servlet/package-info.java @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2018 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Servlet, tests. + * + * @since 1.14 + */ +package org.takes.servlet; From ab2d8d6f9918a8ce8c969af480edc234f074973c Mon Sep 17 00:00:00 2001 From: Fabricio Cabral Date: Thu, 3 Jan 2019 01:48:51 -0300 Subject: [PATCH 2/2] #866 Added more details to puzzle description --- src/main/java/org/takes/servlet/RqFrom.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/takes/servlet/RqFrom.java b/src/main/java/org/takes/servlet/RqFrom.java index d3b12a24e..d5b2e20db 100644 --- a/src/main/java/org/takes/servlet/RqFrom.java +++ b/src/main/java/org/takes/servlet/RqFrom.java @@ -38,6 +38,7 @@ * @since 2.0 * @todo #866:30min Servlet request adapter is not unit-tested. * There should be tests for reading headers and body from servlet request. + * See https://github.com/yegor256/takes/pull/865 discussion for details. */ final class RqFrom implements Request { /**