package org.hamcrest.xml;

import org.hamcrest.Matcher;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.util.HashSet;
import java.util.Iterator;

import static org.hamcrest.AbstractMatcherTest.*;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.xml.HasXPath.hasXPath;
import static org.junit.Assert.fail;

/**
 
* @author Joe Walnes
 
* @author Tom Denley
 
*/

public final class HasXPathTest {

    
private final Document xml = parse(""
            
+ "<root type='food'>\n"
            
+ " <something id='a'><cheese>Edam</cheese></something>\n"
            
+ " <something id='b'><cheese>Cheddar</cheese></something>\n"
            
+ " <f:foreignSomething xmlns:f=\"http://cheese.com\" milk=\"camel\">Caravane</f:foreignSomething>\n"
            
+ " <emptySomething />\n"
            
+ " <f:emptySomething xmlns:f=\"http://cheese.com\" />"
            
+ "</root>\n"
            
);

    
private final NamespaceContext ns = new NamespaceContext() {
        
@Override
        
public String getNamespaceURI(String prefix) {
            
return ("cheese".equals(prefix) ? "http://cheese.com" : null);
        
}

        
@Override
        
public String getPrefix(String namespaceURI) {
            
return ("http://cheese.com".equals(namespaceURI) ? "cheese" : null);
        
}

        
@Override
        
public Iterator<String> getPrefixes(String namespaceURI) {
            
HashSet<String> prefixes = new HashSet<String>();
            
String prefix = getPrefix(namespaceURI);
            
if (prefix != null) {
                
prefixes.add(prefix);
            
}
            
return prefixes.iterator();
        
}
    
};

    
@Test public void
    
copesWithNullsAndUnknownTypes() {
        
Matcher<Node> matcher = hasXPath("//irrelevant");
        

        
assertNullSafe(matcher);
        
assertUnknownTypeSafe(matcher);
    
}

    
@Test public void
    
appliesMatcherToXPathInDocument() {
        
assertMatches(hasXPath("/root/something[2]/cheese", equalTo("Cheddar")), xml);
        
assertMatches(hasXPath("//something[1]/cheese", containsString("dam")), xml);
        
assertMatches(hasXPath("//something[2]/cheese", not(containsString("dam"))), xml);
        
assertMatches(hasXPath("/root/@type", equalTo("food")), xml);
        
assertMatches(hasXPath("//something[@id='b']/cheese", equalTo("Cheddar")), xml);
        
assertMatches(hasXPath("//something[@id='b']/cheese"), xml);
    
}

    
@Test public void
    
matchesEmptyElement() {
        
assertMatches(hasXPath("//emptySomething"), xml);
    
}

    
@Test public void
    
matchesEmptyElementInNamespace() {
        
assertMatches(hasXPath("//cheese:emptySomething", ns), xml);
    
}

    
@Test public void
    
failsIfNodeIsMissing() {
        
assertDoesNotMatch(hasXPath("/root/something[3]/cheese", ns, equalTo("Cheddar")), xml);
        
assertDoesNotMatch(hasXPath("//something[@id='c']/cheese", ns), xml);
    
}

    
@Test public void
    
failsIfNodeIsMissingInNamespace() {
        
assertDoesNotMatch(hasXPath("//cheese:foreignSomething", equalTo("Badger")), xml);
        
assertDoesNotMatch(hasXPath("//cheese:foreignSomething"), xml);
    
}

    
@Test public void
    
matchesWithNamespace() {
        
assertMatches(hasXPath("//cheese:foreignSomething", ns), xml);
        
assertMatches(hasXPath("//cheese:foreignSomething/@milk", ns, equalTo("camel")), xml);
        
assertMatches(hasXPath("//cheese:foreignSomething/text()", ns, equalTo("Caravane")), xml);
    
}

    
@Test public void
    
throwsIllegalArgumentExceptionIfGivenIllegalExpression() {
        
try {
            
hasXPath("\\g:dfgd::DSgf/root/something[2]/cheese", equalTo("blah"));
            
fail("Expected exception");
        
} catch (IllegalArgumentException expectedException) {
            
// expected exception
        
}
    
}

    
@Test public void
    
describesItself() {
        
assertDescription("an XML document with XPath /some/path \"Cheddar\"",
                          
hasXPath("/some/path", equalTo("Cheddar")));
        

        
assertDescription("an XML document with XPath /some/path",
                          
hasXPath("/some/path"));
    
}

    
@Test public void
    
describesMissingNodeMismatch() {
        
assertMismatchDescription("xpath returned no results.", hasXPath("//honky"), xml);
    
}

    
@Test public void
    
describesIncorrectNodeValueMismatch() {
        
assertMismatchDescription("was \"Edam\"", hasXPath("//something[1]/cheese", equalTo("parmesan")), xml);
    
}

    
private static Document parse(String xml) {
        
try {
            
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            
documentBuilderFactory.setNamespaceAware(true);
            
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            
return documentBuilder.parse(new ByteArrayInputStream(xml.getBytes()));
        
}
        
catch (Exception e) {
            
throw new IllegalStateException(e);
        
}
    
}
}