/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.stanbol.entityhub.test.it;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.stanbol.commons.testing.http.RequestExecutor;
import org.apache.stanbol.entityhub.servicesapi.defaults.NamespaceEnum;
import org.apache.stanbol.entityhub.test.query.QueryTestCase;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utilities for asserting JSON responses encoded in the Entityhub JSON format.
* The intended usage is similar to Ass
* @author Rupert Westenthaler.
*
*/
public class AssertEntityhubJson {
private final static Logger log = LoggerFactory.getLogger(AssertEntityhubJson.class);
private AssertEntityhubJson(){/*noinstances*/}
/**
* Validates Results of a Query (/find or /query requests) based on the
* data defined by the test case
* @param re the {@link RequestExecutor} used for the test case
* @param test the query test case
* @throws JSONException in case the {@link RequestExecutor#getContent()} are
* no valid JSON. NOTE that the contents are only parsed if the
* {@link QueryTestCase#getExpectedStatus()} is a 2xx status code.
*/
public static void assertQueryResults(RequestExecutor re, QueryTestCase test) throws JSONException{
re.assertStatus(test.getExpectedStatus());
re.assertContentType("application/json"); //currently only application/json is supported
if(!test.expectsSuccess()){
return; //no further checks for tests that expect failure
}
JSONObject jso = new JSONObject(re.getContent());
JSONArray results = jso.getJSONArray("results");
if(test.expectesResults()){
assertTrue("Missing Results for Query: \n "+test+
"\n Result:\n "+results.toString(4),
results.length() > 0);
} else {
assertTrue("Unexpected Results for Query:\n "+test+
"\n Result:\n "+results.toString(4),
results.length() == 0);
}
Set<String> expectedIds;
if(test.getExpectedResultIds() != null && !test.getExpectedResultIds().isEmpty()){
expectedIds = new HashSet<String>(test.getExpectedResultIds());
} else {
expectedIds = null;
}
//iterate over the results
//General NOTE:
// use opt**(..) methods to avoid JSON Exception. We want to parse
// everything and than do asserts!
for(int i=0;i<results.length();i++){
JSONObject result = results.getJSONObject(i);
String id = result.optString("id", null);
log.info("({}) {}",i,id);
assertNotNull("ID missing for an Result", id);
if(expectedIds != null){
expectedIds.remove(id); //not all results must be in the list
}
if(test.getProhibitedResultIds() != null){
assertFalse("Prohibited Result '"+id+"' found!",
test.getProhibitedResultIds().contains(id));
}
assertRepresentation(result,test.getRequiredFields(),test.getAllowedFields());
}
if(expectedIds != null){ // if there where expected results check that all where found
assertTrue("The following expected results where missing in the Response: \n "+expectedIds,
expectedIds.isEmpty());
}
}
/**
* Asserts that the Query is present in the response and if so returns the
* query
* @param content the returned content
* @return the query as contained in the response
* @throws JSONException on any Error while parsing the JSON query from the
* parsed content
*/
public static JSONObject assertResponseQuery(String content) throws JSONException {
assertNotNull("The content of the Response is NULL",content);
JSONObject jResult = new JSONObject(content);
JSONObject jQuery = jResult.optJSONObject("query");
assertNotNull("Result does not contain the processed Query",jQuery);
return jQuery;
}
/**
* Asserts that the selected JSONArray of the field query returned within
* the result list contains parsed selected fields
* @param jQuery the query e.g. as returned by
* {@link #assertQueryResults(RequestExecutor, QueryTestCase)}
* @return the selected fields for further processing
* @throws JSONException on any error while parsing the JSON
*/
public static JSONArray assertSelectedField(JSONObject jQuery,String...selected) throws JSONException {
Set<String> selectedSet = new HashSet<String>();
if(selected == null || selected.length == 0) {
selectedSet = Collections.emptySet();
} else {
selectedSet = new HashSet<String>(Arrays.asList(selected));
}
JSONArray jSelected = jQuery.optJSONArray("selected");
assertNotNull("Result Query is missing the 'selected' property",jSelected);
assertTrue("Result Query is expected to have at least a single selected field",
jSelected.length() > 0);
boolean found = false;
for(int i=0;i<jSelected.length() && !found;i++){
String selectedField = jSelected.optString(i,null);
assertNotNull("Selected array contains a NULL element \n"+jSelected.toString(4),
selectedField);
selectedSet.remove(selectedField);
}
assertTrue("Fields "+selectedSet+" are not selected by\n"+jSelected.toString(4),
selectedSet.isEmpty());
return jSelected;
}
/**
* Asserts that the parsed Stirng is an valid Entity. This Method only tests
* for the Entity properties use
* {@link #assertRepresentation(JSONObject, Collection, Collection)} to check for
* required/optional fields of the representation and metadata
* @param content the content e.g. as returned by an request to the
* Entityhub
* @param to test the id or <code>null</code> to skip this test
* @param to test the site or <code>null</code> to skip this test
* @return the parsed Entity typically used for further tests
*/
public static JSONObject assertEntity(String content,String id, String site) throws JSONException {
assertNotNull("The content to parse the Entity from is NULL",content);
JSONObject jEntity = new JSONObject(content);
if(id != null){
assertEquals("Entity has the wrong ID", id,jEntity.optString("id", null));
} else {
id = jEntity.optString("id", null);
assertNotNull("ID of the Entity MUST NOT be NULL",id);
}
if(site != null){
assertEquals("Entity has the wrong Site", site, jEntity.optString("site",null));
}
assertTrue("Representation is missing",jEntity.has("representation"));
JSONObject jRepresentation = jEntity.getJSONObject("representation");
assertNotNull("Representation is not an JSON Object",jRepresentation);
assertEquals("Representation MUST have the same ID as the the Entity",
id,jEntity.optString("id", null));
assertTrue("Metadata are missing",jEntity.has("metadata"));
JSONObject jMetadata = jEntity.getJSONObject("metadata");
assertNotNull("Metadata is not an JSON Object",jMetadata);
Collection<String> requiredMetadata;
if("entityhub".equals(site)){
requiredMetadata = Arrays.asList(
//NamespaceEnum.entityhub+"isChached", not used by the entityhub
NamespaceEnum.entityhub+"about",
NamespaceEnum.rdf+"type");
} else {
requiredMetadata = Arrays.asList(
NamespaceEnum.entityhub+"isChached",
NamespaceEnum.entityhub+"about",
NamespaceEnum.rdf+"type");
}
Map<String,Set<List<String>>> metadata = assertRepresentation(jMetadata, requiredMetadata ,null);
assertTrue("The Metadata of an Entity MUST BE about the Entity",
metadata.get(NamespaceEnum.entityhub+"about")
.contains(Arrays.asList(id,"xsd:anyURI")));
return jEntity;
}
/**
* Asserts a JSONObject that represents a Representation for allowed and
* required fields. Any field that is not required or optional will cause
* an assertion to fail. Any required field that is missing will also fail
* this test.
* @param jRepresentation
* @param required
* @param optional
* @return the values of the Representation with the fields as key and the
* values as value. The values contain the string value at index '0' and the
* xsd:datatype or xml:lang or <code>null</code> (if none) at index '1'
*/
public static Map<String,Set<List<String>>> assertRepresentation(JSONObject jRepresentation, Collection<String> required,Collection<String> optional) throws JSONException {
Set<String> checkRequiredFields = new HashSet<String>(); //copy over the required fields
if(required != null && !required.isEmpty()){
checkRequiredFields.addAll(required);
}
checkRequiredFields.add("id"); //the "id" is required by all representations
Map<String,Set<List<String>>> valueMap = new HashMap<String,Set<List<String>>>();
for(Iterator<?> keys = jRepresentation.keys(); keys.hasNext();){
Object key = keys.next();
assertFalse("Duplicate 'field' "+key,valueMap.containsKey(key));
if(checkRequiredFields == null || !checkRequiredFields.remove(key)){
//process key
if(optional != null){
assertTrue("Field "+key+" is not an expected one: \n" +
" required: "+required+"\n"+
" optional: "+optional+"\n"+
" representation: "+jRepresentation.toString(4),
optional.contains(key));
}
}
if(!"id".equals(key)){
Set<List<String>> values = new HashSet<List<String>>();
JSONArray jValues = jRepresentation.getJSONArray(key.toString());
assertTrue("Fields MUST contain at least a single value!",jValues.length() > 0);
for(int i=0;i<jValues.length();i++){
JSONObject fieldValue = jValues.optJSONObject(i);
assertNotNull("Values for field "+key+" does contain an value " +
"that is not an JSONObject "+jValues.optString(i),
fieldValue);
String[] value = new String[2];
value[0] = fieldValue.optString("value");
value[1] = null;
assertNotNull("All Field-values MUST have the 'value' property",value[0]);
assertFalse("All Field-values MUST not be empty",value[0].isEmpty());
if(fieldValue.has("xsd:datatype")){
value[1] = fieldValue.getString("xsd:datatype");
assertFalse("The 'xsd:datatype' (if present) MUST NOT be empty",value[1].isEmpty());
}
if(fieldValue.has("xml:lang")){
assertNull("Field-values MUST NOT have both 'xsd:datatype' and 'xml:lang' defined!",
value[1]);
value[1] = fieldValue.getString("xml:lang");
assertFalse("The 'xml:lang' (if present) MUST NOT be empty",value[1].isEmpty());
}
assertFalse("Duplicate value "+value+" for field "+key,
values.contains(value));
values.add(Arrays.asList(value));
//both xsd:datatype and xml:lang are optional depending on the
//type of the value. Therefore it can not be tested
//the 'type' property is deprecated
}
valueMap.put(key.toString(), values);
}
}
if(checkRequiredFields != null){
assertTrue("Missing required Fields "+checkRequiredFields+" present: "+valueMap,
checkRequiredFields.isEmpty());
}
return valueMap;
}
}