content: /*
   * Copyright (c) 2013 3 Round Stones Inc., Some Rights Reserved
   *
   * 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 org.callimachusproject.webdriver.helpers;
  
  import static java.net.URLDecoder.decode;
  
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.lang.management.ManagementFactory;
  import java.lang.management.RuntimeMXBean;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.net.MalformedURLException;
  import java.net.URI;
  import java.net.URL;
  import java.net.URLEncoder;
  import java.util.LinkedHashMap;
  import java.util.Map;
  import java.util.concurrent.TimeUnit;
  
  import junit.framework.TestCase;
  import junit.framework.TestSuite;
  
  import org.apache.http.HttpResponse;
  import org.apache.http.auth.AuthScope;
  import org.apache.http.auth.UsernamePasswordCredentials;
  import org.apache.http.client.ClientProtocolException;
  import org.apache.http.client.CredentialsProvider;
  import org.apache.http.client.ResponseHandler;
  import org.apache.http.client.methods.HttpPut;
  import org.apache.http.client.protocol.HttpClientContext;
  import org.apache.http.config.SocketConfig;
  import org.apache.http.entity.ContentType;
  import org.apache.http.entity.StringEntity;
  import org.apache.http.impl.client.BasicCredentialsProvider;
  import org.apache.http.impl.client.CloseableHttpClient;
  import org.apache.http.impl.client.HttpClientBuilder;
  import org.apache.http.util.EntityUtils;
  import org.callimachusproject.Version;
  import org.callimachusproject.test.TemporaryServer;
  import org.callimachusproject.test.TemporaryServerFactory;
  import org.callimachusproject.test.WebResource;
  import org.callimachusproject.util.DomainNameSystemResolver;
  import org.callimachusproject.webdriver.pages.CalliPage;
  import org.callimachusproject.webdriver.pages.FolderView;
  import org.openqa.selenium.WebDriverException;
  import org.openqa.selenium.chrome.ChromeDriver;
  import org.openqa.selenium.firefox.FirefoxDriver;
  import org.openqa.selenium.ie.InternetExplorerDriver;
  import org.openqa.selenium.remote.DesiredCapabilities;
  import org.openqa.selenium.remote.LocalFileDetector;
  import org.openqa.selenium.remote.RemoteWebDriver;
  import org.openrdf.OpenRDFException;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  public abstract class BrowserFunctionalTestCase extends TestCase {
  	private static final char DELIM = ' ';
  	private static final String PASSWORD = "testPassword1";
  	private static final String EMAIL = "test@example.com";
  	private static final int PORT = 8088;
  	protected static final Logger logger = LoggerFactory.getLogger(BrowserFunctionalTestCase.class);
  	private static final String HOSTNAME = DomainNameSystemResolver
  			.getInstance().getLocalHostName();
  	private static final String ORIGIN = "http://" + HOSTNAME + ":" + PORT;
  	private static final TemporaryServer server;
  	private static final Map<String, RemoteWebDriverFactory> factories = new LinkedHashMap<String, RemoteWebDriverFactory>();
  	static {
  		String service = System
  				.getProperty("org.callimachusproject.test.service");
  		if (service == null || service.length() == 0) {
  			String email = System.getProperty("org.callimachusproject.test.email");
  			if (email == null || email.length() == 0) {
  				email = EMAIL;
  			}
  			String password = System.getProperty("org.callimachusproject.test.password");
  			if (password == null || password.length() == 0) {
  				password = PASSWORD;
  			}
  			server = new TemporaryServerFactory(ORIGIN, PORT, email, password.toCharArray())
  					.createServer();
  		} else {
  			server = null;
  		}
  		String remotewebdriver = System
  				.getProperty("org.callimachusproject.test.remotewebdriver");
  		try {
  			final URL url = remotewebdriver == null
  					|| remotewebdriver.length() == 0 ? null : new URL(
  					remotewebdriver);
  			if (url == null) {
  				checkAndStore("chrome", new RemoteWebDriverFactory() {
  					public RemoteWebDriver create(String name) {
  						return new ChromeDriver();
  					}
  				});
  				checkAndStore("firefox", new RemoteWebDriverFactory() {
  					public RemoteWebDriver create(String name) {
  						return new FirefoxDriver();
  					}
  				});
  				checkAndStore("ie", new RemoteWebDriverFactory() {
  					public RemoteWebDriver create(String name) {
  						return new InternetExplorerDriver();
  					}
  				});
  			} else {
  				factories.put("chrome", new RemoteWebDriverFactory() {
  					public RemoteWebDriver create(String name) {
  						DesiredCapabilities caps = DesiredCapabilities.chrome();
  						caps.setVersion("35");
  						caps.setCapability("platform", "Windows 7");
  						caps.setCapability("name", name);
  						caps.setCapability("build", getBuild());
  						caps.setCapability("tags", getHostTag());
  						return new RemoteWebDriver(url, caps);
  					}
  				});
  				factories.put("firefox", new RemoteWebDriverFactory() {
  					public RemoteWebDriver create(String name) {
  						DesiredCapabilities caps = DesiredCapabilities
  								.firefox();
  						caps.setCapability("platform", "Windows 7");
  						caps.setCapability("name", name);
  						caps.setCapability("build", getBuild());
  						caps.setCapability("tags", getHostTag());
  						return new RemoteWebDriver(url, caps);
  					}
  				});
  				factories.put("ie11", new RemoteWebDriverFactory() {
  					public RemoteWebDriver create(String name) {
  						DesiredCapabilities caps = DesiredCapabilities
  								.internetExplorer();
  						caps.setVersion("11");
  						caps.setCapability("platform", "Windows 7");
  						caps.setCapability("name", name);
  						caps.setCapability("build", getBuild());
  						caps.setCapability("tags", getHostTag());
  						caps.setCapability("requireWindowFocus", true);
  						return new RemoteWebDriver(url, caps);
  					}
  				});
  			}
  		} catch (MalformedURLException e) {
  			throw new AssertionError(e);
  		}
  	}
  
  	public static TestSuite suite() throws Exception {
  		return new TestSuite();
  	}
  
  	private static void checkAndStore(String browser,
  			RemoteWebDriverFactory supplier) {
  		try {
  			supplier.create("availability").quit();
  			factories.put(browser, supplier);
  		} catch (IllegalStateException e) {
  			logger.warn("Local {} web driver not available", browser);
  		} catch (WebDriverException e) {
  			logger.warn("Remote {} web driver not available", browser);
  		}
  	}
  
  	public static TestSuite suite(
  			Class<? extends BrowserFunctionalTestCase> testcase)
  			throws Exception {
  		TestSuite suite = new TestSuite(testcase.getName());
  		for (Method method : testcase.getMethods()) {
  			if (method.getName().startsWith("test")
  					&& method.getParameterTypes().length == 0
  					&& method.getReturnType().equals(Void.TYPE)) {
  				addTests(testcase, suite, method);
  			}
  		}
  		if (suite.countTestCases() == 0) {
  			suite.addTest(TestSuite.warning(testcase.getName()
  					+ " has no public test methods"));
  		}
  		return suite;
  	}
  
  	private static void addTests(
  			Class<? extends BrowserFunctionalTestCase> testcase,
  			TestSuite suite, Method method) throws InstantiationException,
  			IllegalAccessException {
  		for (String browser : getInstalledWebDrivers().keySet()) {
  			BrowserFunctionalTestCase test = testcase.newInstance();
  			test.setName(method.getName() + DELIM + browser);
  			suite.addTest(test);
  		}
  	}
  
  	private static Map<String, RemoteWebDriverFactory> getInstalledWebDrivers() {
  		return factories;
  	}
  
  	private static String getStartUrl() {
  		if (server == null) {
  			return System.getProperty("org.callimachusproject.test.service");
  		} else {
  			try {
  				return server.getRepository().getCallimachusUrl(server.getOrigin(), "/");
  			} catch (OpenRDFException e) {
  				logger.error(e.toString(), e);
  				return ORIGIN + "/";
  			}
  		}
  	}
  
  	private static String getHostTag() {
  		String authority = HOSTNAME;
  		if (authority.contains(".")) {
  			return authority.substring(0, authority.indexOf('.'));
  		} else if (authority.contains(":")) {
  			return authority.substring(0, authority.indexOf(':'));
  		} else {
  			return authority;
  		}
  	}
  
  	private static int getPort() {
  		return URI.create(getStartUrl()).getPort();
  	}
  
  	private static String getBuild() {
  		String build = System.getProperty("org.callimachusproject.test.build");
  		String implementationVersion = Version.class.getPackage()
  				.getImplementationVersion();
  		if (build != null) {
  			return build;
  		} else if (implementationVersion != null) {
  			return implementationVersion;
  		} else {
  			RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
  			long startTime = bean.getStartTime();
  			long code = startTime % (1000 * 60 * 60 * 24 * 30) / 1000 / 60;
  			String version = Version.getInstance().getVersionCode();
  			return version + "+" + Long.toHexString(code);
  		}
  	}
  
  	public RemoteWebDriver driver;
  	public WebBrowserDriver browser;
  	public CalliPage page;
  	private String folderUrl;
  	public BrowserFunctionalTestCase() {
  		super();
  	}
  
  	public BrowserFunctionalTestCase(BrowserFunctionalTestCase parent) {
  		super();
  		if (parent != null) {
  			this.driver = parent.driver;
  			this.browser = parent.browser;
  			this.page = parent.page;
  		}
  	}
  
  	public String getUsername() {
  		if (server == null) {
  			return System.getProperty("org.callimachusproject.test.username");
  		} else {
  			return server.getUsername();
  		}
  	}
  
  	public char[] getPassword() {
  		if (server == null) {
  			String password = System.getProperty("org.callimachusproject.test.password");
  			if (password == null) {
  				return null;
  			} else {
  				return password.toCharArray();
  			}
  		} else {
  			return server.getPassword();
  		}
  	}
  
  	public String getUniqueToken() {
  		return getBrowserName();
  	}
  
  	@Override
  	public void runBare() throws Throwable {
  		init();
  		Throwable exception = null;
  		try {
  			try {
  				setUp();
  				runTest();
  			} catch (Throwable running) {
  				exception = running;
  			} finally {
  				try {
  					tearDown();
  				} catch (Throwable tearingDown) {
  					if (exception == null) {
  						exception = tearingDown;
  					}
  				}
  			}
  			String jobId = driver.getSessionId().toString();
  			if (exception == null) {
  				recordPass(jobId);
  			} else {
  				recordFailure(jobId, exception);
  				throw exception;
  			}
  		} finally {
  			try {
  				destroy();
  			} catch (Throwable ex) {
  				if (exception == null)
  					throw ex;
  			}
  		}
  	}
  
  	public void init() throws Exception, IOException {
  		if (server != null) {
  			server.resume();
  			String url = getStartUrl();
  			WebResource home = new WebResource(url);
  			home.get("text/html");
  			home.ref("/callimachus/scripts.js").get("text/javascript");
  			home.ref("/callimachus/1.5/styles/callimachus.less?less").get(
  					"text/css");
  		}
  		driver = createWebDriver();
  		browser = new WebBrowserDriver(driver);
  		page = new CalliPage(browser);
  	}
  
  	@Override
  	public void setUp() throws Exception {
  		folderUrl = null;
  		String username = getUsername();
  		if (username != null) {
  			logger.info("Login {}", username);
  			page.openLogin().with(username, getPassword()).login();
  		}
  		String folderName = getFolderName();
  		logger.info("Create folder {}", folderName);
  		page.openCurrentFolder().openFolderCreate().with(folderName).create()
  				.waitUntilFolderOpen(folderName);
  		folderUrl = driver.getCurrentUrl();
  	}
  
  	@Override
  	public void runTest() throws Throwable {
  		Method runMethod = null;
  		try {
  			runMethod = this.getClass().getMethod(getMethodName(),
  					(Class[]) null);
  		} catch (NoSuchMethodException e) {
  			fail("Method \"" + getMethodName() + "\" not found");
  		}
  		if (!Modifier.isPublic(runMethod.getModifiers())) {
  			fail("Method \"" + getMethodName() + "\" should be public");
  		}
  	
  		try {
  			runMethod.invoke(this, (Object[]) new Class[0]);
  		} catch (InvocationTargetException e) {
  			e.fillInStackTrace();
  			throw e.getTargetException();
  		} catch (IllegalAccessException e) {
  			e.fillInStackTrace();
  			throw e;
  		}
  	}
  
  	@Override
  	public void tearDown() throws Exception {
  		if (folderUrl != null) {
  			String folderName = getFolderName();
  			logger.info("Delete folder {}", folderName);
  			page.open(folderUrl, FolderView.class)
  					.waitUntilFolderOpen(folderName).openEdit()
  					.waitUntilTitle(folderName).delete();
  		}
  		logger.info("Logout");
  		page.logout();
  		super.tearDown();
  	}
  
  	public void destroy() throws Throwable, Exception {
  		try {
  			if (driver != null) {
  				driver.quit();
  			}
  		} finally {
  			driver = null;
  			if (server != null) {
  				server.pause();
  			}
  		}
  	}
  
  	private RemoteWebDriver createWebDriver() {
  		RemoteWebDriverFactory driverFactory = getInstalledWebDrivers().get(
  				getBrowserName());
  		if (driverFactory == null)
  			throw new WebDriverException("No WebDrivers available for "
  					+ getBrowserName() + ", try one of "
  					+ getInstalledWebDrivers().keySet());
  		String testname = getMethodName();
  		RemoteWebDriver driver = driverFactory.create(testname);
  		init(driver);
  		return driver;
  	}
  
  	private void init(RemoteWebDriver driver) {
  		try {
  			driver.setFileDetector(new LocalFileDetector());
  		} catch (WebDriverException e) {
  			// ignore
  		}
  		int wait = WebBrowserDriver.IMPLICITLY_WAIT;
  		driver.manage().timeouts().implicitlyWait(wait, TimeUnit.SECONDS);
  		driver.manage().timeouts().setScriptTimeout(wait, TimeUnit.SECONDS);
  		driver.navigate().to(getStartUrl());
  	}
  
  	private void recordPass(String jobId) throws IOException {
  		int p = getPort();
  		String port = "";
  		if (p > 0) {
  			port = ", \"" + p + "\"";
  		}
  		recordTest(jobId, "{\"name\": \"" + getMethodName()
  				+ "\", \"build\": \"" + getBuild() + "\", \"tags\": [\""
  				+ getHostTag() + "\"" + port
  				+ "], \"video-upload-on-pass\": false, \"passed\": true}", 3);
  	}
  
  	private void recordFailure(String jobId, Throwable e) throws IOException {
  		int p = getPort();
  		String port = "";
  		if (p > 0) {
  			port = ", \"" + p + "\"";
  		}
  		recordTest(jobId, "{\"name\": \"" + getMethodName()
  				+ "\", \"build\": \"" + getBuild() + "\", \"tags\": [\""
  				+ getHostTag() + "\"" + port + ", \""
  				+ e.getClass().getSimpleName() + "\"], \"passed\": false}", 3);
  	}
  
  	private void recordTest(String jobId, String data, int retry) {
  		String remotewebdriver = System
  				.getProperty("org.callimachusproject.test.remotewebdriver");
  		if (remotewebdriver != null
  				&& remotewebdriver.contains("saucelabs.com")) {
  			URI uri = URI.create(remotewebdriver);
  			CloseableHttpClient client = HttpClientBuilder
  					.create()
  					.useSystemProperties()
  					.setDefaultSocketConfig(
  							SocketConfig.custom().setSoTimeout(5000).build())
  					.build();
  			try {
  				try {
  					String info = uri.getUserInfo();
  					putTestData(info, jobId, data, client);
  				} finally {
  					client.close();
  				}
  			} catch (IOException ex) {
  				if (retry > 0) {
  					logger.warn(ex.toString(), ex);
  					recordTest(jobId, data, retry - 1);
  				} else {
  					logger.error(ex.toString(), ex);
  				}
  			} catch (RuntimeException ex) {
  				logger.error(ex.toString(), ex);
  			}
  		}
  	}
  
  	private void putTestData(String info, String jobId, String data,
  			CloseableHttpClient client) throws IOException,
  			ClientProtocolException {
  		String username = decode(info.substring(0, info.indexOf(':')), "UTF-8");
  		String password = decode(info.substring(info.indexOf(':') + 1), "UTF-8");
  		UsernamePasswordCredentials cred = new UsernamePasswordCredentials(
  				username, password);
  		AuthScope scope = new AuthScope("saucelabs.com", 80);
  		CredentialsProvider credsProvider = new BasicCredentialsProvider();
  		credsProvider.setCredentials(scope, cred);
  		HttpClientContext ctx = HttpClientContext.create();
  		ctx.setCredentialsProvider(credsProvider);
  		HttpPut put = new HttpPut("http://saucelabs.com/rest/v1/" + username
  				+ "/jobs/" + jobId);
  		put.setEntity(new StringEntity(data, ContentType.APPLICATION_JSON));
  		client.execute(put, new ResponseHandler<Void>() {
  			public Void handleResponse(HttpResponse response)
  					throws ClientProtocolException, IOException {
  				if (300 <= response.getStatusLine().getStatusCode()) {
  					System.err.println(EntityUtils.toString(response.getEntity()));
  				}
  				assertTrue(response.getStatusLine().toString(), 300 > response.getStatusLine().getStatusCode());
  				return null;
  			}
  		}, ctx);
  	}
  
  	private String getMethodName() {
  		String name = getName();
  		if (name == null)
  			return "test";
  		return name.substring(0, name.indexOf(DELIM));
  	}
  
  	private String getBrowserName() {
  		String name = getName();
  		if (name == null) {
  			Map<String, RemoteWebDriverFactory> map = getInstalledWebDrivers();
  			if (map.isEmpty())
  				throw new WebDriverException("No WebDrivers installed");
  			return map.keySet().iterator().next();
  		}
  		return name.substring(name.lastIndexOf(DELIM) + 1);
  	}
  
  	private String getFolderName() {
  		String name = getName();
  		if (name == null) {
  			name = "test";
  		}
  		try {
  			return URLEncoder.encode(name, "UTF-8").replace("+", "&") + "'s%20Folder";
  		} catch (UnsupportedEncodingException e) {
  			throw new AssertionError(e);
  		}
  	}
  }
  