diff --git a/src/main/java/org/takes/facets/hamcrest/HmRqHeader.java b/src/main/java/org/takes/facets/hamcrest/HmRqHeader.java index b062ad368..52201310f 100644 --- a/src/main/java/org/takes/facets/hamcrest/HmRqHeader.java +++ b/src/main/java/org/takes/facets/hamcrest/HmRqHeader.java @@ -24,12 +24,16 @@ package org.takes.facets.hamcrest; import java.io.IOException; -import java.util.Collections; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.takes.Request; +import org.takes.misc.EntryImpl; import org.takes.rq.RqHeaders; /** @@ -42,31 +46,42 @@ * @author Eugene Kondrashev (eugene.kondrashev@gmail.com) * @version $Id$ * @since 0.23.3 - * @todo #260:30min Implement additional constructors. - * According to #260 there should be also available such constructors: - * public HmRsHeader(final Matcher> mtchr); - * public HmRsHeader(final String header, - * final Matcher> mtchr); - * public HmRsHeader(final String header, - * final Matcher mtchr); - * public HmRsHeader(final String header, final String value); */ public final class HmRqHeader extends TypeSafeMatcher { /** * Expected request header matcher. */ - private final transient Matcher> matcher; + private final transient HeaderMatcher matcher; /** - * Expected matcher. - * @param mtchr Is expected header matcher. + * Ctor. + * @param mtchr Matcher */ - public HmRqHeader(final Matcher> mtchr) { + public HmRqHeader( + final Matcher> mtchr) { super(); - this.matcher = mtchr; + this.matcher = new EntryHeaderMatcher(mtchr); + } + + /** + * Ctor. + * @param header Header name + * @param mtchr Matcher + */ + public HmRqHeader(final String header, + final Matcher> mtchr) { + super(); + this.matcher = new IterableHeaderMatcher(header, mtchr); + } + + /** + * Ctor. + * @param header Header name + * @param value Header value + */ + public HmRqHeader(final String header, final String value) { + this(new EntryMatcher(header, value)); } /** @@ -86,38 +101,126 @@ public void describeTo(final Description description) { @Override public boolean matchesSafely(final Request item) { try { + return this.matcher.matches(new RqHeaders.Base(item)); + } catch (final IOException ex) { + throw new IllegalStateException(ex); + } + } + + /** + * Header matcher. + */ + private interface HeaderMatcher { + + /** + * Performs the matching. + * + * @param headers Headers to check + * @return True if positive match + * @throws IOException If fails + */ + boolean matches(final RqHeaders headers) throws IOException; + + /** + * Generates a description of the matcher. + * + * @param description The description to be built or appended to + */ + void describeTo(final Description description); + } + + /** + * Header matcher for {@code Matcher>}. + */ + private static class EntryHeaderMatcher implements HeaderMatcher { + + /** + * Matcher. + */ + private final transient + Matcher> matcher; + + /** + * Ctor. + * @param mtchr Matcher + */ + public EntryHeaderMatcher( + final Matcher> mtchr) { + this.matcher = mtchr; + } + + @Override + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + public boolean matches(final RqHeaders headers) throws IOException { boolean result = false; - final RqHeaders headers = new RqHeaders.Base(item); - for (final String name: headers.names()) { - if (this.matchHeader(name, headers.header(name))) { - result = true; + for (final String name : headers.names()) { + for (final String value: headers.header(name)) { + final Map.Entry entry = + new EntryImpl( + name.trim().toLowerCase(Locale.ENGLISH), + value.trim() + ); + if (this.matcher.matches(entry)) { + result = true; + break; + } + } + if (result) { break; } } return result; - } catch (final IOException ex) { - throw new IllegalStateException(ex); + } + + @Override + public void describeTo(final Description description) { + this.matcher.describeTo(description); } } /** - * Runs matcher against each found pair. - * @param name Is header name - * @param values Available header values - * @return True when expected type matched. + * Header matcher for {@code Matcher>}. */ - private boolean matchHeader(final String name, - final Iterable values) { - boolean result = false; - for (final String value: values) { - if (this.matcher.matches( - Collections.singletonMap(name, value) - )) { - result = true; - break; + private static class IterableHeaderMatcher implements HeaderMatcher { + + /** + * Header. + */ + private final transient String header; + + /** + * Matcher. + */ + private final transient Matcher> matcher; + + /** + * Ctor. + * @param hdr Header + * @param mtchr Matcher + */ + public IterableHeaderMatcher(final String hdr, + final Matcher> mtchr) { + this.header = hdr; + this.matcher = mtchr; + } + + @Override + public boolean matches(final RqHeaders headers) throws IOException { + final Collection hdrs = new LinkedList(); + for (final String name : headers.names()) { + final String lower = name.trim().toLowerCase(Locale.ENGLISH); + if (lower.equals(this.header)) { + for (final String value : headers.header(name)) { + hdrs.add(value); + } + } } + return this.matcher.matches(hdrs); } - return result; - } + @Override + public void describeTo(final Description description) { + this.matcher.describeTo(description); + } + } } diff --git a/src/main/java/org/takes/facets/hamcrest/HmRsHeader.java b/src/main/java/org/takes/facets/hamcrest/HmRsHeader.java index 02dea94b2..c2de777ef 100644 --- a/src/main/java/org/takes/facets/hamcrest/HmRsHeader.java +++ b/src/main/java/org/takes/facets/hamcrest/HmRsHeader.java @@ -166,6 +166,7 @@ public boolean matches(final Iterator headers) { ); if (this.matcher.matches(entry)) { result = true; + break; } } return result; diff --git a/src/test/java/org/takes/facets/hamcrest/HmRqHeaderTest.java b/src/test/java/org/takes/facets/hamcrest/HmRqHeaderTest.java index 8034613d0..93d657247 100644 --- a/src/test/java/org/takes/facets/hamcrest/HmRqHeaderTest.java +++ b/src/test/java/org/takes/facets/hamcrest/HmRqHeaderTest.java @@ -28,6 +28,8 @@ import org.hamcrest.Matchers; import org.junit.Test; import org.takes.rq.RqFake; +import org.takes.rq.RqWithHeader; +import org.takes.rq.RqWithHeaders; /** * Test case for {@link org.takes.facets.hamcrest.HmRqHeader}. @@ -38,7 +40,7 @@ public final class HmRqHeaderTest { /** - * HmRqHeader can test header available. + * HmRqHeader can test whether a header is available. * @throws Exception If some problem inside */ @Test @@ -53,16 +55,20 @@ public void testsHeaderAvailable() throws Exception { ), "" ), - new HmRqHeader(Matchers.hasEntry("accept", "text/xml")) + new HmRqHeader( + new EntryMatcher( + "accept", "text/xml" + ) + ) ); } /** - * HmRqHeader can test header not available. + * HmRqHeader can test whether a header value is not available. * @throws Exception If some problem inside */ @Test - public void testsHeaderNotAvailable() throws Exception { + public void testsHeaderValueNotAvailable() throws Exception { MatcherAssert.assertThat( new RqFake( Arrays.asList( @@ -74,9 +80,61 @@ public void testsHeaderNotAvailable() throws Exception { ), new HmRqHeader( Matchers.not( - Matchers.hasEntry("host", "fake.org") + new EntryMatcher("host", "fake.org") ) ) ); } + + /** + * HmRqHeader can test whether header name and value are available. + * @throws Exception If some problem inside + */ + @Test + public void testsHeaderNameAndValueAvailable() throws Exception { + MatcherAssert.assertThat( + new RqWithHeader(new RqFake(), "header1: value1"), + new HmRqHeader("header1", "value1") + ); + } + + /** + * HmRqHeader can test whether header name is available + * and value is not available. + * @throws Exception If some problem inside + */ + @Test + public void testsValueNotAvailable() throws Exception { + MatcherAssert.assertThat( + new RqWithHeader(new RqFake(), "header2: value2"), + Matchers.not(new HmRqHeader("header2", "value21")) + ); + } + + /** + * HmRqHeader can test whether multiple headers are available. + * @throws Exception If some problem inside + */ + @Test + public void testsMultipleHeadersAvailable() throws Exception { + MatcherAssert.assertThat( + new RqWithHeaders( + new RqFake(), + "header3: value31", "header3: value32" + ), + new HmRqHeader("header3", Matchers.iterableWithSize(2)) + ); + } + + /** + * HmRqHeader can test whether a header is not available. + * @throws Exception If some problem inside + */ + @Test + public void testsHeaderNotAvailable() throws Exception { + MatcherAssert.assertThat( + new RqWithHeaders(new RqFake(), "header4: value4"), + new HmRqHeader("header41", Matchers.iterableWithSize(0)) + ); + } }