org.alfresco.bm.api.v1.RestAPITest.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.bm.api.v1.RestAPITest.java

Source

/*
 * Copyright (C) 2005-2013 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */
package org.alfresco.bm.api.v1;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;

import org.alfresco.bm.exception.ObjectNotFoundException;
import org.alfresco.bm.log.LogService;
import org.alfresco.bm.log.LogWatcher;
import org.alfresco.bm.report.DataReportService;
import org.alfresco.bm.test.TestConstants;
import org.alfresco.bm.test.TestRun;
import org.alfresco.bm.test.TestRunServicesCache;
import org.alfresco.bm.test.TestRunState;
import org.alfresco.bm.test.TestServiceImpl;
import org.alfresco.bm.test.mongo.MongoTestDAO;
import org.alfresco.mongo.MongoClientFactory;
import org.alfresco.mongo.MongoDBFactory;
import org.alfresco.mongo.MongoDBForTestsFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.POIXMLProperties;
import org.apache.poi.POIXMLProperties.CoreProperties;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.bson.types.ObjectId;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;

import com.google.gson.Gson;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.ServerAddress;

/**
 * @see TestRestAPI V1
 * 
 * @author Derek Hulley
 * @since 2.0
 */
@RunWith(JUnit4.class)
public class RestAPITest implements TestConstants {
    public static final String RELEASE = "RestAPITest";
    public static final int SCHEMA = 0;
    public static final String MONGO_TEST_DATABASE = "bm21-data";

    private static Log logger = LogFactory.getLog(RestAPITest.class);

    private Gson gson = new Gson();
    private MongoDBForTestsFactory mockDBFactory;
    private ClassPathXmlApplicationContext ctx;

    private MongoTestDAO dao;
    private TestRestAPI api;
    private TestRunServicesCache testRunServicesCache;
    private DB appDB;
    private String mongoHost;

    @Before
    public void setUp() throws Exception {
        gson = new Gson();

        // Create a mock DB to connect to
        mockDBFactory = new MongoDBForTestsFactory();
        mongoHost = mockDBFactory.getMongoHost();
        String database = mockDBFactory.getObject().getName();

        // Create an application context (don't start, yet)
        ctx = new ClassPathXmlApplicationContext(new String[] { PATH_APP_CONTEXT }, false);

        // Pass our properties to the new context
        Properties ctxProperties = new Properties();
        {
            ctxProperties.put(PROP_MONGO_CONFIG_HOST, mongoHost);
            ctxProperties.put(PROP_MONGO_CONFIG_DATABASE, database);
            ctxProperties.put(PROP_MONGO_TEST_DATABASE, MONGO_TEST_DATABASE);
            ctxProperties.put(PROP_APP_RELEASE, RELEASE);
            ctxProperties.put(PROP_APP_SCHEMA, "" + SCHEMA);
            ctxProperties.put(PROP_APP_INHERITANCE, "COMMON, TEST");
            ctxProperties.put(PROP_TEST_RUN_MONITOR_PERIOD, "" + 20000);
            ctxProperties.put(PROP_SYSTEM_CAPABILITIES, "Java");
            ctxProperties.put(PROP_APP_CONTEXT_PATH, "/");
            ctxProperties.put(PROP_APP_DIR, ".");
        }
        ConfigurableEnvironment ctxEnv = ctx.getEnvironment();
        ctxEnv.getPropertySources().addFirst(new PropertiesPropertySource("RestAPITest", ctxProperties));
        // Override all properties with system properties
        ctxEnv.getPropertySources().addFirst(new PropertiesPropertySource("system", System.getProperties()));
        // Start the context now
        ctx.refresh();

        // Get beans
        dao = ctx.getBean(MongoTestDAO.class);
        appDB = dao.getDb();
        TestServiceImpl testService = ctx.getBean(TestServiceImpl.class);
        LogService logService = ctx.getBean(LogService.class);
        this.testRunServicesCache = ctx.getBean(TestRunServicesCache.class);
        api = new TestRestAPI(dao, testService, logService, testRunServicesCache);
    }

    @After
    public void tearDown() throws Exception {
        if (ctx != null) {
            try {
                ctx.close();
            } catch (Exception e) {
            }
        }
        if (mockDBFactory != null) {
            try {
                mockDBFactory.destroy();
            } catch (Exception e) {
            }
        }
    }

    @Test
    public void basic() {
        assertNotNull(appDB);
        Set<String> collectionNames = new HashSet<String>();
        collectionNames.add("test.drivers");
        collectionNames.add("test.defs");
        collectionNames.add("tests");
        collectionNames.add("test.runs");
        collectionNames.add("test.props");

        Set<String> dbColNames = removeSystemValues(appDB.getCollectionNames());
        assertEquals(collectionNames, dbColNames);
    }

    /**
     * make sure the system name is NOT contained as from 3.2 on
     * 
     * @param collection
     *        (Set<String>) collection to check
     * @return
     */
    private Set<String> removeSystemValues(Set<String> collection) {
        if (null != collection) {
            // make sure the system name is NOT contained as from 3.2 on
            if (collection.contains("system.indexes")) {
                collection.remove("system.indexes");
            }
        }
        return collection;
    }

    @Test
    public void connectToMongoDB() throws UnknownHostException {
        DB db = dao.getDb();
        String dbName = db.getName();
        ServerAddress mongoAddress = db.getMongo().getAddress();

        // Now connect a real mongo client to it
        MongoClient mongoClient = new MongoClient(mongoAddress);
        // won't be removed in V3.x of MongoDB .... new code uses new API ....
        @SuppressWarnings("deprecation")
        DB dbCheck = mongoClient.getDB(dbName);

        // See if we can add/remove stuff and that it works
        assertTrue(dao.createTest("TESTconnectToMongoDB", "connectToMongoDB", "R1", 1));

        MongoTestDAO daoCheck = new MongoTestDAO(dbCheck);
        DBObject testCheckObj = daoCheck.getTest("TESTconnectToMongoDB", false);
        assertNotNull(testCheckObj);

        // clean-up
        mongoClient.close();
    }

