org.openremote.android.test.console.net.ORConnectionTest.java Source code

Java tutorial

Introduction

Here is the source code for org.openremote.android.test.console.net.ORConnectionTest.java

Source

/*
 * OpenRemote, the Home of the Digital Home.
 * Copyright 2008-2010, OpenRemote Inc.
 *
 * See the contributors.txt file in the distribution for a
 * full listing of individual contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.openremote.android.test.console.net;

import java.net.HttpURLConnection;
import java.net.UnknownHostException;
import java.net.MalformedURLException;
import java.io.IOException;
import java.util.Random;
import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.openremote.android.console.net.ORConnection;
import org.openremote.android.console.net.ORHttpMethod;
import org.openremote.android.console.AppSettingsActivity;
import org.apache.http.HttpResponse;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.xml.sax.SAXException;
import android.test.ActivityInstrumentationTestCase2;
import android.app.Activity;

/**
 * Tests for {@link ORConnection} class.
 *
 * TODO:
 *   Strictly speaking these are integration tests, rather than unit tests, operating against
 *   public controller instances. Haven't separated out unit vs. integration tests as separate
 *   targets yet. These tests can take little time to finish, although I've tried to keep time
 *   spent minimal.
 *                                                                                      [JPL]
 *
 *
 * @author <a href="mailto:juha@openremote.org">Juha Lindfors</a>
 */
public class ORConnectionTest extends ActivityInstrumentationTestCase2<AppSettingsActivity> {

    // Constants ------------------------------------------------------------------------------------

    /**
     * Controller 2.0 REST/XML URI for listing all available panel ids in the controller
     */
    public final static String RESTAPI_PANEL_URI = "/rest/panels";

    /**
     * Controller 2.0 REST/XML URI for retrieving panel by id from the controller.
     */
    public final static String RESTAPI_FULLPANEL_URI = "/rest/panel/";

    /**
     * Controller 2.0 REST/XML URI for write commands to the controller.
     */
    public final static String RESTAPI_CONTROL_URI = "/rest/control/";

    /**
     * Controller 2.0 REST/XML URI for read commands.
     */
    public final static String RESTAPI_STATUS_URI = "/rest/status/";

    /**
     * Don't authenticate against controllers.
     */
    public final static boolean NO_HTTP_AUTH = false;

    /**
     * MIME type for HTTP content-type header
     */
    public final static String TEXTHTML_MIME_TYPE = "text/html";

    /**
     * MIME type for HTTP content-type header
     */
    public final static String APPLICATIONXML_MIME_TYPE = "application/xml";

    /**
     *
     */
    public static final String TEST_CONTROLLER_URL = "http://controller.openremote.org/test/controller";
    public static final String TEST_CMDERRORS_CONTROLLER_URL = "http://controller.openremote.org/cmderrors/controller";
    public static final String TEST_EMPTY_CONTROLLER_URL = "http://controller.openremote.org/empty/controller";
    public static final String TEST_BROKEN_CONTROLLER_URL = "http://controller.openremote.org/broken/controller";

    /**
     * Command param for HTTP assertions to skip charset check due to existing bugs in Controller
     * REST/XML implementation.
     */
    public final static boolean SKIP_CHARSET_CHECK = true;

    /**
     * Command param for HTTP assertions to explicitly ensure UTF-8 character encoding in responses.
     */
    public final static boolean ASSERT_UTF8_ENCODING = !SKIP_CHARSET_CHECK;

    public final static boolean ASSERT_NON_ZERO_BODY_LENGTH = true;
    public final static boolean DO_NOT_CHECK_BODY_LENGTH = !ASSERT_NON_ZERO_BODY_LENGTH;

    public final static int NO_HTTP_BODY = 0;

    // Instance Fields ------------------------------------------------------------------------------

    /**
     * Reference to the activity used to back the tests.
     */
    private Activity activity;

    // Constructors ---------------------------------------------------------------------------------

    public ORConnectionTest() {
        super("org.openremote.android.console", AppSettingsActivity.class);
    }

    // Test Setup -----------------------------------------------------------------------------------

    @Override
    public void setUp() {
        try {
            super.setUp();
        } catch (Throwable t) {
            fail("Test setup failed in ActivityInstrumentationTestCase2: " + t.getMessage());
        }

        this.activity = getActivity();
    }

    // Tests ----------------------------------------------------------------------------------------

    /**
     * Tests a basic GET through {@link ORConnection#checkURLWithHTTPProtocol} method. Attempts to
     * reach the welcome page of a publicly deployed controller.
     *
     * @throws IOException see checkURLWithHTTPProtocol javadoc for details
     */
    public void testURLConnectionBasicGET() throws IOException {
        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET,
                TEST_CONTROLLER_URL, NO_HTTP_AUTH);

