package org.mitre.rhex; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.commons.lang.StringUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.mitre.test.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Test for section creation * * <pre> * 6.2 Operations on the Base URL * * 6.2.2 POST � Parameters:extensionID, path, name * * This operation is used to create a new Section at the root of the document. * The request body is of type �application/xwww-form-urlencoded� and MUST contain * the extensionId, path, and name parameters. The extensionId parameter MAY be * a string that is equal to value of one of the registered <extension> nodes of * the root document of the HDR identified by baseURL. The path MUST be a string * that can be used as a URL path segment. If any parameters are incorrect or * not existent, the server MUST return a status code of 400. * * The system MUST confirm that there is no other section registered as a * child node that uses the same path name. If there is a collision, the * server MUST return a status code of 409. * * If the extensionId is not registered as a valid extension, the server * <B>MUST</B> verify that it can support this extension. If it cannot support * the extension it MUST return a status code of 406. It MAY provide * additional entity information. * * If it can support that extension, it <B>MUST</B> register it with the root.xml * of this record. When creating the section resource, the server <B>MUST</B> * update the root document: in the node of the parent section a new child node * must be inserted. If successful, the server <B>MUST</B> return a 201 status * code and SHOULD include the location of the new section. * * The [optional] name parameter MUST be used as the user-friendly name for the * new section. * * Status Code: <B>201</B>, 400, 406, 409 * </pre> * * @author Jason Mathews, MITRE Corp. * @see CreateSectionCodeCheck * Date: 2/20/12 10:45 AM */ public class CreateSection extends BaseXmlTest { private String sectionPath; public CreateSection() { // forces source test to keep its Document objects after it executes setProperty(BaseUrlRootXml.class, PROP_KEEP_DOCUMENT_BOOL, Boolean.TRUE); } @NonNull public String getId() { return "6.2.2.1"; } @Override public boolean isRequired() { return true; // implied MUST } @NonNull public String getName() { return "POST operation on baseURL creates a new Section at the root of the document"; } @NonNull public List<Class<? extends TestUnit>> getDependencyClasses() { return Collections.<Class<? extends TestUnit>> singletonList(BaseUrlRootXml.class); // 6.3.1.1 } public void execute() throws TestException { // pre-conditions: for this test to be executed the prerequisite test BaseUrlRootXml must have passed // with 200 HTTP response and valid root.xml content. TestUnit baseTest = getDependency(BaseUrlRootXml.class); if (baseTest == null) { // assertion failed: this should never be null log.error("Failed to retrieve prerequisite test: BaseUrlRootXml"); setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test: 6.3.1.1"); return; } Document doc = ((BaseUrlRootXml)baseTest).getDocument(); if (doc == null) { log.error("Failed to retrieve prerequisite test results: BaseUrlRootXml"); setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test results: 6.3.1.1"); return; } /* expecting: <?xml version="1.0" encoding="UTF-8"?> <root xmlns="http://projecthdata.org/hdata/schemas/2009/06/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <extensions> <extension extensionId="1">http://projecthdata.org/extension/c32</extension> <extension extensionId="2">http://projecthdata.org/hdata/schemas/2009/06/allergy</extension> ... <extension extensionId="11">http://projecthdata.org/hdata/schemas/2009/06/social_history</extension> </extensions> ... </root> */ final Namespace ns = Namespace.getNamespace(NAMESPACE_HDATA_SCHEMAS_2009_06_CORE); Element extensionsElt = doc.getRootElement().getChild("extensions", ns); String extensionId = null; if (extensionsElt != null) { for(Object child : extensionsElt.getChildren("extension", ns)) { if (!(child instanceof Element)) continue; Element ext = (Element)child; String id = StringUtils.trimToNull(ext.getAttributeValue("extensionId")); // required if (id != null) { extensionId = id; // prefer non-c32 section if available which may have special handling // PDS only allows one c32 per patient String section = ext.getText(); // log.debug("XXX: id={} section={}", id, section); if (section == null || !section.contains("/c32")) break; } } } if (extensionId == null) { log.error("Failed to retrieve extensionId from prerequisite test results: BaseUrlRootXml"); setStatus(StatusEnumType.SKIPPED, "Failed to find extensionId in prerequisite test results: 6.3.1.1"); return; } final Context context = Loader.getInstance().getContext(); final HttpClient client = context.getHttpClient(); try { sectionPath = Long.toHexString(System.currentTimeMillis()); URI baseUrl = context.getBaseURL(); if (log.isDebugEnabled()) { System.out.println("URL: " + baseUrl); System.out.println("path=" + sectionPath); System.out.println("extensionId=" + extensionId); } HttpPost post = new HttpPost(baseUrl); List<NameValuePair> formParams = new ArrayList<NameValuePair>(3); formParams.add(new BasicNameValuePair("path", sectionPath)); formParams.add(new BasicNameValuePair("name", sectionPath)); // optional formParams.add(new BasicNameValuePair("extensionId", extensionId)); post.setEntity(new UrlEncodedFormEntity(formParams)); HttpResponse response = context.executeRequest(client, post); int code = response.getStatusLine().getStatusCode(); if (code != 201 || log.isDebugEnabled()) { dumpResponse(post, response); } if (code >= 400) { if (!log.isDebugEnabled()) { System.out.println("URL: " + baseUrl); System.out.println("path=" + sectionPath); System.out.println("extensionId=" + extensionId); } // request failed entirely setStatus(StatusEnumType.FAILED, "Expected 201 HTTP status code but was: " + code); return; } // NOTE: response status code 201 checked in another test. See CreateSectionCodeCheck. // here we're going to verify if the new section was actually created checkRootXML(context); // TODO: check if baseURL/section returns 200 status code with skeleton atom feed StatusEnumType status = getStatus(); if (status == StatusEnumType.SUCCESS) { setResponse(response); // save response } else if (status == null) { setStatus(StatusEnumType.FAILED, "Failed to verify if new section path was created in root.xml"); } /* if (code != 201) { // wrong status code but operation may have created the section if (status != null) { // status == StatusEnumType.SUCCESS || status == StatusEnumType.FAILED) { // TODO: verify for non-201 status code does this test pass with warning or fail ?? addWarning("Expected 201 HTTP status code but was: " + code); } else setStatus(StatusEnumType.FAILED, "Expected 201 HTTP status code but was: " + code); } else if (status == null) { // status != StatusEnumType.SUCCESS) { // right return status code but unable to verify root.xml is updated setStatus(StatusEnumType.FAILED, "Failed to verify if new section path was created in root.xml"); } // Document doc = BaseUrlRootXml.loadDocument(baseUrl); */ } catch (IOException e) { throw new TestException(e); } catch (URISyntaxException e) { throw new TestException(e); } catch (JDOMException e) { throw new TestException(e); } finally { client.getConnectionManager().shutdown(); } } private void checkRootXML(Context context) throws URISyntaxException, IOException, JDOMException { URI baseURL = context.getBaseURL("root.xml"); HttpGet req = new HttpGet(baseURL); req.setHeader("Accept", "text/xml"); if (log.isDebugEnabled()) { System.out.println("\nGET URL: " + req.getURI()); } final HttpClient client = context.getHttpClient(); try { HttpResponse response = context.executeRequest(client, req); int code = response.getStatusLine().getStatusCode(); if (log.isDebugEnabled()) { System.out.println("Response status=" + code); for (Header header : response.getAllHeaders()) { System.out.println("\t" + header.getName() + ": " + header.getValue()); } } if (code != 200) { addWarning("Unexpected HTTP response: " + code); return; } final HttpEntity entity = response.getEntity(); if (entity == null) { addWarning("Expected XML in body of GET response"); return; } ByteArrayOutputStream bos = new ByteArrayOutputStream(); entity.writeTo(bos); if (log.isDebugEnabled()) { System.out.println("Content:\n" + bos.toString("UTF-8")); } final Document doc = getDefaultDocument(context, bos); final Element root = doc.getRootElement(); // assertEquals(NAMESPACE_HDATA_SCHEMAS_2009_06_CORE, root.getNamespace().getURI()); final Namespace ns = Namespace.getNamespace(NAMESPACE_HDATA_SCHEMAS_2009_06_CORE); Element sections = root.getChild("sections", ns); if (sections != null) { int sectionCount = 0; for(Object child : sections.getChildren("section", ns)) { if (!(child instanceof Element)) continue; sectionCount++; Element section = (Element)child; if (sectionPath.equals(section.getAttributeValue("path"))) { setStatus(StatusEnumType.SUCCESS); System.out.println("XXX: found section new " +section + " in ATOM feed"); // debug return; // section path found and test passed } } if (sectionCount > 0) { setStatus(StatusEnumType.FAILED, "Failed to update root.xml with new section path"); } } } finally { client.getConnectionManager().shutdown(); } } public String getSectionPath() { return sectionPath; } }