Skip to content

Commit

Permalink
#413: Added additional constructors to HmRqHeader.
Browse files Browse the repository at this point in the history
  • Loading branch information
bdragan committed Nov 14, 2015
1 parent e686052 commit 49af8fa
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 40 deletions.
177 changes: 139 additions & 38 deletions src/main/java/org/takes/facets/hamcrest/HmRqHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -42,31 +46,42 @@
* @author Eugene Kondrashev ([email protected])
* @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<? extends Map.Entry<String,String>> mtchr);
* public HmRsHeader(final String header,
* final Matcher<? extends Iterable<String>> mtchr);
* public HmRsHeader(final String header,
* final Matcher<? extends String> mtchr);
* public HmRsHeader(final String header, final String value);
*/
public final class HmRqHeader extends TypeSafeMatcher<Request> {

/**
* Expected request header matcher.
*/
private final transient Matcher<? extends Map<? extends CharSequence,
? extends CharSequence>> matcher;
private final transient HeaderMatcher matcher;

/**
* Expected matcher.
* @param mtchr Is expected header matcher.
* Ctor.
* @param mtchr Matcher
*/
public HmRqHeader(final Matcher<? extends Map<? extends CharSequence,
? extends CharSequence>> mtchr) {
public HmRqHeader(
final Matcher<? extends Map.Entry<String, String>> 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<? extends Iterable<String>> 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<String, String>(header, value));
}

/**
Expand All @@ -86,38 +101,124 @@ 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<? extends Map.Entry<String, String>>}.
*/
private static class EntryHeaderMatcher implements HeaderMatcher {

/**
* Matcher.
*/
private final transient
Matcher<? extends Map.Entry<String, String>> matcher;

/**
* Ctor.
* @param mtchr Matcher
*/
public EntryHeaderMatcher(
final Matcher<? extends Entry<String, String>> 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;
break;
outer:
for (final String name : headers.names()) {
for (final String value: headers.header(name)) {
final Map.Entry<String, String> entry =
new EntryImpl<String, String>(
name.trim().toLowerCase(Locale.ENGLISH),
value.trim()
);
if (this.matcher.matches(entry)) {
result = true;
break outer;
}
}
}
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<? extends Iterable<String>>}.
*/
private boolean matchHeader(final String name,
final Iterable<String> 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<? extends Iterable<String>> matcher;

/**
* Ctor.
* @param hdr Header
* @param mtchr Matcher
*/
public IterableHeaderMatcher(final String hdr,
final Matcher<? extends Iterable<String>> mtchr) {
this.header = hdr;
this.matcher = mtchr;
}

@Override
public boolean matches(final RqHeaders headers) throws IOException {
final Collection<String> hdrs = new LinkedList<String>();
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);
}
}
}
1 change: 1 addition & 0 deletions src/main/java/org/takes/facets/hamcrest/HmRsHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public boolean matches(final Iterator<String> headers) {
);
if (this.matcher.matches(entry)) {
result = true;
break;
}
}
return result;
Expand Down
61 changes: 59 additions & 2 deletions src/test/java/org/takes/facets/hamcrest/HmRqHeaderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand All @@ -53,7 +55,11 @@ public void testsHeaderAvailable() throws Exception {
),
""
),
new HmRqHeader(Matchers.hasEntry("accept", "text/xml"))
new HmRqHeader(
new EntryMatcher<String, String>(
"accept", "text/xml"
)
)
);
}

Expand All @@ -74,9 +80,60 @@ public void testsHeaderNotAvailable() throws Exception {
),
new HmRqHeader(
Matchers.not(
Matchers.hasEntry("host", "fake.org")
new EntryMatcher<String, String>("host", "fake.org")
)
)
);
}

/**
* HmRqHeader can test header name and value 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 header name and value not available.
* @throws Exception If some problem inside
*/
@Test
public void testsHeaderNameAndValueNotAvailable() throws Exception {
MatcherAssert.assertThat(
new RqWithHeader(new RqFake(), "header2: value2"),
Matchers.not(new HmRqHeader("header2", "value21"))
);
}

/**
* HmRqHeader can test headers available.
* @throws Exception If some problem inside
*/
@Test
public void testsHeadersAvailable() throws Exception {
MatcherAssert.assertThat(
new RqWithHeaders(
new RqFake(),
"header3: value31", "header3: value32"
),
new HmRqHeader("header3", Matchers.<String>iterableWithSize(2))
);
}

/**
* HmRqHeader can test headers not available.
* @throws Exception If some problem inside
*/
@Test
public void testsHeadersNotAvailable() throws Exception {
MatcherAssert.assertThat(
new RqWithHeaders(new RqFake(), "header4: value4"),
new HmRqHeader("header41", Matchers.<String>iterableWithSize(0))
);
}
}

0 comments on commit 49af8fa

Please sign in to comment.