package unittesting;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.auth.InvalidCredentialsException;
import com.mendix.thirdparty.org.json.JSONArray;
import com.mendix.thirdparty.org.json.JSONObject;
import unittesting.proxies.TestSuite;
import unittesting.proxies.UnitTest;
import unittesting.proxies.UnitTestResult;
import com.mendix.core.Core;
import com.mendix.core.CoreException;
import com.mendix.externalinterface.connector.RequestHandler;
import com.mendix.logging.ILogNode;
import com.mendix.m2ee.api.IMxRuntimeRequest;
import com.mendix.m2ee.api.IMxRuntimeResponse;
import com.mendix.systemwideinterfaces.core.IContext;
import communitycommons.XPath;
public class RemoteApiServlet extends RequestHandler {
private static final Object COMMAND_START = "start";
private static final Object COMMAND_STATUS = "status";
private static final String PARAM_PASSWORD = "password";
private final String password;
private boolean detectedUnitTests = false;
private final static ILogNode LOG = TestManager.LOG;
private volatile TestSuiteRunner testSuiteRunner;
public RemoteApiServlet(String password) {
this.password = password;
}
@Override
protected void processRequest(IMxRuntimeRequest req,
IMxRuntimeResponse resp, String path) throws Exception {
HttpServletRequest request = req.getHttpServletRequest();
HttpServletResponse response = resp.getHttpServletResponse();
try {
if (!"POST".equals(request.getMethod()))
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
else if (COMMAND_START.equals(path))
serveRunStart(request, response, path);
else if (COMMAND_STATUS.equals(path))
serveRunStatus(request, response, path);
else
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
catch (IllegalArgumentException e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
write(response, e.getMessage());
}
catch (InvalidCredentialsException e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
write(response, "Invalid password provided");
}
}
private void write(HttpServletResponse response, String data) {
try {
response.getOutputStream().write(data.getBytes("UTF-8"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private synchronized void serveRunStatus(HttpServletRequest request,
HttpServletResponse response, String path) throws Exception {
JSONObject input = parseInput(request);
verifyPassword(input);
if (testSuiteRunner == null) {
throw new IllegalArgumentException("No testrun was started yet");
}
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("Content-Type", "application/json");
write(response, testSuiteRunner.getStatus().toString(4));
}
private synchronized void serveRunStart(HttpServletRequest request,
HttpServletResponse response, String path) throws IOException, CoreException, InvalidCredentialsException {
JSONObject input = parseInput(request);
verifyPassword(input);
IContext context = Core.createSystemContext();
if (!detectedUnitTests) {
TestManager.instance().findAllTests(context);
detectedUnitTests = true;
}
if (testSuiteRunner != null && !testSuiteRunner.isFinished()) {
throw new IllegalArgumentException("Cannot start a test run while another test run is still running");
}
LOG.info("[remote api] starting new test run");
testSuiteRunner = new TestSuiteRunner();
Thread t = new Thread() {
@Override
public void run() {
testSuiteRunner.run();
}
};
t.start();
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
private void verifyPassword(JSONObject input) throws InvalidCredentialsException {
if (!input.has(PARAM_PASSWORD)) {
LOG.warn("[remote api] Missing password");
throw new IllegalArgumentException("No '" + PARAM_PASSWORD + "' attribute found in the JSON body. Please provide a password");
}
if (!password.equals(input.getString(PARAM_PASSWORD))) {
LOG.warn("[remote api] Invalid password");
throw new InvalidCredentialsException();
}
}
private JSONObject parseInput(HttpServletRequest request) throws IOException {
String data = IOUtils.toString(request.getInputStream());
return new JSONObject(data);
}
private class TestSuiteRunner {
boolean finished = false;
long startTime = System.currentTimeMillis();
long totalTime = -1;
public void run() {
try {
TestManager.instance().runTestSuites();
} catch (CoreException e) {
LOG.error("[remote api] error while running test suite: " + e.getMessage(), e);
} finally {
totalTime = System.currentTimeMillis() - startTime;
finished = true;
LOG.info("[remote api] finished test run");
}
}
public synchronized boolean isFinished() {
return finished;
}
public synchronized JSONObject getStatus() throws CoreException {
JSONObject result = new JSONObject();
result.put("completed", this.finished);
result.put("runtime", totalTime);
IContext context = Core.createSystemContext();
long count = 0l;
long failures = 0l;
for(TestSuite suite : XPath.create(context, TestSuite.class).all()) {
count += suite.getTestCount();
failures += suite.getTestFailedCount();
}
result.put("tests", count);
result.put("failures", failures);
JSONArray failedTests = new JSONArray();
result.put("failed_tests", failedTests);
for(UnitTest test : XPath.create(context, UnitTest.class)
//failed tests
.eq(UnitTest.MemberNames.Result, UnitTestResult._2_Failed)
//in testsuites which are not running anymore
.eq(UnitTest.MemberNames.UnitTest_TestSuite, TestSuite.entityName, TestSuite.MemberNames.Result, UnitTestResult._2_Failed)
.all())
{
JSONObject i = new JSONObject();
i.put("name", test.getName());
i.put("error", test.getResultMessage());
i.put("step", test.getLastStep());
failedTests.put(i);
}
return result;
}
}
}