    /**
     * Helper to convert Json to map for quick testing of values
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> fromJson(String json) {
        Map<String, Object> map = (HashMap<String, Object>) gson.fromJson(json, HashMap.class);
        return map;
    }

    private void checkLongValue(String json, String field, Long value) {
        Map<String, Object> map = fromJson(json);
        Long actual = Double.valueOf(map.get(field).toString()).longValue();
        assertEquals("Incorrect json for field  '" + field + "': \n" + json + "\n", value, actual);
    }

    private void checkTimeValue(String json, String field, long time) {
        Map<String, Object> map = fromJson(json);
        long actual = Double.valueOf(map.get(field).toString()).longValue();
        long startTime = time - 30000L;
        long endTime = time + 30000L;
        assertTrue("Incorrect time range for field '" + field + "' around " + time + ": \n" + json,
                startTime <= actual && actual <= endTime);
    }

    private void checkDoubleValue(String json, String field, Double value) {
        Map<String, Object> map = fromJson(json);
        Double actual = Double.valueOf(map.get(field).toString());
        assertEquals("Incorrect json for field  '" + field + "': \n" + json + "\n", value, actual);
    }

    /**
     * Helper method to check that a test run is in a particular state
     */
    private void checkTestRunState(String test, String run, TestRunState state, boolean expected) {
        long now = System.currentTimeMillis();
        // Do a direct check
        String checkStateStr = api.getTestRunState(test, run);
        TestRunState checkState = TestRunState.valueOf(checkStateStr);
        if (expected) {
            assertEquals("State was incorrect for " + test + "." + run + ". ", state, checkState);
        } else {
            assertNotSame("State was incorrect for " + test + "." + run + ". ", state, checkState);
        }
        // Now check by querying for the state
        assertEquals(expected,
                api.getTestRuns(test, 0, 2, state.toString()).contains("\"name\" : \"" + run + "\""));
        // Check that the appropriate fields have been set for the given state
        String checkJson = api.getTestRunSummary(test, run);
        switch (checkState) {
        case NOT_SCHEDULED:
            checkTimeValue(checkJson, FIELD_SCHEDULED, -1L);
            checkTimeValue(checkJson, FIELD_STARTED, -1L);
            checkTimeValue(checkJson, FIELD_STOPPED, -1L);
            checkTimeValue(checkJson, FIELD_COMPLETED, -1L);
            checkLongValue(checkJson, FIELD_DURATION, 0L);
            checkDoubleValue(checkJson, FIELD_PROGRESS, 0.0);
            checkLongValue(checkJson, FIELD_RESULTS_SUCCESS, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_FAIL, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_TOTAL, 0L);
            checkDoubleValue(checkJson, FIELD_SUCCESS_RATE, 1.0);
            break;
        case SCHEDULED:
            checkTimeValue(checkJson, FIELD_SCHEDULED, now);
            checkTimeValue(checkJson, FIELD_STARTED, -1L);
            checkTimeValue(checkJson, FIELD_STOPPED, -1L);
            checkTimeValue(checkJson, FIELD_COMPLETED, -1L);
            checkLongValue(checkJson, FIELD_DURATION, 0L);
            checkDoubleValue(checkJson, FIELD_PROGRESS, 0.0);
            checkLongValue(checkJson, FIELD_RESULTS_SUCCESS, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_FAIL, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_TOTAL, 0L);
            checkDoubleValue(checkJson, FIELD_SUCCESS_RATE, 1.0);
            break;
        case STARTED:
            checkTimeValue(checkJson, FIELD_SCHEDULED, now);
            checkTimeValue(checkJson, FIELD_STARTED, now);
            checkTimeValue(checkJson, FIELD_STOPPED, -1L);
            checkTimeValue(checkJson, FIELD_COMPLETED, -1L);
            checkLongValue(checkJson, FIELD_RESULTS_FAIL, 0L);
            checkDoubleValue(checkJson, FIELD_SUCCESS_RATE, 1.0);
            break;
        case COMPLETED:
            checkTimeValue(checkJson, FIELD_SCHEDULED, now);
            checkTimeValue(checkJson, FIELD_STARTED, now);
            checkTimeValue(checkJson, FIELD_STOPPED, -1L);
            checkTimeValue(checkJson, FIELD_COMPLETED, now);
            checkTimeValue(checkJson, FIELD_DURATION, 30001L); // The
                                                               // range
                                                               // is +-
                                                               // 30s,
                                                               // so we
                                                               // check
                                                               // it's
                                                               // in
                                                               // range
                                                               // 1ms-60001ms
            checkDoubleValue(checkJson, FIELD_PROGRESS, 1.0);
            checkLongValue(checkJson, FIELD_RESULTS_SUCCESS, 21L);
            checkLongValue(checkJson, FIELD_RESULTS_FAIL, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_TOTAL, 21L);
            checkDoubleValue(checkJson, FIELD_SUCCESS_RATE, 1.0);
            break;
        case STOPPED:
            checkTimeValue(checkJson, FIELD_SCHEDULED, now);
            checkTimeValue(checkJson, FIELD_STARTED, now);
            checkTimeValue(checkJson, FIELD_STOPPED, now);
            checkTimeValue(checkJson, FIELD_COMPLETED, -1L);
            checkTimeValue(checkJson, FIELD_DURATION, 30001L); // The
                                                               // range
                                                               // is +-
                                                               // 30s,
                                                               // so we
                                                               // check
                                                               // it's
                                                               // in
                                                               // range
                                                               // 1ms-60001ms
            checkDoubleValue(checkJson, FIELD_PROGRESS, 0.0);
            checkLongValue(checkJson, FIELD_RESULTS_SUCCESS, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_FAIL, 0L);
            checkLongValue(checkJson, FIELD_RESULTS_TOTAL, 0L);
            checkDoubleValue(checkJson, FIELD_SUCCESS_RATE, 1.0);
            break;
        }
    }