        assertNotNullResponse(response, TEST_CONTROLLER_URL);
        assertHttpReturnCode(response, HttpURLConnection.HTTP_OK);
        assertNotZeroResponseBody(response);
        assertHttpContentType(response, TEXTHTML_MIME_TYPE);
    }

    /**
     * Tests a GET behavior on an unknown (no DNS resolution) host.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionUnknownHost() throws IOException {
        final String NOHOST_TESTURL = "http://controller.openremotetest.org/test/controller";

        try {
            ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, NOHOST_TESTURL, NO_HTTP_AUTH);

            fail("Should not get here...");
        } catch (UnknownHostException e) {
            // expected unknown host exception...
        }
    }

    /**
     * Test null arg behavior...
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionWithNullArg() throws IOException {
        final String TESTURL = null;

        try {
            ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL, NO_HTTP_AUTH);

            fail("Should not get here...");
        } catch (MalformedURLException e) {
            // expected malformed URL exception...
        }
    }

    /**
     * Test empty string arg behavior...
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionWithEmptyArg() throws IOException {
        final String TESTURL = "";

        try {
            ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL, NO_HTTP_AUTH);

            fail("Should not get here...");
        } catch (MalformedURLException e) {
            // expected malformed URL exception...
        }
    }

    /**
     * Test invalid URL arg behavior...
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionWithRandomString() throws IOException {
        final String TESTURL = "random";

        try {
            ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL, NO_HTTP_AUTH);

            fail("Should not get here...");
        } catch (MalformedURLException e) {
            // expected malformed URL exception...
        }
    }

    /**
     * Test malformed URL arg behavior...
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionWithMissingProtocol() throws IOException {
        final String NO_PROTOCOL_SCHEME_TESTURL = "controller.openremote.org/test/controller";

        try {
            ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, NO_PROTOCOL_SCHEME_TESTURL,
                    NO_HTTP_AUTH);

            fail("Should not get here...");
        } catch (MalformedURLException e) {
            // expected malformed URL exception...
        }
    }

    /**
     * Test malformed URL arg behavior...
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionWithUnknownProtocol() throws IOException {
        final String INVALID_PROTOCOL_TESTURL = "foo://controller.openremote.org/test/controller";

        try {
            ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, INVALID_PROTOCOL_TESTURL,
                    NO_HTTP_AUTH);

            fail("Should not get here...");
        } catch (MalformedURLException e) {
            // expected malformed URL exception...
        }
    }

    /**
     * Test non-existent URL path behavior...
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testURLConnectionWithWrongURLPath() throws IOException {
        final String NOT_EXIST_TESTURL = "http://controller.openremote.org/test/controller/does/not/exist";

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, NOT_EXIST_TESTURL,
                NO_HTTP_AUTH);

        assertNotNullResponse(response, NOT_EXIST_TESTURL);

        assertHttpReturnCode(response, HttpURLConnection.HTTP_NOT_FOUND);
    }

    // Tests against Controller 2.0 REST/XML API ----------------------------------------------------

    //
    // TODO:
    //
    //  The following tests validate the REST/XML API assumptions from the point of view of the
    //  Android client.
    //
    //  These tests are somewhat redundant to unit tests in the Controller that should (ideally)
    //  ensure that the REST API is implemented correctly. However, testing the API from the
    //  client side is useful for documenting the expected version of API the client assumes,
    //  and they can therefore be used to test client compatibility against different versions of
    //  Controller, as the API evolves (therefore, these are also integration tests, rather than
    //  unit tests).
    //
    //  The current version of Controller available at the time of writing of these tests did not
    //  implement the REST/XML API completely and correctly. Some of the following tests point out
    //  these issues. These particular tests that document incorrect REST/XML API implementation
    //  should phase out once the controller becomes release-quality.

    /**
     * Test basic get of list of panels from public controller instance -- expects two pre-configured
     * panels to be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     * @throws SAXException if parsing fails on return data
     */
    public void testControllerGETRestPanelXML() throws IOException, ParserConfigurationException, SAXException {
        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, HttpURLConnection.HTTP_OK);

        // TODO :
        //   This should be fixed in Controller 2.0 Alpha 12 -- uncomment when online test controller
        //   has been upgraded (currently returns 'text/plain')
        //
        try {
            assertMimeType(response, APPLICATIONXML_MIME_TYPE);

            fail("\n\nIncorrect content-type issue has been fixed, please update this test.\n\n");
        } catch (Throwable t) {
            // TODO: Ignore for now, remove the check once content-type issue has been fixed.
        }

        Document doc = getDOMDocument(response);

        assertOpenRemoteRootElement(doc);

        NodeList list = doc.getElementsByTagName("panel");

        assertTrue(list.getLength() >= 2);

        Map<String, String> panels = new HashMap<String, String>(3);

        for (int panelIndex = 0; panelIndex < list.getLength(); ++panelIndex) {
            Node panel = list.item(panelIndex);
            NamedNodeMap attrs = panel.getAttributes();

            Node id = attrs.getNamedItem("id");
            String idVal = id.getNodeValue();

            Node name = attrs.getNamedItem("name");
            String nameVal = name.getNodeValue();

            panels.put(idVal, nameVal);
        }

        assertNotNull("Expected panel id '1' but that was not found.", panels.get("1"));
        assertNotNull("Expected panel id '5' but that was not found.", panels.get("5"));

        assertTrue("Expected panel id=1 with name 'SimpleName', got " + panels.get("1"),
                panels.get("1").equalsIgnoreCase("SimpleName"));

        assertTrue("Expected panel id=5 with name 'Name With Space', got " + panels.get("5"),
                panels.get("5").equalsIgnoreCase("Name With Spaces"));
    }

    /**
     * TODO:
     *
     *  This test points out incorrectly implemented Controller REST/XML API -- returned content
     *  type should be application/xml. Once the issue has been fixed, this test can be removed.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testKnownBugAgainstController_2_0_Alpha11_RESTAPI() throws IOException {
        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO :
        //   once the issue with incorrect content-type header is fixed, this test can be removed
        //   and the correct assertions in other tests can be made
        //                                                                                [JPL]
        //
        assertHttpResponse(response, TESTURL, HttpURLConnection.HTTP_OK, APPLICATIONXML_MIME_TYPE);
    }

    /**
     * Asserts the panel XML definition contains all the necessary elements
     * for the integration tests.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     * @throws SAXException if parsing fails on return data
     */
    public void testControllerGETRestFullPanelXML() throws IOException, ParserConfigurationException, SAXException {
        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_FULLPANEL_URI + "SimpleName";

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, HttpURLConnection.HTTP_OK);

        // TODO :
        //   This should be fixed in Controller 2.0 Alpha 12 -- uncomment when online test controller
        //   has been upgraded (currently returns 'text/plain')
        //
        try {
            assertMimeType(response, APPLICATIONXML_MIME_TYPE);

            fail("\n\nIncorrect content-type issue has been fixed, please update this test.\n\n");
        } catch (Throwable t) {
            // TODO: Ignore for now, remove the check once content-type issue has been fixed.
        }

        Document doc = getDOMDocument(response);

        assertOpenRemoteRootElement(doc);

        NodeList list = doc.getElementsByTagName("screens");

        assertTrue("Expected one <screens> element in panel definition.", list.getLength() == 1);

        list = doc.getElementsByTagName("groups");

        assertTrue("Expected one <groups> element in panel definition.", list.getLength() == 1);

        list = doc.getElementsByTagName("group");

        assertTrue("Expected at least one group in the panel definition.", list.getLength() >= 1);

        Set<String> screenReferences = new HashSet<String>(10);

        for (int groupIndex = 0; groupIndex < list.getLength(); ++groupIndex) {
            Node group = list.item(groupIndex);

            if (group.getNodeType() != Node.ELEMENT_NODE)
                continue;

            assertTrue("Expected <group> to have child elements, found none.", group.hasChildNodes());

            NodeList includes = group.getChildNodes();

            assertTrue("Expected at least one included screen in the group.", includes.getLength() >= 1);

            for (int includeIndex = 0; includeIndex < includes.getLength(); ++includeIndex) {
                Node include = includes.item(includeIndex);

                if (include.getNodeType() != Node.ELEMENT_NODE)
                    continue;

                NamedNodeMap attrs = include.getAttributes();

                assertNotNull("Expected to find attributes in include", attrs);
                assertNotNull("Expected to find 'ref' attribute in <include>, got null", attrs.getNamedItem("ref"));

                Node ref = attrs.getNamedItem("ref");

                String screenRef = ref.getNodeValue();

                screenReferences.add(screenRef);
            }
        }

        list = doc.getElementsByTagName("screen");

        Set<String> screenIDs = new HashSet<String>(10);

        assertTrue("Expected at least one <screen> in panel definition.", list.getLength() >= 1);

        for (int screenIndex = 0; screenIndex < list.getLength(); ++screenIndex) {
            Node screen = list.item(screenIndex);

            NamedNodeMap attrs = screen.getAttributes();

            assertNotNull("Expected to find 'id' attribute in screen, not null.", attrs.getNamedItem("id"));

            Node id = attrs.getNamedItem("id");

            screenIDs.add(id.getNodeValue());
        }

        for (String ref : screenReferences) {
            assertTrue("Expected to find screen with id: " + ref, screenIDs.contains(ref));
        }

        list = doc.getElementsByTagName("button");

        assertTrue("Expected at least two <button> elements, got " + list.getLength(), list.getLength() >= 2);

        Set<String> buttonIDs = new HashSet<String>(10);

        for (int btnIndex = 0; btnIndex < list.getLength(); ++btnIndex) {
            Node button = list.item(btnIndex);

            NamedNodeMap attrs = button.getAttributes();

            assertNotNull("Expected to find attributes in <button>, got null.", attrs);

            assertNotNull("Expected to find id attribute in <button>.", attrs.getNamedItem("id"));

            Node id = attrs.getNamedItem("id");

            buttonIDs.add(id.getNodeValue());
        }

        assertTrue("Expected to find a button with ID=22", buttonIDs.contains("22"));
        assertTrue("Expected to find a button with ID=24", buttonIDs.contains("24"));

        list = doc.getElementsByTagName("switch");

        assertTrue("Expected at least one <switch> element, got " + list.getLength(), list.getLength() >= 1);

        Set<String> switchIDs = new HashSet<String>(10);
        Set<String> sensorReferences = new HashSet<String>(10);

        for (int switchIndex = 0; switchIndex < list.getLength(); ++switchIndex) {
            Node switchComp = list.item(switchIndex);

            NamedNodeMap attrs = switchComp.getAttributes();

            assertNotNull("Expected to find attributes in <switch>, got null.", attrs);

            assertNotNull("Expected to find id attribute in <switch>.", attrs.getNamedItem("id"));

            Node id = attrs.getNamedItem("id");

            switchIDs.add(id.getNodeValue());

            NodeList links = switchComp.getChildNodes();

            for (int linksIndex = 0; linksIndex < links.getLength(); ++linksIndex) {
                Node link = links.item(linksIndex);

                if (link.getNodeType() != Node.ELEMENT_NODE)
                    continue;

                if (!link.getNodeName().equalsIgnoreCase("link"))
                    continue;

                NamedNodeMap linkAttrs = link.getAttributes();

                assertNotNull("Expected attributes on <link>, got null.", linkAttrs);

                Node typeAttr = linkAttrs.getNamedItem("type");

                if (typeAttr.getNodeValue().equalsIgnoreCase("sensor")) {
                    Node refAttr = linkAttrs.getNamedItem("ref");

                    assertNotNull("Expected a 'ref' attribute in 'sensor' link, didn't find it.", refAttr);

                    sensorReferences.add(refAttr.getNodeValue());
                }
            }
        }

        assertTrue("Expected to find a switch with ID=28", switchIDs.contains("28"));
        assertTrue("Expected to find sensor link with ID=29", sensorReferences.contains("29"));
    }

    /**
     * Tests the HTTP response code on request for panel list against controller that has no
     * configuration deployed.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerGETRestPanelXML_EmptyController() throws IOException {
        final String TESTURL = TEST_EMPTY_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO :
        //   Returned HTTP headers specify 'utf8' instead of 'utf-8' as the charset encoding -- this
        //   test skips the assertion. The buggy charset header value is triggered by another test.
        //   Once the issue has been fixed (should be in Controller 2.0 Alpha 12), remove the
        //   SKIP_CHARSET_CHECK parameter.
        //                                                                                [JPL]
        //
        assertHttpResponse(response, TESTURL, 426, SKIP_CHARSET_CHECK);

        try {
            assertHttpResponse(response, TESTURL, 426);

            fail("\n\nIt appears the charset bug has been addressed, please update this test.\n\n");
        } catch (Throwable t) {
            // TODO: Ignore this until the charset issue has been fixed...
        }
    }

    /**
     * TODO:
     *
     *   This test points out incorrect character encoding against Controller 2.0 Alpha 11.
     *   Test can be removed once the controller REST/XML API has been fixed (fix should
     *   be in Alpha 12).
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerGETRestPanelXML_EmptyController_BROKEN_CHARSET_ENCODING() throws IOException {
        final String TESTURL = TEST_EMPTY_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 426);

        fail("\n\nAppears the character encoding issue has been fixed. This test can be removed.\n\n");
    }

    /**
     * Tests the HTTP response code on request for panel list against controller that has a malformed
     * configuration deployed
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerGETRestPanelXML_BrokenPanelXML() throws IOException {
        final String TESTURL = TEST_BROKEN_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO :
        //   Returned HTTP headers specify 'utf8' instead of 'utf-8' as the charset encoding -- this
        //   test skips the assertion. The buggy charset header value is triggered by another test.
        //   Once the issue has been fixed (should be in Controller 2.0 Alpha 12), remove the
        //   SKIP_CHARSET_CHECK parameter.
        //                                                                                [JPL]
        //
        assertHttpResponse(response, TESTURL, 424, SKIP_CHARSET_CHECK);

        try {
            assertHttpResponse(response, TESTURL, 424);

            fail("\n\nIt appears the charset bug has been addressed, please update this test.\n\n");
        } catch (Throwable t) {
            // TODO: Ignore this until the charset issue has been fixed...
        }
    }

    /**
     * TODO:
     *
     *   This test points out incorrect character encoding against Controller 2.0 Alpha 11.
     *   Test can be removed once the controller REST/XML API has been fixed (fix should
     *   be in Alpha 12).
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerGETRestPanelXML_BrokenPanelXML_BROKEN_CHARSET_ENCODING() throws IOException {
        final String TESTURL = TEST_BROKEN_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 424);

        fail("\n\nAppears the character encoding issue has been fixed. This test can be removed.\n\n");
    }

    /**
     * Tests the HTTP response code when requesting panel definition by name that does not exist.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerGETRestFullPanelXML_NotExist() throws IOException {
        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_FULLPANEL_URI + "DoesNotExist";

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO :
        //   Returned HTTP headers specify 'utf8' instead of 'utf-8' as the charset encoding -- this
        //   test skips the assertion. The buggy charset header value is triggered by another test.
        //   Once the issue has been fixed (should be in Controller 2.0 Alpha 12), remove the
        //   SKIP_CHARSET_CHECK parameter.
        //                                                                                [JPL]
        //
        assertHttpResponse(response, TESTURL, 428, SKIP_CHARSET_CHECK);

        try {
            assertHttpResponse(response, TESTURL, 428);

            fail("\n\nIt appears the charset bug has been addressed, please update this test.\n\n");
        } catch (Throwable t) {
            // TODO: Ignore this until the charset issue has been fixed...
        }
    }

    /**
     * TODO:
     *
     *   This test points out incorrect character encoding against Controller 2.0 Alpha 11.
     *   Test can be removed once the controller REST/XML API has been fixed (fix should
     *   be in Alpha 12).
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerGETRestFullPanelXML_NotExist_BROKEN_CHARSET_ENCODING() throws IOException {
        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_FULLPANEL_URI + "DoesNotExist";

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 428);

        fail("\n\nAppears the character encoding issue has been fixed. This test can be removed.\n\n");
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerGETRestPanelXML_EmptyController_BROKEN_API_IMPL()
            throws ParserConfigurationException, IOException {
        final String TESTURL = TEST_EMPTY_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO : remove skip once Controller REST/XML API has been fixed...

        assertHttpResponse(response, TESTURL, 426, SKIP_CHARSET_CHECK);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerGETRestPanelXML_BrokenPanelXML_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String TESTURL = TEST_BROKEN_CONTROLLER_URL + RESTAPI_PANEL_URI;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO : remove skip once Controller REST/XML API has been fixed...

        assertHttpResponse(response, TESTURL, 424, SKIP_CHARSET_CHECK);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerGETRestFullPanelXML_NotExist_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_FULLPANEL_URI + "DoesNotExist";

        final boolean NO_HTTP_AUTH = false;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, TESTURL,
                NO_HTTP_AUTH);

        // TODO : remove skip once Controller REST/XML API has been fixed...

        assertHttpResponse(response, TESTURL, 428, SKIP_CHARSET_CHECK);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");

    }

    /**
     * Test a 'click' write command on a pre-configured button ID.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerSimpleButtonCommand() throws IOException {
        final String BUTTON_ID = "22";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + BUTTON_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, HttpURLConnection.HTTP_OK, NO_HTTP_BODY);
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerCmdBuildErrorOnButton_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String BUTTON_ID = "999";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CMDERRORS_CONTROLLER_URL + RESTAPI_CONTROL_URI + BUTTON_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 418);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");
    }

    /**
     * Test the HTTP error response on a command that has invalid XML configuration.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerCmdBuildErrorOnButton() throws IOException {
        final String BROKEN_BUTTON_ID = "999";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CMDERRORS_CONTROLLER_URL + RESTAPI_CONTROL_URI + BROKEN_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 418);
    }

    /**
     * Test the HTTP error response on a write command URI that has an invalid/malformed component ID.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerRESTControlInvalidURI() throws IOException {
        final String INVALID_BUTTON_ID = "must-be-integer";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CMDERRORS_CONTROLLER_URL + RESTAPI_CONTROL_URI + INVALID_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 400);
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerRESTControlInvalidURI_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String INVALID_BUTTON_ID = "must-be-integer";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CMDERRORS_CONTROLLER_URL + RESTAPI_CONTROL_URI + INVALID_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 400);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");

    }

    /**
     * A very light and simple stress test on multiple consequtive button clicks to ensure the
     * behavior and response remains constant.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerButtonClicks() throws IOException {
        final String LIGHTON_BUTTON_ID = "22";
        final String LIGHTOFF_BUTTON_ID = "24";

        final String BUTTON_COMMAND_PARAM = "/click";

        final String LIGHTON_BUTTON_URL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + LIGHTON_BUTTON_ID
                + BUTTON_COMMAND_PARAM;

        final String LIGHTOFF_BUTTON_URL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + LIGHTOFF_BUTTON_ID
                + BUTTON_COMMAND_PARAM;

        Random rand = new Random(System.currentTimeMillis());

        for (int index = 0; index < 20; ++index) {
            int option = rand.nextInt(2);

            String url = "<undefined>";

            switch (option) {
            case 0:
                url = LIGHTON_BUTTON_URL;
                break;

            case 1:
                url = LIGHTOFF_BUTTON_URL;
                break;
            }

            HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, url,
                    NO_HTTP_AUTH);

            assertHttpResponse(response, url, HttpURLConnection.HTTP_OK, NO_HTTP_BODY);
        }

    }

    /**
     * Test HTTP response on a 'click' write command where the component ID is not found.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerButtonCommandUnknownID() throws IOException {
        final String UNKNOWN_BUTTON_ID = "22222";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + UNKNOWN_BUTTON_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 419);
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerButtonCommandUnknownID_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String UNKNOWN_BUTTON_ID = "22222";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + UNKNOWN_BUTTON_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 419);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");

    }

    /**
     * Test HTTP response on a 'click' command that has been configured with an unknown protocol.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerButtonCommandUnknownProtocol() throws IOException {
        final String UNKNOWN_PROTOCOL_BUTTON_ID = "444";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CMDERRORS_CONTROLLER_URL + RESTAPI_CONTROL_URI + UNKNOWN_PROTOCOL_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 420);
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerButtonCommandUnknownProtocol_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String UNKNOWN_PROTOCOL_BUTTON_ID = "444";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CMDERRORS_CONTROLLER_URL + RESTAPI_CONTROL_URI + UNKNOWN_PROTOCOL_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 420);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");
    }

    /**
     * Test HTTP Response on button 'click' command when controller XML has not been deployed.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerButtonCommandOnEmptyController() throws IOException {
        final String BUTTON_ID = "444";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_EMPTY_CONTROLLER_URL + RESTAPI_CONTROL_URI + BUTTON_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 422);
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerButtonCommandOnEmptyController_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String NONEXISTENT_BUTTON_ID = "444";
        final String COMMAND_PARAM = "/cLiCk";

        final String TESTURL = TEST_EMPTY_CONTROLLER_URL + RESTAPI_CONTROL_URI + NONEXISTENT_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 422);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");

    }

    /**
     * Test HTTP error response on 'click' command when the command cannot be parsed (invalid XML).
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerButtonCommandOnBrokenControllerXML() throws IOException {
        final String UNPARSEABLE_BUTTON_ID = "444";
        final String COMMAND_PARAM = "/CLICK";

        final String TESTURL = TEST_BROKEN_CONTROLLER_URL + RESTAPI_CONTROL_URI + UNPARSEABLE_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 424);
    }

    /**
     * Quick test that URL's get trimmed properly.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerButtonCommandURITrimming() throws IOException {
        final String BUTTON_ID = "24";
        final String COMMAND_PARAM = "/CLICK        ";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + BUTTON_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, HttpURLConnection.HTTP_OK, NO_HTTP_BODY);
    }

    /**
     * TODO:
     *
     *   This test points out incorrect return documents against Controller 2.0 Alpha 11 REST/XML API.
     *   As per the documentation, an XML document with error data should be returned.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     */
    public void testControllerButtonCommandOnBrokenControllerXML_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException {
        final String UNPARSEABLE_BUTTON_ID = "444";
        final String COMMAND_PARAM = "/Click";

        final String TESTURL = TEST_BROKEN_CONTROLLER_URL + RESTAPI_CONTROL_URI + UNPARSEABLE_BUTTON_ID
                + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL, 424);

        try {
            getDOMDocument(response);
        } catch (SAXException e) {
            fail("\n\nError codes should return an XML body content as per the API documentation \n"
                    + "This is currently not implemented as per Controller 2.0 Alpha 11 -- \n"
                    + "it is supposedly fixed in /branches/feature/Controller_REST_JSON_API branch.\n\n");
        }

        // TODO -- once the controller is fixed, this test can be completed...

        fail("\n\nError type return values issue appears to be fixed (or changed), "
                + "update this test accordingly.\n\n");

    }

    /**
     * Basic switch on/off test with a preconfigured switch component
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerSimpleSwitch() throws IOException {
        final String COMPONENT_ID = "28";
        final String COMMAND_PARAM_ON = "/on";
        final String COMMAND_PARAM_OFF = "/off";

        final String TESTURL_ON = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + COMPONENT_ID + COMMAND_PARAM_ON;
        final String TESTURL_OFF = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + COMPONENT_ID + COMMAND_PARAM_OFF;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL_ON,
                NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL_ON, HttpURLConnection.HTTP_OK, NO_HTTP_BODY);

        response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL_OFF, NO_HTTP_AUTH);

        assertHttpResponse(response, TESTURL_OFF, HttpURLConnection.HTTP_OK, NO_HTTP_BODY);
    }

    /**
     *  TODO:
     *
     *    improvement for the current Controller REST/XML API implementation -- when passing an
     *    unknown command paratemer to component, the HTTP return response is OK (200), should
     *    indicate incorrect API use instead.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerWrongSwitchCommand() throws IOException {
        final String COMPONENT_ID = "28";
        final String COMMAND_PARAM = "/click";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + COMPONENT_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertTrue("Expected response to " + TESTURL + ", got <null>", response != null);

        int httpResponseCode = response.getStatusLine().getStatusCode();

        assertTrue("Expected HTTP Response other than " + HttpURLConnection.HTTP_OK
                + " on incorrect command parameter.", httpResponseCode != HttpURLConnection.HTTP_OK);

    }

    /**
     *  TODO:
     *
     *    improvement for the current Controller REST/XML API implementation -- when passing an
     *    unknown command paratemer to component, the HTTP return response is OK (200), should
     *    indicate incorrect API use instead.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerWrongButtonCommand() throws IOException {
        final String COMPONENT_ID = "22";
        final String COMMAND_PARAM = "/on";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + COMPONENT_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertTrue("Expected response to " + TESTURL + ", got <null>", response != null);

        int httpResponseCode = response.getStatusLine().getStatusCode();

        assertTrue("Expected HTTP Response other than " + HttpURLConnection.HTTP_OK
                + " on incorrect command parameter.", httpResponseCode != HttpURLConnection.HTTP_OK);

    }

    /**
     *  TODO:
     *
     *    improvement for the current Controller REST/XML API implementation -- when passing an
     *    unknown command paratemer to component, the HTTP return response is OK (200), should
     *    indicate incorrect API use instead.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     */
    public void testControllerWrongButtonCommand2() throws IOException {
        final String COMPONENT_ID = "24";
        final String COMMAND_PARAM = "/off";

        final String TESTURL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + COMPONENT_ID + COMMAND_PARAM;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, TESTURL,
                NO_HTTP_AUTH);

        assertTrue("Expected response to " + TESTURL + ", got <null>", response != null);

        int httpResponseCode = response.getStatusLine().getStatusCode();

        assertTrue("Expected HTTP Response other than " + HttpURLConnection.HTTP_OK
                + " on incorrect command parameter.", httpResponseCode != HttpURLConnection.HTTP_OK);

    }

    /**
     * Small stress test on a switch to ensure the switch state is returned correctly after
     * consequtive calls with random order of 'on' and 'off' commands.  <p>
     *
     * Note that the system is asynchronous due to polling implementation on devices -- this
     * means that the correct state might not be immediately available in state cache after
     * the write command, so we iterate a couple of times to ensure the state is changed.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     * @throws SAXException if parsing fails on return data
     * @throws InterruptedException if tests were interrupted prematurely
     */
    public void testControllerSwitches()
            throws IOException, ParserConfigurationException, SAXException, InterruptedException {
        final String SWITCH_ID = "28";
        final String SENSOR_ID = "29";

        final String COMMAND_ON_PARAM = "/on";
        final String COMMAND_OFF_PARAM = "/off";

        final String SWITCH_ON_URL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + SWITCH_ID + COMMAND_ON_PARAM;

        final String SWITCH_OFF_URL = TEST_CONTROLLER_URL + RESTAPI_CONTROL_URI + SWITCH_ID + COMMAND_OFF_PARAM;

        final String READ_SENSOR_URL = TEST_CONTROLLER_URL + RESTAPI_STATUS_URI + SENSOR_ID;

        Random rand = new Random(System.currentTimeMillis());

        for (int index = 0; index < 20; ++index) {
            int option = rand.nextInt(2);

            String url = "<undefined>";

            switch (option) {
            case 0:
                url = SWITCH_ON_URL;
                break;

            case 1:
                url = SWITCH_OFF_URL;
                break;
            }

            HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.POST, url,
                    NO_HTTP_AUTH);

            assertHttpResponse(response, url, HttpURLConnection.HTTP_OK, NO_HTTP_BODY);

            final int MAX_ATTEMPTS = 5;

            for (int attempts = 1; attempts <= MAX_ATTEMPTS; ++attempts) {
                response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, READ_SENSOR_URL,
                        NO_HTTP_AUTH);

                assertHttpResponse(response, READ_SENSOR_URL, HttpURLConnection.HTTP_OK);

                Document doc = getDOMDocument(response);

                NodeList nodes = doc.getElementsByTagName("status");

                assertTrue("Expected exactly one status node, got " + nodes.getLength(), nodes.getLength() == 1);

                Node node = nodes.item(0);
                String content = node.getFirstChild().getNodeValue().trim();

                boolean foundCorrectState = false;

                switch (option) {
                case 0:
                    if (content.equalsIgnoreCase("on")) {
                        foundCorrectState = true;
                    }
                    break;

                case 1:
                    if (content.equalsIgnoreCase("off")) {
                        foundCorrectState = true;
                    }
                    break;
                }

                if (foundCorrectState)
                    break;

                else {
                    if (attempts == MAX_ATTEMPTS) {
                        fail("Sent " + url + " on round " + index + "\nGot wrong response '" + content + "'.");
                    } else {
                        Thread.sleep(500);
                    }
                }
            }
        }
    }

    /**
     * TODO:
     *
     *   Demonstrates error in current Controller implementation that returns an incorrect
     *   HTTP content-type header.
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     * @throws SAXException if parsing fails on return data
     */
    public void testControllerSwitch_BROKEN_API_IMPL()
            throws IOException, ParserConfigurationException, SAXException {
        final String SENSOR_ID = "29";

        final String READ_SENSOR_URL = TEST_CONTROLLER_URL + RESTAPI_STATUS_URI + SENSOR_ID;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, READ_SENSOR_URL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, READ_SENSOR_URL, HttpURLConnection.HTTP_OK, APPLICATIONXML_MIME_TYPE);
    }

    /**
     * Test a simple 'switch' sensor read (disregarding returned state)
     *
     * @throws IOException if connection fails for any reason, see checkURLWithHTTPProtocol javadoc
     * @throws ParserConfigurationException if DOM parsing fails on return data
     * @throws SAXException if parsing fails on return data
     */
    public void testControllerSwitchSensor() throws IOException, ParserConfigurationException, SAXException {
        final String SENSOR_ID = "29";

        final String READ_SENSOR_URL = TEST_CONTROLLER_URL + RESTAPI_STATUS_URI + SENSOR_ID;

        HttpResponse response = ORConnection.checkURLWithHTTPProtocol(activity, ORHttpMethod.GET, READ_SENSOR_URL,
                NO_HTTP_AUTH);

        assertHttpResponse(response, READ_SENSOR_URL, HttpURLConnection.HTTP_OK);

        // TODO :
        //   This should be fixed in Controller 2.0 Alpha 12 -- uncomment when online test controller
        //   has been upgraded (currently returns 'text/plain')
        //
        try {
            assertMimeType(response, APPLICATIONXML_MIME_TYPE);

            fail("\n\nIncorrect content-type issue has been fixed, please update this test.\n\n");
        } catch (Throwable t) {
            // TODO: Ignore for now, remove the check once content-type issue has been fixed.
        }

        Document doc = getDOMDocument(response);

        NodeList nodes = doc.getElementsByTagName("status");

        assertTrue("Expected exactly one status node, got " + nodes.getLength(), nodes.getLength() == 1);

        Node node = nodes.item(0);
        String content = node.getFirstChild().getNodeValue().trim();

        assertTrue(content.equalsIgnoreCase("on") || content.equalsIgnoreCase("off"));
    }

    // Helpers --------------------------------------------------------------------------------------

    /**
     * TODO:
     *
     *    The skipCharsetCheck boolean parameter can be removed once the Controller has been fixed
     *    to return correct UTF-8 encoded responses.
     *
     * @param response            HTTP response
     * @param url                 Original URL
     * @param returnCode          HTTP response code
     * @param skipCharsetCheck    Whether to assert UTF-8 character encoding or not
     */
    private void assertHttpResponse(HttpResponse response, String url, int returnCode, boolean skipCharsetCheck) {
        assertHttpResponse(response, url, returnCode, skipCharsetCheck, ASSERT_NON_ZERO_BODY_LENGTH);
    }

    /**
     * TODO:
     *
     *    The skipCharsetCheck boolean parameter can be removed once the Controller has been fixed
     *    to return correct UTF-8 encoded responses.
     *
     * @param response            HTTP response
     * @param url                 Original URL
     * @param returnCode          HTTP response code
     * @param skipCharsetCheck    Whether to assert UTF-8 character encoding or not
     * @param bodyLength          Assert precise body length in HTTP response
     */
    private void assertHttpResponse(HttpResponse response, String url, int returnCode, boolean skipCharsetCheck,
            int bodyLength) {
        assertHttpResponse(response, url, returnCode, skipCharsetCheck, DO_NOT_CHECK_BODY_LENGTH);

        assertHttpBodyLength(response, bodyLength);
    }

    /**
     * TODO:
     *
     *    The skipCharsetCheck boolean parameter can be removed once the Controller has been fixed
     *    to return correct UTF-8 encoded responses.
     *
     * @param response            HTTP response
     * @param url                 Original URL
     * @param returnCode          HTTP response code
     * @param skipCharsetCheck    Whether to assert UTF-8 character encoding or not
     * @param hasContentBodyCheck Indicates if any assertions should be made wrt HTTP body content
     */
    private void assertHttpResponse(HttpResponse response, String url, int returnCode, boolean skipCharsetCheck,
            boolean hasContentBodyCheck) {
        assertNotNullResponse(response, url);
        assertHttpReturnCode(response, returnCode);

        if (hasContentBodyCheck)
            assertNotZeroResponseBody(response);

        assertHasContentTypeHeader(response);

        if (!skipCharsetCheck)
            assertUTF8Encoding(response);
    }

    /**
     *
     * @param response
     * @param url
     * @param returnCode
     * @param mimeType
     * @param skipCharsetCheck
     */
    private void assertHttpResponse(HttpResponse response, String url, int returnCode, String mimeType,
            boolean skipCharsetCheck) {
        assertHttpResponse(response, url, returnCode, skipCharsetCheck);

        assertMimeType(response, mimeType);
    }

    private void assertHttpResponse(HttpResponse response, String url, int returnCode, int bodyLength) {
        assertHttpResponse(response, url, returnCode, ASSERT_UTF8_ENCODING, bodyLength);
    }

    private void assertHttpResponse(HttpResponse response, String url, int returnCode) {
        assertHttpResponse(response, url, returnCode, ASSERT_UTF8_ENCODING);
    }

    private void assertHttpResponse(HttpResponse response, String url, int returnCode, String mimeType) {
        assertHttpResponse(response, url, returnCode, mimeType, ASSERT_UTF8_ENCODING);

    }

    private void assertMimeType(HttpResponse response, String mimeType) {
        String httpMimeContentType = response.getEntity().getContentType().getValue();

        assertTrue("Expected HTTP Mime type '" + mimeType + "', got '" + httpMimeContentType + "'.",
                httpMimeContentType.startsWith(mimeType));
    }

    private void assertNotNullResponse(HttpResponse response, String URL) {
        assertNotNull("Expected response to " + URL + ", got <null>", response);
    }

    private void assertHttpReturnCode(HttpResponse response, int code) {
        int httpResponseCode = response.getStatusLine().getStatusCode();

        assertTrue("Expected HTTP Response '" + code + "', got '" + httpResponseCode + "'.",
                httpResponseCode == code);
    }

    private void assertHttpBodyLength(HttpResponse response, int bodyLen) {
        long httpResponseContentLen = response.getEntity().getContentLength();

        assertTrue("Expected content length of '" + bodyLen + " bytes, got " + httpResponseContentLen,
                response.getEntity().getContentLength() == bodyLen);
    }

    private void assertNotZeroResponseBody(HttpResponse response) {
        long httpResponseContentLen = response.getEntity().getContentLength();

        assertTrue("Expected content length > 0, got " + httpResponseContentLen,
                response.getEntity().getContentLength() > 0);
    }

    private void assertHttpContentType(HttpResponse response, String mimeType) {
        final String HTTP_CONTENT_TYPE_HEADER = "content-type";

        String httpContentTypeHeader = response.getEntity().getContentType().getName();
        String httpMimeContentType = response.getEntity().getContentType().getValue();

        assertTrue("Expected HTTP Header '" + HTTP_CONTENT_TYPE_HEADER + "', got '" + httpContentTypeHeader + "'.",
                httpContentTypeHeader.equalsIgnoreCase(HTTP_CONTENT_TYPE_HEADER));

        assertTrue("Expected HTTP Mime type '" + mimeType + "', got '" + httpMimeContentType + "'.",
                httpMimeContentType.startsWith(mimeType));

    }

    private void assertUTF8Encoding(HttpResponse response) {
        final String HTTP_CHAR_ENCODING = "charset=UTF-8".toUpperCase();

        String httpMimeContentType = response.getEntity().getContentType().getValue();

        assertTrue("Expected character encoding '" + HTTP_CHAR_ENCODING + "', got '" + httpMimeContentType + "'.",
                httpMimeContentType.toUpperCase().contains(HTTP_CHAR_ENCODING));

    }

    private void assertHasContentTypeHeader(HttpResponse response) {
        final String HTTP_CONTENT_TYPE_HEADER = "content-type";

        String httpContentTypeHeader = response.getEntity().getContentType().getName();

        assertTrue("Expected HTTP Header '" + HTTP_CONTENT_TYPE_HEADER + "', got '" + httpContentTypeHeader + "'.",
                httpContentTypeHeader.equalsIgnoreCase(HTTP_CONTENT_TYPE_HEADER));
    }

    private Document getDOMDocument(HttpResponse response)
            throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder parser = domFactory.newDocumentBuilder();

        return parser.parse(response.getEntity().getContent());
    }

    private void assertOpenRemoteRootElement(Document doc) {
        NodeList list = doc.getElementsByTagName("openremote");

        assertTrue("Expected to find exactly one <openremote> element, got " + list.getLength(),
                list.getLength() == 1);

        list = doc.getChildNodes();
        int foundRootElement = 0;

        for (int cindex = 0; cindex < list.getLength(); cindex++) {
            if (list.item(cindex).getNodeType() == Node.ELEMENT_NODE)
                ++foundRootElement;
        }

        assertTrue("Expected exactly one child element on document, got " + foundRootElement,
                foundRootElement == 1);
    }

}