// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.htmlmonitor; import static org.junit.Assert.*; import java.io.File; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.junit.After; import org.junit.Before; import org.junit.Test; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.IEventType; import de.ugoe.cs.autoquest.eventcore.gui.KeyboardFocusChange; import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction; import de.ugoe.cs.autoquest.eventcore.gui.MouseClick; import de.ugoe.cs.autoquest.eventcore.gui.MouseDoubleClick; import de.ugoe.cs.autoquest.eventcore.gui.Scroll; import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; import de.ugoe.cs.autoquest.plugin.html.HTMLLogParser; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer; import de.ugoe.cs.util.console.TextConsole; /** * * @author Patrick Harms */ public class HtmlMonitorTest { /** * */ public static final TextConsole CONSOLE = new TextConsole(); /** * */ private final static String LOG_FILE_DIR = "target/tmp/logfiles/"; /** * */ private static final int PORT = 19098; /** * */ private HtmlMonitor htmlMonitor; /** * */ @Before public void setUp() throws Exception { htmlMonitor = new HtmlMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) }); htmlMonitor.init(); htmlMonitor.start(); } /** * */ @After public void tearDown() throws Exception { if (htmlMonitor != null) { try { htmlMonitor.stop(); } finally { htmlMonitor = null; } } deleteFiles(new File(LOG_FILE_DIR)); } /** * */ @Test public void testOneSimpleMessage() throws Exception { String clientId = "123"; String message = "{" + " \"message\": {" + " \"clientInfos\": {" + " \"clientId\":\"" + clientId + "\"," + " \"userAgent\":\"Agent\"," + " \"title\":\"Title\"," + " \"url\":\"http://host/path\"" + " }," + " \"guiModel\": {" + " \"tagName\":\"html\"," + " \"index\":\"0\"," + " \"children\":" + " [ {" + " \"tagName\":\"head\"," + " \"index\":\"0\"," + " }," + " {" + " \"tagName\":\"body\"," + " \"htmlId\":\"gsr\"," + " }" + " ]" + " }," + " \"events\":" + " [ {" + " \"time\":\"12345\"," + " \"path\":\"/html[0]/body(htmlId=gsr)\"," + " \"eventType\":\"onclick\"" + " \"coordinates\": [\"194\", \"7\"]" + " }" + " ]" + " }" + "}"; sendMessageAndAssertResponse(message); htmlMonitor.stop(); htmlMonitor = null; File logFile = new File(LOG_FILE_DIR + File.separator + "host" + File.separator + clientId + File.separator + "htmlmonitor_" + clientId + "_000.log"); assertTrue(logFile.exists()); HTMLLogParser parser = new HTMLLogParser(); parser.parseFile(logFile); // check the GUI model GUIModel guiModel = parser.getGuiModel(); assertNotNull(guiModel); List nodes = guiModel.getRootElements(); assertNotNull(nodes); assertEquals(1, nodes.size()); // get server node IGUIElement node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLServer); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get document node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLDocument); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get html node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored // get body node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertTrue(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(0, nodes.size()); // check the sequences Collection> sequences = parser.getSequences(); assertNotNull(sequences); Iterator> iterator = sequences.iterator(); assertTrue(iterator.hasNext()); List sequence = iterator.next(); assertFalse(iterator.hasNext()); assertNotNull(sequence); assertEquals(1, sequence.size()); assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7); } /** * */ @Test public void testSeveralMessagesInOneSession() throws Exception { String clientId = "123"; String message = "{" + " \"message\": {" + " \"clientInfos\": {" + " \"clientId\":\"" + clientId + "\"," + " \"userAgent\":\"Agent\"," + " \"title\":\"Title\"," + " \"url\":\"http://host/path\"" + " }," + " \"guiModel\": {" + " \"tagName\":\"html\"," + " \"index\":\"0\"," + " \"children\":" + " [ {" + " \"tagName\":\"head\"," + " \"index\":\"0\"," + " }," + " {" + " \"tagName\":\"body\"," + " \"htmlId\":\"gsr\"," + " \"children\":" + " [ {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input1\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input2\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input3\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input4\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input5\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input6\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input7\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input8\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input9\"," + " }," + " {" + " \"tagName\":\"input_button\"," + " \"htmlId\":\"input10\"," + " }," + " ]" + " }" + " ]" + " }," + " \"events\":" + " [ {" + " \"time\":\"1\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input1)\"," + " \"eventType\":\"onclick\"," + " \"coordinates\": [\"194\", \"7\"]" + " }," + " {" + " \"time\":\"2\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input2)\"," + " \"eventType\":\"ondblclick\"," + " \"coordinates\": [\"194\", \"7\"]" + " }," + " {" + " \"time\":\"3\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input3)\"," + " \"eventType\":\"onfocus\"" + " }," + " {" + " \"time\":\"4\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input4)\"," + " \"eventType\":\"onclick\"," + " \"coordinates\": [\"125\", \"14\"]" + " }," + " {" + " \"time\":\"5\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input5)\"," + " \"eventType\":\"onfocus\"" + " }," + " {" + " \"time\":\"6\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input6)\"," + " \"eventType\":\"onfocus\"" + " }," + " {" + " \"time\":\"7\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input7)\"," + " \"eventType\":\"onfocus\"" + " }," + " {" + " \"time\":\"8\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input8)\"," + " \"eventType\":\"onclick\"," + " \"coordinates\": [\"255\", \"4\"]" + " }," + " {" + " \"time\":\"9\"," + " \"path\":\"/html[0]/body(htmlId=gsr)\"," + " \"eventType\":\"onscroll\"," + " \"scrollPosition\": [\"23\", \"567\"]" + " }," + " {" + " \"time\":\"10\"," + " \"path\":\"/html[0]/body(htmlId=gsr)/input_button(htmlId=input10)\"," + " \"eventType\":\"onclick\"," + " \"coordinates\": [\"516\", \"154\"]" + " }" + " ]" + " }" + "}"; sendMessageAndAssertResponse(message); htmlMonitor.stop(); htmlMonitor = null; File logFile = new File(LOG_FILE_DIR + File.separator + "host" + File.separator + clientId + File.separator + "htmlmonitor_" + clientId + "_000.log"); assertTrue(logFile.exists()); HTMLLogParser parser = new HTMLLogParser(); parser.parseFile(logFile); // check the GUI model GUIModel guiModel = parser.getGuiModel(); assertNotNull(guiModel); List nodes = guiModel.getRootElements(); assertNotNull(nodes); assertEquals(1, nodes.size()); // get server node IGUIElement node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLServer); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get document node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLDocument); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get html node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored // get body node IGUIElement body = nodes.get(0); assertNotNull(body); assertTrue(body instanceof HTMLPageElement); assertEquals("HTML", body.getPlatform()); assertTrue(body.isUsed()); nodes = guiModel.getChildren(body); assertNotNull(nodes); assertEquals(10, nodes.size()); // get input nodes for (int i = 0; i < nodes.size(); i++) { node = nodes.get(i); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); if (i != 8) { assertTrue(node.isUsed()); } else { assertFalse(node.isUsed()); } assertNotNull(guiModel.getChildren(node)); assertEquals(0, guiModel.getChildren(node).size()); } // check the sequences Collection> sequences = parser.getSequences(); assertNotNull(sequences); Iterator> iterator = sequences.iterator(); assertTrue(iterator.hasNext()); List sequence = iterator.next(); assertFalse(iterator.hasNext()); assertNotNull(sequence); assertEquals(10, sequence.size()); assertEvent(sequence.get(0), 1, MouseClick.class, nodes.get(0), 194, 7); assertEvent(sequence.get(1), 2, MouseDoubleClick.class, nodes.get(1), 194, 7); assertEvent(sequence.get(2), 3, KeyboardFocusChange.class, nodes.get(2), 0, 0); assertEvent(sequence.get(3), 4, MouseClick.class, nodes.get(3), 125, 14); assertEvent(sequence.get(4), 5, KeyboardFocusChange.class, nodes.get(4), 0, 0); assertEvent(sequence.get(5), 6, KeyboardFocusChange.class, nodes.get(5), 0, 0); assertEvent(sequence.get(6), 7, KeyboardFocusChange.class, nodes.get(6), 0, 0); assertEvent(sequence.get(7), 8, MouseClick.class, nodes.get(7), 255, 4); assertEvent(sequence.get(8), 9, Scroll.class, body, 0, 0); assertEvent(sequence.get(9), 10, MouseClick.class, nodes.get(9), 516, 154); } /** * */ @Test public void testSeveralSessions() throws Exception { String clientId = "123"; String message = "{" + " \"message\": {" + " \"clientInfos\": {" + " \"clientId\":\"" + clientId + "\"," + " \"userAgent\":\"Agent\"," + " \"title\":\"Title\"," + " \"url\":\"http://host/path\"" + " }," + " \"guiModel\": {" + " \"tagName\":\"html\"," + " \"index\":\"0\"," + " \"children\":" + " [ {" + " \"tagName\":\"head\"," + " \"index\":\"0\"," + " }," + " {" + " \"tagName\":\"body\"," + " \"htmlId\":\"gsr\"," + " }" + " ]" + " }," + " \"events\":" + " [ {" + " \"time\":\"12345\"," + " \"path\":\"/html[0]/body(htmlId=gsr)\"," + " \"eventType\":\"onclick\"" + " \"coordinates\": [\"194\", \"7\"]" + " }" + " ]" + " }" + "}"; sendMessageAndAssertResponse(message); int numberOfSessions = 10; for (int i = 0; i < numberOfSessions; i++) { htmlMonitor.stop(); htmlMonitor = new HtmlMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) }); htmlMonitor.init(); htmlMonitor.start(); sendMessageAndAssertResponse(message); } htmlMonitor.stop(); htmlMonitor = null; HTMLLogParser parser = new HTMLLogParser(); // assert 9 already rotated log files for (int i = 0; i < numberOfSessions; i++) { File logFile = new File(LOG_FILE_DIR + File.separator + "host" + File.separator + clientId + File.separator + "htmlmonitor_" + clientId + "_00" + i + ".log"); assertTrue(logFile.exists()); parser.parseFile(logFile); } // check the GUI model GUIModel guiModel = parser.getGuiModel(); assertNotNull(guiModel); List nodes = guiModel.getRootElements(); assertNotNull(nodes); assertEquals(1, nodes.size()); // get server node IGUIElement node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLServer); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get document node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLDocument); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get html node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored // get body node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertTrue(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(0, nodes.size()); // check the sequences Collection> sequences = parser.getSequences(); assertNotNull(sequences); assertEquals(numberOfSessions, sequences.size()); Iterator> iterator = sequences.iterator(); while (iterator.hasNext()) { List sequence = iterator.next(); assertNotNull(sequence); assertEquals(1, sequence.size()); assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7); } } /** * */ @Test public void testRevertOfOldFiles() throws Exception { String clientId = "123"; String message = "{" + " \"message\": {" + " \"clientInfos\": {" + " \"clientId\":\"" + clientId + "\"," + " \"userAgent\":\"Agent\"," + " \"title\":\"Title\"," + " \"url\":\"http://host/path\"" + " }," + " \"guiModel\": {" + " \"tagName\":\"html\"," + " \"index\":\"0\"," + " \"children\":" + " [ {" + " \"tagName\":\"head\"," + " \"index\":\"0\"," + " }," + " {" + " \"tagName\":\"body\"," + " \"htmlId\":\"gsr\"," + " }" + " ]" + " }," + " \"events\":" + " [ {" + " \"time\":\"12345\"," + " \"path\":\"/html[0]/body(htmlId=gsr)\"," + " \"eventType\":\"onclick\"" + " \"coordinates\": [\"194\", \"7\"]" + " }" + " ]" + " }" + "}"; sendMessageAndAssertResponse(message); htmlMonitor.stop(); htmlMonitor = null; File logFile1 = new File(LOG_FILE_DIR + File.separator + "host" + File.separator + clientId + File.separator + "htmlmonitor_" + clientId + "_000.log"); assertTrue(logFile1.exists()); // Move file to a the directory in which it would resist in the old structure and then // restart the server, resend the message and ensure two separate log files in the new // structure File oldLogFile = new File(LOG_FILE_DIR + File.separator + clientId + File.separator + "htmlmonitor_" + clientId + "_000.log"); assertTrue(oldLogFile.getParentFile().mkdirs()); assertTrue(logFile1.renameTo(oldLogFile)); htmlMonitor = new HtmlMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) }); htmlMonitor.init(); htmlMonitor.start(); sendMessageAndAssertResponse(message); htmlMonitor.stop(); htmlMonitor = null; assertTrue(logFile1.exists()); File logFile2 = new File(LOG_FILE_DIR + File.separator + "host" + File.separator + clientId + File.separator + "htmlmonitor_" + clientId + "_001.log"); assertTrue(logFile2.exists()); HTMLLogParser parser = new HTMLLogParser(); parser.parseFile(logFile1); parser.parseFile(logFile2); // check the GUI model GUIModel guiModel = parser.getGuiModel(); assertNotNull(guiModel); List nodes = guiModel.getRootElements(); assertNotNull(nodes); assertEquals(1, nodes.size()); // get server node IGUIElement node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLServer); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get document node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLDocument); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // get html node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertFalse(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored // get body node node = nodes.get(0); assertNotNull(node); assertTrue(node instanceof HTMLPageElement); assertEquals("HTML", node.getPlatform()); assertTrue(node.isUsed()); nodes = guiModel.getChildren(node); assertNotNull(nodes); assertEquals(0, nodes.size()); // check the sequences Collection> sequences = parser.getSequences(); assertNotNull(sequences); Iterator> iterator = sequences.iterator(); assertTrue(iterator.hasNext()); List sequence = iterator.next(); assertNotNull(sequence); assertEquals(1, sequence.size()); assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7); assertTrue(iterator.hasNext()); sequence = iterator.next(); assertFalse(iterator.hasNext()); assertNotNull(sequence); assertEquals(1, sequence.size()); assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7); } /** * */ private void sendMessageAndAssertResponse(String message) throws Exception { DefaultHttpClient httpclient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost("http://localhost:" + PORT + "/"); HttpEntity entity = new StringEntity(message, ContentType.APPLICATION_JSON); httpPost.setEntity(entity); try { HttpResponse response = httpclient.execute(httpPost); // the monitor always returns 200 without any additional information. The client must // never get more or less information. This is especially important for preventing // hackers from finding out more assertEquals(200, response.getStatusLine().getStatusCode()); assertTrue ((response.getEntity() == null) || (response.getEntity().getContentLength() == 0) || ((response.getEntity().getContentLength() == 1) && (response.getEntity().getContent().read() == ' '))); } finally { httpPost.releaseConnection(); } } /** * */ private void assertEvent(Event event, int timestamp, Class eventType, IGUIElement eventTarget, int xCoordinate, int yCoordinate) { assertEquals(timestamp, event.getTimestamp()); assertTrue(eventType.isInstance(event.getType())); assertEquals(eventTarget, event.getTarget()); if (event.getType() instanceof MouseButtonInteraction) { assertEquals(xCoordinate, ((MouseButtonInteraction) event.getType()).getX()); assertEquals(yCoordinate, ((MouseButtonInteraction) event.getType()).getY()); } } /** * */ private void deleteFiles(File file) { if (file.exists()) { if (file.isDirectory()) { for (File child : file.listFiles()) { deleteFiles(child); } } try { file.delete(); } catch (Exception e) { // ignore and delete as much as possible } } } }