    /**
     * Basic CRUD of a test run
     */
    @Test
    public void testScenario01() {
        DBObject dbObject;
        String json;
        TestDetails testDetails;
        TestRunDetails testRunDetails;
        PropSetBean propSetBean;

        // Check the response when there are no tests
        json = api.getTests("A", 4, 0, 10);
        assertEquals("Empty Json results must still be an array", "[]", json);

        testDetails = new TestDetails();
        testDetails.setName("T01");
        testDetails.setRelease(RELEASE);
        testDetails.setSchema(SCHEMA);
        testDetails.setDescription("A test for scenario 01.");
        json = api.createTest(testDetails);
        assertFalse(json, json.indexOf("\"default\" : \"NEVER-LEAVES-THE-SERVER\"") >= 0);
        assertTrue(json, json.indexOf(MASK) >= 0);
        assertTrue(json,
                json.indexOf("\"description\" : \"The password for the user executing the process.\"") >= 0);
        assertTrue(json, json.indexOf("A test for scenario 01.") >= 0);

        assertEquals("Expect exact match from createTest and getTest", json, api.getTest("T01"));

        assertTrue(api.getTests(RELEASE, SCHEMA, 0, 2).contains("\"name\" : \"T01\""));

        // Update test
        testDetails = new TestDetails();
        testDetails.setOldName("T01");
        testDetails.setName("TEST01");
        testDetails.setVersion(0);
        testDetails.setDescription("The test for scenario 01.");
        json = api.updateTest(testDetails);
        assertTrue(json, json.indexOf("The test for scenario 01.") >= 0);

        // Set 'proc.pwd' at the test level
        propSetBean = new PropSetBean();
        propSetBean.setVersion(0);
        propSetBean.setValue("pwd1");
        json = api.setTestProperty("TEST01", "proc.pwd", propSetBean);
        assertFalse(json, json.indexOf("pwd1") >= 0);
        assertTrue(json, json.indexOf(MASK) >= 0);
        assertTrue(json,
                json.indexOf("\"description\" : \"The password for the user executing the process.\"") >= 0);
        dbObject = dao.getProperty("TEST01", null, "proc.pwd");
        assertEquals("NEVER-LEAVES-THE-SERVER", dbObject.get(FIELD_DEFAULT));
        assertEquals("pwd1", dbObject.get(FIELD_VALUE));

        // Set 'processCount' for test 'T01'
        propSetBean = new PropSetBean();
        propSetBean.setVersion(0);
        propSetBean.setValue("500");
        json = api.setTestProperty("TEST01", "processCount", propSetBean);
        assertTrue(json, json.indexOf("\"default\" : \"200\"") >= 0);
        assertTrue(json, json.indexOf("\"value\" : \"500\"") >= 0);
        assertTrue(json, json.indexOf("\"title\" : \"Process Count\"") >= 0);
        dbObject = dao.getProperty("TEST01", null, "processCount");
        assertEquals("200", dbObject.get(FIELD_DEFAULT));
        assertEquals("500", dbObject.get(FIELD_VALUE));

        // Check the response when there are no test runs
        json = api.getTestRuns("TEST01", 0, 10, "");
        assertEquals("Empty Json results must still be an array", "[]", json);

        // Create run 01
        testRunDetails = new TestRunDetails();
        testRunDetails.setName("01");
        testRunDetails.setDescription("Scenario 01 - Run 01");
        json = api.createTestRun("TEST01", testRunDetails);
        assertTrue(json, json.indexOf("\"description\" : \"Scenario 01 - Run 01\"") >= 0);
        assertFalse(json, json.indexOf("\"default\" : \"NEVER-LEAVES-THE-SERVER\"") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"admin\"") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"" + MASK) >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"500\"") >= 0);

        assertEquals("Expect exact match from createTestRun and getTestRun", json, api.getTestRun("TEST01", "01"));

        assertTrue(api.getTestRuns("TEST01", 0, 2, "").contains("\"name\" : \"01\""));

        // Update test run
        testRunDetails = new TestRunDetails();
        testRunDetails.setOldName("01");
        testRunDetails.setName("001");
        testRunDetails.setVersion(0);
        testRunDetails.setDescription("The test run 001.");
        json = api.updateTestRun("TEST01", testRunDetails);
        assertTrue(json, json.indexOf("The test run 001.") >= 0);
        assertTrue(json, json.indexOf("\"progress\" : 0.0") >= 0);

        // Set 'processCount' for run '01'
        propSetBean = new PropSetBean();
        propSetBean.setVersion(0);
        propSetBean.setValue("125");
        json = api.setTestRunProperty("TEST01", "001", "processCount", propSetBean);
        assertTrue(json, json.indexOf("\"version\" : 1") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"500\"") >= 0);
        assertTrue(json, json.indexOf("\"value\" : \"125\"") >= 0);
        assertTrue(json, json.indexOf("\"title\" : \"Process Count\"") >= 0);
        dbObject = dao.getProperty("TEST01", "001", "processCount");
        assertEquals("500", dbObject.get(FIELD_DEFAULT));
        assertEquals("125", dbObject.get(FIELD_VALUE));

        // Get the test run by its state
        checkTestRunState("TEST01", "001", TestRunState.NOT_SCHEDULED, true);
        checkTestRunState("TEST01", "001", TestRunState.SCHEDULED, false);

        // Delete the test run
        api.deleteTestRun("TEST01", "001", true);
        try {
            api.getTestRun("TEST01", "001");
        } catch (WebApplicationException e) {
            assertEquals(Status.NOT_FOUND.getStatusCode(), e.getResponse().getStatus());
        }
    }

    /**
     * Copy test run
     */
    @Test
    public void testScenario02() {
        DBObject dbObject;
        String json;
        TestDetails testDetails;
        TestRunDetails testRunDetails;
        PropSetBean propSetBean;

        testDetails = new TestDetails();
        testDetails.setName("T02");
        testDetails.setRelease(RELEASE);
        testDetails.setSchema(SCHEMA);
        testDetails.setDescription("A test for scenario 02.");
        json = api.createTest(testDetails);

        // Set 'proc.pwd' at the test level
        propSetBean = new PropSetBean();
        propSetBean.setVersion(0);
        propSetBean.setValue("pwd1");
        json = api.setTestProperty("T02", "proc.pwd", propSetBean);

        // Copy the test
        testDetails.setName("T02_CP");
        testDetails.setRelease(null);
        testDetails.setSchema(null);
        testDetails.setDescription(null);
        testDetails.setCopyOf("T02");
        testDetails.setVersion(0);
        json = api.createTest(testDetails);

        assertTrue(json, json.indexOf("T02_CP") >= 0);
        assertTrue(json, json.indexOf("A test for scenario 02.") >= 0);

        assertEquals("Expect exact match from createTest and getTest", json, api.getTest("T02_CP"));

        assertTrue(api.getTests(RELEASE, SCHEMA, 0, 2).contains("\"name\" : \"T02\""));
        assertTrue(api.getTests(RELEASE, SCHEMA, 0, 2).contains("\"name\" : \"T02_CP\""));

        json = api.getTestProperty("T02_CP", "proc.pwd");
        assertFalse(json, json.indexOf("pwd1") >= 0);
        assertTrue(json, json.indexOf(MASK) >= 0);
        assertTrue(json,
                json.indexOf("\"description\" : \"The password for the user executing the process.\"") >= 0);
        dbObject = dao.getProperty("T02_CP", null, "proc.pwd");
        assertEquals("NEVER-LEAVES-THE-SERVER", dbObject.get(FIELD_DEFAULT));
        assertEquals("pwd1", dbObject.get(FIELD_VALUE));

        // Create run 01
        testRunDetails = new TestRunDetails();
        testRunDetails.setName("01");
        testRunDetails.setDescription("Scenario 02 - Run 01");
        json = api.createTestRun("T02_CP", testRunDetails);
        assertTrue(json, json.indexOf("\"description\" : \"Scenario 02 - Run 01\"") >= 0);
        assertFalse(json, json.indexOf("\"default\" : \"NEVER-LEAVES-THE-SERVER\"") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"admin\"") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"" + MASK) >= 0);

        // Copy-create run 02
        testRunDetails.setDescription(null);
        testRunDetails.setName("02");
        testRunDetails.setCopyOf("01");
        testRunDetails.setVersion(0);
        json = api.createTestRun("T02_CP", testRunDetails);
        assertTrue(json, json.indexOf("\"description\" : \"Scenario 02 - Run 01\"") >= 0);
        assertFalse(json, json.indexOf("\"default\" : \"NEVER-LEAVES-THE-SERVER\"") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"admin\"") >= 0);
        assertTrue(json, json.indexOf("\"default\" : \"" + MASK) >= 0);
    }

    /**
     * Schedule a test run
     */
    @Test
    public void testScenario03() {
        String json;
        TestDetails testDetails;
        TestRunDetails testRunDetails;
        PropSetBean propSetBean;

        testDetails = new TestDetails();
        testDetails.setName("T03");
        testDetails.setRelease(RELEASE);
        testDetails.setSchema(SCHEMA);
        testDetails.setDescription("A test for scenario 03.");
        json = api.createTest(testDetails);

        // Set 'proc.pwd' at the test level
        propSetBean = new PropSetBean();
        propSetBean.setVersion(0);
        propSetBean.setValue("pwd1");
        json = api.setTestProperty("T03", "proc.pwd", propSetBean);

        // Create run 01
        testRunDetails = new TestRunDetails();
        testRunDetails.setName("01");
        testRunDetails.setDescription("Scenario 03 - Run 01");
        json = api.createTestRun("T03", testRunDetails);

        // Set 'processCount' for run '01'
        propSetBean = new PropSetBean();
        propSetBean.setVersion(0);
        propSetBean.setValue("125");
        json = api.setTestRunProperty("T03", "01", "processCount", propSetBean);

        // Schedule the run
        TestRunSchedule testRunSchedule = new TestRunSchedule();
        testRunSchedule.setVersion(5); // Wrong version
        testRunSchedule.setScheduled(System.currentTimeMillis());
        try {
            api.scheduleTestRun("T03", "01", testRunSchedule);
            fail("Version number not checked for test run scheduling.");
        } catch (WebApplicationException e) {
            // Expected
        }
        testRunSchedule.setVersion(0); // Correct
        json = api.scheduleTestRun("T03", "01", testRunSchedule);
        assertTrue("'scheduled' not set", json.contains("" + testRunSchedule.getScheduled()));

        // Get the test run by its state
        checkTestRunState("T03", "01", TestRunState.NOT_SCHEDULED, false);
        checkTestRunState("T03", "01", TestRunState.SCHEDULED, true);
        checkTestRunState("T03", "01", TestRunState.STARTED, false);
    }

    /**
     * Helper method to create a test run
     */
    private String createTestRun(String test, String testDescription, String run, String runDescription) {
        TestDetails testDetails;
        TestRunDetails testRunDetails;

        testDetails = new TestDetails();
        testDetails.setName(test);
        testDetails.setRelease(RELEASE);
        testDetails.setSchema(SCHEMA);
        testDetails.setDescription(testDescription);
        try {
            api.createTest(testDetails);
        } catch (WebApplicationException e) {
            if (e.getResponse().getStatus() == Status.CONFLICT.getStatusCode()) {
                // Test already exists
            } else {
                throw e;
            }
        }

        // Create run 01
        testRunDetails = new TestRunDetails();
        testRunDetails.setName(run);
        testRunDetails.setDescription(runDescription);
        return api.createTestRun(test, testRunDetails);
    }

    /**
     * Locking of properties and state change protection
     */
    @Test
    public synchronized void testScenario04() {
        String json = createTestRun("T04", "A test for scenario 04.", "01", "Scenario 04 - Run 01");

        // First check that we get the correct response for a missing test run
        try {
            api.getTestRunSummary("Fred", "01");
            fail("Missing test must throw NOT FOUND.");
        } catch (WebApplicationException e) {
            assertEquals(Status.NOT_FOUND.getStatusCode(), e.getResponse().getStatus());
        }
        try {
            api.getTestRunSummary("T04", "Fred");
            fail("Missing test run must throw NOT FOUND.");
        } catch (WebApplicationException e) {
            assertEquals(Status.NOT_FOUND.getStatusCode(), e.getResponse().getStatus());
        }

        // It should not have started
        json = api.getTestRunSummary("T04", "01");
        assertTrue(json.contains("\"started\" : -1"));

        // Attempt to override the test run properties
        json = api.getTestRunProperty("T04", "01", "proc.pwd");
        PropSetBean propSetBean = new PropSetBean();
        propSetBean.setValue("NEW VALUE BEFORE START");
        propSetBean.setVersion(0);
        api.setTestRunProperty("T04", "01", "proc.pwd", propSetBean);
        try {
            // Using an incorrect version
            api.setTestRunProperty("T04", "01", "proc.pwd", propSetBean);
        } catch (WebApplicationException e) {
            assertEquals(Status.CONFLICT.getStatusCode(), e.getResponse().getStatus());
        }

        // Point to the correct MongoDB
        propSetBean.setValue(mongoHost);
        propSetBean.setVersion(0);
        api.setTestRunProperty("T04", "01", PROP_MONGO_TEST_HOST, propSetBean);

        // Monitor the test. We do this here so that we don't have to wait a
        // long time for the monitor to kick in.
        // The defaults here do not matter as the test definition was written in
        // the @Before phase
        org.alfresco.bm.test.Test test = ctx.getBean(org.alfresco.bm.test.Test.class);

        // Extract the test run wrapper
        DBObject testRunObj = null;
        try {
            testRunObj = dao.getTestRun("T04", "01", false);
        } catch (ObjectNotFoundException e1) {
            fail("Should find 'T04.01'!");
        }
        ObjectId testRunId = (ObjectId) testRunObj.get(FIELD_ID);
        TestRun testRun = test.getTestRun(testRunId);

        // The test run should not even be on the menu
        assertNull("Should not even be considering the test run", testRun);

        // Now schedule it
        TestRunSchedule schedule = new TestRunSchedule();
        schedule.setScheduled(System.currentTimeMillis() + 20000L); // It
                                                                    // is
                                                                    // scheduled
                                                                    // for
                                                                    // 20s
                                                                    // from
                                                                    // now
        schedule.setVersion(0);
        json = api.scheduleTestRun("T04", "01", schedule);

        // Poke the test and check that the test run's new state brings it into
        // view
        test.forcePing();
        testRun = test.getTestRun(testRunId);
        assertNotNull("The test should have found the test run as it is now scheduled.", testRun);
        assertNull("The test run has not hit scheduled time so it must have started its context.",
                testRun.getCtx());
        // The test run should not have started as the scheduled time will not
        // have passed
        checkTestRunState("T04", "01", TestRunState.SCHEDULED, true);
        checkTestRunState("T04", "01", TestRunState.STARTED, false);

        // Now update the schedule to make it runnable immediately
        schedule.setScheduled(System.currentTimeMillis() - 20000L); // Overdue
        schedule.setVersion(1);
        json = api.scheduleTestRun("T04", "01", schedule);
        test.forcePing();
        assertNotNull("Should be a test run context", testRun.getCtx());
        checkTestRunState("T04", "01", TestRunState.SCHEDULED, false);
        checkTestRunState("T04", "01", TestRunState.STARTED, true);

        // Attempt to override the test run properties
        json = api.getTestRunProperty("T04", "01", "proc.pwd");
        propSetBean = new PropSetBean();
        propSetBean.setValue("NEW VALUE AFTER START");
        propSetBean.setVersion(1);
        try {
            api.setTestRunProperty("T04", "01", "proc.pwd", propSetBean);
            fail("Should not be able to override properties of a running test.");
        } catch (WebApplicationException e) {
            assertEquals(Status.FORBIDDEN.getStatusCode(), e.getResponse().getStatus());
        }

        // Terminate
        checkTestRunState("T04", "01", TestRunState.STARTED, true);
        json = api.terminateTestRun("T04", "01");
        checkTestRunState("T04", "01", TestRunState.STOPPED, true);
        String nowStr = "" + System.currentTimeMillis();
        assertTrue(json.contains("\"stopped\" : " + nowStr.substring(0, 5)));

        // Must still not be able to edit any properties
        try {
            api.setTestRunProperty("T04", "01", "proc.pwd", propSetBean);
            fail("Should not be able to override properties of a terminated test.");
        } catch (WebApplicationException e) {
            assertEquals(Status.FORBIDDEN.getStatusCode(), e.getResponse().getStatus());
        }

        // The test run should have stopped
        test.forcePing();
        assertNull("Should NOT be a test run context after termination", testRun.getCtx());

        // Make sure that the test drops the test run from consideration
        test.forcePing();
        assertNull("Test must drop a completed test run", test.getTestRun(testRunId));

        try {
            test.stop();
        } catch (Exception e) {
            fail("Test should not fail on stop!");
        }
    }

    /**
     * Check that progress updates are made automatically and that the test run
     * automatically
     * stops and closes down.
     */
    @Test
    public synchronized void testScenario05() throws Exception {
        createTestRun("T05", "A test for scenario 05.", "01", "Scenario 05 - Run 01");

        // Monitor the test. We do this here so that we don't have to wait a
        // long time for the monitor to kick in.
        // The defaults here do not matter as the test definition was written in
        // the @Before phase
        org.alfresco.bm.test.Test test = ctx.getBean(org.alfresco.bm.test.Test.class);

        // Force an immediate ping update
        test.forcePing();

        // Extract the test run wrapper
        DBObject testRunObj = null;
        try {
            testRunObj = dao.getTestRun("T05", "01", false);
        } catch (ObjectNotFoundException e1) {
            fail("Should find 'T05.01'!");
        }
        ObjectId testRunId = (ObjectId) testRunObj.get(FIELD_ID);
        TestRun testRun = test.getTestRun(testRunId);
        assertNull("The test run should not be instantiated until it has been scheduled.", testRun);

        // Now schedule it
        TestRunSchedule schedule = new TestRunSchedule();
        schedule.setScheduled(System.currentTimeMillis());
        schedule.setVersion(0);
        api.scheduleTestRun("T05", "01", schedule);

        // Point to the correct MongoDB
        PropSetBean propSetBean = new PropSetBean();
        propSetBean.setValue(mongoHost);
        propSetBean.setVersion(0);
        api.setTestRunProperty("T05", "01", PROP_MONGO_TEST_HOST, propSetBean);

        // Force another ping, which will activate the test run
        test.forcePing();
        testRun = test.getTestRun(testRunId);
        assertNotNull("The test run shouldbe instantiated once it has been scheduled.", testRun);

        // Loop a bit to allow the test run to complete
        boolean completed = false;
        for (int i = 0; i < 10; i++) {
            this.wait(1000L);
            // Need to keep checking progress
            test.forcePing();
            String testRunStateJson = api.getTestRunSummary("T05", "01");
            if (testRunStateJson.contains(TestRunState.COMPLETED.toString())) {
                completed = true;
                break; // This is what we were looking for
            }
        }
        checkTestRunState("T05", "01", TestRunState.COMPLETED, true);

        // Check that it's completed
        assertTrue("Test run did not progress to completion: " + api.getTestRunSummary("T05", "01"), completed);

        // Force another ping, which will deactivate the test run
        test.forcePing();

        // Terminate
        try {
            api.terminateTestRun("T05", "01");
            fail("Cannot terminate a test run that has completed.");
        } catch (WebApplicationException e) {
            assertEquals(Status.CONFLICT.getStatusCode(), e.getResponse().getStatus());
        }

        // The test run should have completed
        assertNull("Should NOT be a test run context after completion", testRun.getCtx());
    }

    /**
     * Point the test at a missing MongoDB
     */
    @Test
    public synchronized void testScenario06() throws Exception {
        createTestRun("T06", "A test for scenario 06.", "01", "Scenario 06 - Run 01");

        // Set an incorrect MongoDB host
        PropSetBean propSetBean = new PropSetBean();
        propSetBean.setValue("localhostFAIL");
        propSetBean.setVersion(0);
        api.setTestRunProperty("T06", "01", PROP_MONGO_TEST_HOST, propSetBean);

        // Direct monitor
        org.alfresco.bm.test.Test test = ctx.getBean(org.alfresco.bm.test.Test.class);

        // Now schedule it
        TestRunSchedule schedule = new TestRunSchedule();
        schedule.setScheduled(System.currentTimeMillis());
        schedule.setVersion(0);
        api.scheduleTestRun("T06", "01", schedule);

        // Poke the test and check that the test run's new state brings it into
        // view
        test.forcePing();
        // The test run should not have started as the mongo host is invalid
        // from server 2.1 on the test would be STOPPED if incorrect server
        // connection - because otherwise it will restart over and over again.
        // depending on the test check speed the status would be scheduled or
        // stopped - but must never be started!
        checkTestRunState("T06", "01", TestRunState.STARTED, false);

        // Now check that the logs reflect the situation
        LogWatcher logWatcher = ctx.getBean(LogWatcher.class);
        boolean found = false;
        for (String logFilename : logWatcher.getLogFilenames()) {
            File logFile = new File(logFilename);
            Scanner scanner = new Scanner(logFile);
            try {
                String scanned = scanner.findWithinHorizon("localhostFAIL", 0);
                if (scanned != null) {
                    found = true;
                    break;
                }
            } finally {
                scanner.close();
            }
        }
        assertTrue("Failed to find error message for MongoDB host.", found);

        test.stop();
    }

    /**
     * Utility method to create and execute a test run
     */
    private void executeTestRun(String testName, String testDescription, String runName, String runDescription)
            throws Exception {
        createTestRun(testName, testDescription, runName, runDescription);
        org.alfresco.bm.test.Test test = ctx.getBean(org.alfresco.bm.test.Test.class);
        test.forcePing();

        // Now schedule it
        TestRunSchedule schedule = new TestRunSchedule();
        schedule.setScheduled(System.currentTimeMillis());
        schedule.setVersion(0);
        api.scheduleTestRun(testName, runName, schedule);

        // Point to the correct MongoDB
        String testHost = api.getTestRunProperty(testName, runName, PROP_MONGO_TEST_HOST);
        PropSetBean propSetBean = new PropSetBean();
        propSetBean.setValue(mongoHost);
        propSetBean.setVersion(0);
        api.setTestRunProperty(testName, runName, PROP_MONGO_TEST_HOST, propSetBean);

        // Force another ping, which will activate the test run
        test.forcePing();

        // Loop a bit to allow the test run to complete
        boolean completed = false;
        for (int i = 0; i < 10; i++) {
            this.wait(1000L);
            // Need to keep checking progress
            test.forcePing();
            String testRunSummaryJson = api.getTestRunSummary(testName, runName);
            Map<String, Object> testRunSummaryMap = fromJson(testRunSummaryJson);
            if (TestRunState.COMPLETED.name().equals((String) testRunSummaryMap.get(FIELD_STATE))) {
                completed = true;
                break; // This is what we were looking for
            }
        }
        checkTestRunState(testName, runName, TestRunState.COMPLETED, true);

        // Check that it's completed
        assertTrue("Test run did not progress to completion: " + api.getTestRunSummary(testName, runName),
                completed);

        // Force another ping, which will deactivate the test run
        test.forcePing();

    }

    /**
     * Checks the test application APIs for retrieving test results.
     */
    @Test
    public synchronized void testScenario07() throws Exception {
        /*
         * In-memory byte[] are used because we know the size of the documents
         * will be manageable.
         */

        executeTestRun("T07", "A test for scenario 07.", "01", "Scenario 07 - Run 01");

        // Get report for test run that does not exist
        try {
            ResultsRestAPI resultsAPI = api.getTestRunResultsAPI("T07MISSING", "01");
            resultsAPI.getReportCSV();
            Assert.fail("Expected exception regarding missing test.");
        } catch (WebApplicationException e) {
            assertEquals(Status.NOT_FOUND.getStatusCode(), e.getResponse().getStatus());
        }

        ResultsRestAPI resultsAPI = api.getTestRunResultsAPI("T07", "01");

        // Get the results CSV
        StreamingOutput csvOutput = resultsAPI.getReportCSV();
        ByteArrayOutputStream csvBos = new ByteArrayOutputStream();
        csvBos.close();
        csvOutput.write(csvBos);
        String csvResults = new String(csvBos.toByteArray());
        // Check
        assertTrue(csvResults.contains("Data:,bm21-data.T07.01.results"));
        assertTrue(csvResults.contains("Started"));
        assertTrue(csvResults.contains("Finished"));
        assertTrue(csvResults.contains("Duration"));

        // Get the JSON results
        String chartJson = resultsAPI.getTimeSeriesResults(0L, "seconds", 1, 5, false);
        assertTrue(chartJson.startsWith("[ { \"time\" : "));
        assertTrue(chartJson.contains("[ { \"time\" : "));
        assertTrue(chartJson.contains("000 , \"name\" : \"start\" , \"mean\" : "));
        assertTrue(chartJson.endsWith(" , \"fail\" : 0 , \"failPerSec\" : 0.0}]"));

        // Get the XLSX report
        StreamingOutput xlsxOutput = resultsAPI.getReportXLSX();
        ByteArrayOutputStream xlsxBos = new ByteArrayOutputStream();
        xlsxOutput.write(xlsxBos);
        xlsxBos.close();
        ByteArrayInputStream xlsxBis = new ByteArrayInputStream(xlsxBos.toByteArray());
        XSSFWorkbook xlsxWorkbook = new XSSFWorkbook(xlsxBis);
        xlsxBis.close();
        // Write it to a temp file just for fun
        File xlsxFile = File.createTempFile(UUID.randomUUID().toString(), ".xlsx");
        FileOutputStream xlsxFos = new FileOutputStream(xlsxFile);
        xlsxWorkbook.write(xlsxFos);
        xlsxFos.close();
        logger.info("XLSX report found here: " + xlsxFile);

        // Check
        POIXMLProperties xlsxProperties = xlsxWorkbook.getProperties();
        CoreProperties xlsxCoreProperties = xlsxProperties.getCoreProperties();
        Assert.assertNotNull("No XLSX description: " + xlsxFile, xlsxCoreProperties.getDescription());
        Assert.assertTrue("Title in XLSX must contain test run FQN: " + xlsxFile,
                xlsxCoreProperties.getTitle().contains("T07.01"));
        Assert.assertTrue("Description in XLSX must contain test name and description: " + xlsxFile,
                xlsxCoreProperties.getDescription().contains("A test for scenario 07."));
        Assert.assertTrue("Description in XLSX must contain test name and description." + xlsxFile,
                xlsxCoreProperties.getDescription().contains("Scenario 07 - Run 01"));
        xlsxWorkbook.close();
    }

    /**
     * Checks that test run deletion removes the associated collections
     */
    @Test
    public synchronized void testScenario08() throws Exception {
        MongoClient testMongoClient = new MongoClientFactory(new MongoClientURI(MONGO_PREFIX + mongoHost), null,
                null).getObject();
        DB testMongoDB = new MongoDBFactory(testMongoClient, MONGO_TEST_DATABASE).getObject();
        Set<String> testRunCollections = removeSystemValues(testMongoDB.getCollectionNames());
        Assert.assertEquals("Unexpected number of collections in results: " + testRunCollections, 0,
                testRunCollections.size());

        executeTestRun("T08", "A test for scenario 08.", "01", "Scenario 08 - Run 01");

        testRunCollections = removeSystemValues(testMongoDB.getCollectionNames());
        Assert.assertEquals("Unexpected number of collections in results: " + testRunCollections, 5,
                testRunCollections.size());
        Assert.assertTrue(testRunCollections.contains("T08.01.events"));
        Assert.assertTrue(testRunCollections.contains("T08.01.results"));
        Assert.assertTrue(testRunCollections.contains("T08.01.sessions"));

        // Delete the test run
        api.deleteTestRun("T08", "01", true);
        testRunCollections = removeSystemValues(testMongoDB.getCollectionNames());
        Assert.assertEquals("Unexpected number of collections in results: " + testRunCollections, 2,
                testRunCollections.size());
    }

    /**
     * Checks that test deletion removes the associated collections
     */
    @Test
    public synchronized void testScenario09() throws Exception {
        MongoClient testMongoClient = new MongoClientFactory(new MongoClientURI(MONGO_PREFIX + mongoHost), null,
                null).getObject();
        DB testMongoDB = new MongoDBFactory(testMongoClient, MONGO_TEST_DATABASE).getObject();
        Set<String> testRunCollections = removeSystemValues(testMongoDB.getCollectionNames());
        Assert.assertEquals("Unexpected number of collections in results: " + testRunCollections, 0,
                testRunCollections.size());

        executeTestRun("T09", "A test for scenario 09.", "01", "Scenario 09 - Run 01");
        executeTestRun("T09", "A test for scenario 09.", "02", "Scenario 09 - Run 02");

        testRunCollections = removeSystemValues(testMongoDB.getCollectionNames());
        Assert.assertEquals("Unexpected number of collections in results: " + testRunCollections, 8,
                testRunCollections.size());
        Assert.assertTrue(testRunCollections.contains("T09.01.events"));
        Assert.assertTrue(testRunCollections.contains("T09.01.results"));
        Assert.assertTrue(testRunCollections.contains("T09.01.sessions"));
        Assert.assertTrue(testRunCollections.contains("T09.02.events"));
        Assert.assertTrue(testRunCollections.contains("T09.02.results"));
        Assert.assertTrue(testRunCollections.contains("T09.02.sessions"));

        // Delete the test run
        api.deleteTest("T09", true);
        testRunCollections = removeSystemValues(testMongoDB.getCollectionNames());
        Assert.assertEquals("Unexpected number of collections in results: " + testRunCollections, 2,
                testRunCollections.size());
    }

    @Test
    public synchronized void testScenario10() throws Exception {
        // Create the test
        executeTestRun("T10", "A test for scenario 10.", "01", "Scenario 10 - Run 01");
        // Now get the registered drivers
        String json = api.getTestDrivers("T10", true);
        assertTrue("Driver not found for test: " + json, json.contains("\"expires\" : { \"$date\" : \"20"));
    }

    /**
     * Extra data creation and export of XSLX Workbook
     * 
     * @throws Exception
     */
    @Test
    public synchronized void testScenario11() throws Exception {
        // test definitions and extra data sheets
        String test = "T11";
        String run = "01";
        String driver = UUID.randomUUID().toString();
        String sheet1 = "extra.data.sheet1";
        String sheet2 = "extra.data.sheet2";
        String sheet3 = "extra.data.sheet3";
        String[] fieldNames = { "tf1", "tf2", "tf3", "tf4", "tf5" };
        String[] descriptions = { "Test Field 1", "Test Field 2", "Test Field 3", "Test Field 4", "Test Field 5" };
        String[] values = new String[5];

        executeTestRun(test, "A test for scenario 11.", run, "Scenario 11 - Run 01");

        // get the extra data report service
        DataReportService reportService = this.testRunServicesCache.getDataReportService(test, run);
        assertNotNull(reportService);

        // create a random value array
        for (int i = 0; i < 5; i++) {
            values[i] = UUID.randomUUID().toString();
        }

        // append some extra data and a description
        reportService.appendData(driver, test, run, sheet1, fieldNames, values);
        reportService.appendData(driver, test, run, sheet2, fieldNames, values);
        reportService.appendData(driver, test, run, sheet2, fieldNames, values);
        reportService.appendData(driver, test, run, sheet3, fieldNames, values);
        reportService.appendData(driver, test, run, sheet3, fieldNames, values);
        reportService.appendData(driver, test, run, sheet3, fieldNames, values);
        reportService.setDescription(driver, test, run, sheet3, fieldNames, descriptions);

        // create and download an XSLX
        ResultsRestAPI resultsAPI = api.getTestRunResultsAPI(test, run);
        StreamingOutput xlsxOutput = resultsAPI.getReportXLSX();
        ByteArrayOutputStream xlsxBos = new ByteArrayOutputStream();
        xlsxOutput.write(xlsxBos);
        xlsxBos.close();
        ByteArrayInputStream xlsxBis = new ByteArrayInputStream(xlsxBos.toByteArray());
        XSSFWorkbook xlsxWorkbook = new XSSFWorkbook(xlsxBis);
        xlsxBis.close();
        File xlsxFile = File.createTempFile(UUID.randomUUID().toString(), ".xlsx");
        FileOutputStream xlsxFos = new FileOutputStream(xlsxFile);
        xlsxWorkbook.write(xlsxFos);
        xlsxFos.close();

        // validate data in DB
        assertNull("Expected no description row for sheet 1, but found one?",
                reportService.getDescription(null, test, run, sheet1));
        assertNull("Expected no description row for sheet 2, but found one?",
                reportService.getDescription(null, test, run, sheet2));
        List<String> descList = reportService.getDescription(null, test, run, sheet3);
        assertNotNull("Expected a description row for sheet 3, but can't find any ...", descList);
        int count = 0;
        for (String desc : descList) {
            assertEquals(
                    "Description of sheet 3 mismatch. Expected '" + descriptions[count] + "' found '" + desc + "'.",
                    descriptions[count++], desc);
        }

        // validate number of sheets
        String[] sheetNames = reportService.getSheetNames(null, test, run);
        assertEquals("Expected 3 sheets of extra data, found: " + sheetNames.length, 3, sheetNames.length);

        // validate number of entries for each extra data
        int countTestEntries = 0;
        for (int j = 0; j < 3; j++) {
            DBCursor dbCursor = reportService.getData(null, test, run, sheetNames[j]);
            while (null != dbCursor && dbCursor.hasNext()) {
                List<String> valuesFromMongo = reportService.getNextValueRow(dbCursor);
                countTestEntries++;
                assertEquals("Expected 5 columns in extra data, found: " + valuesFromMongo.size(), 5,
                        valuesFromMongo.size());
            }
        }
        assertEquals("Expected 6 lines of extra data ... found: " + countTestEntries, 6, countTestEntries);

        // delete test
        api.deleteTest(test, true);

        // validate
        assertNull("Sheet names expected to be empty.", reportService.getSheetNames(null, test, run));
        assertNull("Description of sheet 3 expected to be empty.",
                reportService.getDescription(null, test, run, sheet3));

        // clean-up: delete XLSX file
        xlsxWorkbook.close();
        xlsxFile.delete();
    }
}