/******************************************************************************* * Copyright (c) 2010-2015 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.gerrit.ssh; import static org.junit.Assert.assertEquals; import java.io.File; import java.lang.reflect.Method; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.commons.io.FileUtils; import org.eclipse.skalli.core.gerrit.ssh.GerritClientSSH.ResultFormat; import org.eclipse.skalli.core.gerrit.ssh.GerritClientSSH.Tables; import org.eclipse.skalli.services.gerrit.CommandException; import org.eclipse.skalli.services.gerrit.ConnectionException; import org.eclipse.skalli.services.gerrit.GerritClient; import org.eclipse.skalli.services.gerrit.GerritClientException; import org.eclipse.skalli.services.gerrit.GerritFeature; import org.eclipse.skalli.services.gerrit.GerritPlugin; import org.eclipse.skalli.services.gerrit.GerritServerConfig; import org.eclipse.skalli.services.gerrit.GerritVersion; import org.eclipse.skalli.services.gerrit.InheritableBoolean; import org.eclipse.skalli.services.gerrit.ProjectOptions; import org.eclipse.skalli.services.gerrit.SubmitType; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("nls") public class GerritClientSSHTest { private static final Logger LOG = LoggerFactory.getLogger(GerritClientSSHTest.class); private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.ENGLISH); // DO NOT CONFIGURE A PRODUCTIVE GERRIT - the test case created a huge amount of projects and // groups that cannot be removed easily. private static String TEST_HOST = null; private static String TEST_ACCOUNT = null; private static String TEST_PORT = "8080"; private static String TEST_PRIVATEKEY = null; private static String TEST_PASSPHRASE = null; private static String TEST_VERSION = null; private static final String TEST_ONBEHALFOF = "userId"; // DO NOT MODIFY THIS DESCRIPTION - it is used in the clean up step to figure out which projects // and groups have been created. private static final String DESCRIPTION = "Generated by jUnit"; private GerritClientSSH client; @BeforeClass public static void setUpOnce() throws Exception { TEST_HOST = System.getProperty("testHost"); String port = System.getProperty("testPort"); TEST_PORT = port != null ? port : TEST_PORT; TEST_VERSION = System.getProperty("testVersion"); TEST_ACCOUNT = System.getProperty("testAccount"); TEST_PRIVATEKEY = System.getProperty("testPrivateKey"); String privateKeyFile = System.getProperty("testPrivateKeyFile"); TEST_PRIVATEKEY = FileUtils.readFileToString(new File(privateKeyFile)); TEST_PASSPHRASE = System.getProperty("testPassphrase"); } @AfterClass public static void tearDownOnce() throws Exception { // clean up and delete all groups + projects that still exist EvilGerritClient evilClient = new EvilGerritClient(getGerritConfig(), TEST_ONBEHALFOF); evilClient.connect(); /* * Deleting groups and clean up. Added WHERE clause that should avoid deleting system groups accidentally */ final StringBuffer deleteGroupsQueries = new StringBuffer(); deleteGroupsQueries.append("DELETE FROM ").append(Tables.ACCOUNT_GROUPS); deleteGroupsQueries.append(" WHERE description = '").append(DESCRIPTION).append("'"); deleteGroupsQueries.append(" AND group_id NOT IN (SELECT admin_group_id FROM ") .append(Tables.SYSTEM_CONFIG).append(")"); deleteGroupsQueries.append(" AND group_id NOT IN (SELECT anonymous_group_id FROM ") .append(Tables.SYSTEM_CONFIG).append(")"); deleteGroupsQueries.append(" AND group_id NOT IN (SELECT registered_group_id FROM ") .append(Tables.SYSTEM_CONFIG).append(")"); deleteGroupsQueries.append(" AND group_id NOT IN (SELECT batch_users_group_id FROM ") .append(Tables.SYSTEM_CONFIG).append(");"); // clean up deleteGroupsQueries.append("DELETE FROM ").append(Tables.ACCOUNT_GROUP_NAMES) .append(" WHERE group_id NOT IN (SELECT group_id FROM ").append(Tables.ACCOUNT_GROUPS) .append(");"); deleteGroupsQueries.append("DELETE FROM ").append(Tables.ACCOUNT_GROUP_MEMBERS) .append(" WHERE group_id NOT IN (SELECT group_id FROM ").append(Tables.ACCOUNT_GROUPS) .append(");"); deleteGroupsQueries.append("DELETE FROM ").append(Tables.ACCOUNT_GROUP_MEMBERS_AUDIT) .append(" WHERE group_id NOT IN (SELECT group_id FROM ").append(Tables.ACCOUNT_GROUPS) .append(");"); evilClient.gsql(deleteGroupsQueries.toString(), ResultFormat.PRETTY); /* * Deleting projects and clean up according to: * http://groups.google.com/group/repo-discuss/browse_thread/thread/9fa2f8978d422709?fwc=1 * * Note: This does not clean up on file system. * So creating a project w/ the same name again won't work. * Hence the test cases always use project names with a timestamp. */ final StringBuffer deleteProjectsQueries = new StringBuffer(); deleteProjectsQueries.append("DELETE FROM ") .append(Tables.PROJECTS).append(" WHERE description = '") .append(DESCRIPTION).append("';"); // clean up deleteProjectsQueries.append("DELETE FROM ").append(Tables.REF_RIGHTS) .append(" WHERE project_name NOT IN (SELECT name FROM ").append(Tables.PROJECTS).append(");"); evilClient.gsql(deleteProjectsQueries.toString(), ResultFormat.PRETTY); evilClient.disconnect(); } private static class TestGerritClient extends GerritClientSSH implements GerritClient { TestGerritClient(GerritServerConfig gerritConfig, String onBehalfOf) { super(gerritConfig, onBehalfOf); } @Override public Map<String, GerritPlugin> getPlugins() throws ConnectionException, CommandException { // simulate the availability of certain plugins HashMap<String,GerritPlugin> plugins = new HashMap<String,GerritPlugin>(); plugins.put("x", new GerritPlugin("x", "1.0", true)); plugins.put("y", new GerritPlugin("y", "1.0", true)); plugins.put("z", new GerritPlugin("z", "1.0", false)); return plugins; } } @Before public void setup() throws Exception { client = new TestGerritClient(getGerritConfig(), TEST_ONBEHALFOF); } @After public void tearDown() throws Exception { if (client != null) { client.disconnect(); } } private static GerritServerConfig getGerritConfig() { GerritServerConfig gerritConfig = new GerritServerConfig(); gerritConfig.setHost(TEST_HOST); gerritConfig.setPort(TEST_PORT); gerritConfig.setUser(TEST_ACCOUNT); gerritConfig.setPrivateKey(TEST_PRIVATEKEY); gerritConfig.setPassphrase(TEST_PASSPHRASE); gerritConfig.addEnabledPlugin("x"); gerritConfig.addEnabledPlugin("y"); gerritConfig.addEnabledPlugin("z"); gerritConfig.addDisabledPlugin("z"); return gerritConfig; } @Test public void testGetVersion() throws Exception { Assert.assertEquals(GerritVersion.asGerritVersion(TEST_VERSION), client.getVersion()); } @Test public void testGroupExists() throws Exception { final String name = generateName("g"); Assert.assertFalse(client.groupExists(name)); client.createGroup(name, null, DESCRIPTION, null); Assert.assertTrue(client.groupExists(name)); } @Test public void testProjectExists() throws Exception { final String name = generateName("p"); Assert.assertFalse(client.projectExists(name)); client.createProject(name, null, null, null, false, DESCRIPTION, null, false, false, true); Assert.assertTrue(client.projectExists(name)); } @Test public void testCreateGroup() throws Exception { final String name = generateName("g"); Assert.assertFalse(client.groupExists(name)); client.createGroup(name, null, DESCRIPTION, null); Assert.assertTrue(client.groupExists(name)); } @Test(expected = CommandException.class) public void testCreateGroupTwice() throws Exception { final String name = generateName("g"); client.createGroup(name, null, DESCRIPTION, null); client.createGroup(name, null, DESCRIPTION, null); } @Test public void testCreateProject() throws Exception { final String name = generateName("p"); Assert.assertFalse(client.projectExists(name)); client.createProject(name, null, null, null, false, DESCRIPTION, null, false, false, true); Assert.assertTrue(client.projectExists(name)); } @Test public void testSshCreateProject() throws Exception { final String name = generateName("p"); ProjectOptions options = new ProjectOptions(); options.setName(name); options.setBranches(Arrays.asList("foo", "bar")); options.setOwners(Arrays.asList("c", "a", "b")); options.setParent("foo bar"); options.setPermissionsOnly(true); options.setDescription("test project"); options.setSubmitType(SubmitType.REBASE_IF_NECESSARY); options.setUseContributorAgreements(InheritableBoolean.TRUE); options.setUseSignedOffBy(InheritableBoolean.TRUE); options.setRequiredChangeId(InheritableBoolean.TRUE); options.setUseContentMerge(InheritableBoolean.TRUE); options.setCreateEmptyCommit(true); options.setMaxObjectSizeLimit("1m"); options.putPluginConfig("x", "c", "d"); options.putPluginConfig("y", "a", "d"); options.putPluginConfig("x", "a", "b"); options.putPluginConfig("z", "a", "b"); // z is disabled! options.putPluginConfig("foo", "a", "b"); // foo is not enabled! assertEquals(getExpectedSshCreateProject(name, GerritVersion.GERRIT_2_7_X), client.sshCreateProject(options, GerritVersion.GERRIT_2_7_X)); assertEquals(getExpectedSshCreateProject(name, GerritVersion.GERRIT_2_8_X), client.sshCreateProject(options, GerritVersion.GERRIT_2_8_X)); assertEquals(getExpectedSshCreateProject(name, GerritVersion.GERRIT_2_9_X), client.sshCreateProject(options, GerritVersion.GERRIT_2_9_X)); } private String getExpectedSshCreateProject(String name, GerritVersion version) { StringBuilder sb = new StringBuilder( "gerrit create-project" + " --name \"" + name + "\"" + " --branch \"bar\" --branch \"foo\"" //sorted! + " --owner \"a\" --owner \"b\" --owner \"c\"" //sorted! + " --parent \"foo bar\"" + " --permissions-only" + " --description \"test project\"" + " --submit-type \"REBASE_IF_NECESSARY\"" + " --use-contributor-agreements" + " --use-signed-off-by" + " --require-change-id" + " --use-content-merge" + " --empty-commit"); if (version.compareTo(GerritVersion.GERRIT_2_8_X) >=0 ) { sb.append(" --max-object-size-limit \"1m\""); } if (version.compareTo(GerritVersion.GERRIT_2_9_X) >= 0) { sb.append( " --plugin-config \"x.a=b\"" //sorted by plugin name and property names + " --plugin-config \"x.c=d\"" + " --plugin-config \"y.a=d\""); } return sb.toString(); } @Test(expected = CommandException.class) public void testCreateProjectTwice() throws Exception { final String name = generateName("p"); client.createProject(name, null, null, null, false, DESCRIPTION, null, false, false, true); client.createProject(name, null, null, null, false, DESCRIPTION, null, false, false, true); } @Test public void testGetProjects() throws Exception { Assert.assertNotNull(client.getProjects()); } @Test public void testGetGroups() throws Exception { Assert.assertNotNull(client.getGroups()); } @Test public void testGetGroupsForProject() throws Exception { final String g1 = generateName("g1"); final String g2 = generateName("g2"); final String g3 = generateName("g3"); final String p1 = generateName("p1"); final String p2 = generateName("p2"); client.createGroup(g1, null, DESCRIPTION, null); client.createGroup(g2, null, DESCRIPTION, null); client.createGroup(g3, null, DESCRIPTION, null); client.createProject(p1, null, new HashSet<String>(Arrays.asList(g1, g2)), null, false, DESCRIPTION, null, false, false, true); client.createProject(p2, null, new HashSet<String>(Arrays.asList(g2, g3)), null, false, DESCRIPTION, null, false, false, true); List<String> groupsForP1 = client.getGroups(p1); Assert.assertTrue(groupsForP1.contains(g1)); Assert.assertTrue(groupsForP1.contains(g2)); Assert.assertFalse(groupsForP1.contains(g3)); List<String> groupsForP2 = client.getGroups(p2); Assert.assertFalse(groupsForP2.contains(g1)); Assert.assertTrue(groupsForP2.contains(g2)); Assert.assertTrue(groupsForP2.contains(g3)); } @Test public void testGetGroupsForProjects() throws Exception { final String g1 = generateName("g1"); final String g2 = generateName("g2"); final String g3 = generateName("g3"); final String p1 = generateName("p1"); final String p2 = generateName("p2"); client.createGroup(g1, null, DESCRIPTION, null); client.createGroup(g2, null, DESCRIPTION, null); client.createGroup(g3, null, DESCRIPTION, null); client.createProject(p1, null, new HashSet<String>(Arrays.asList(g1)), null, false, DESCRIPTION, null, false, false, true); client.createProject(p2, null, new HashSet<String>(Arrays.asList(g2)), null, false, DESCRIPTION, null, false, false, true); List<String> groupsForP1 = client.getGroups(p1, p2); Assert.assertTrue(groupsForP1.contains(g1)); Assert.assertTrue(groupsForP1.contains(g2)); Assert.assertFalse(groupsForP1.contains(g3)); } @Test public void testInvalidProjectNames() throws Exception { String[] invalidProjectNames = new String[] { null, "", " ", "/", "/project", "project/", "my project", " project", "project ", "pro\0ject", "my\tproject", "../project", "<project>", "my/../project", "my/./project", "pro\\ject", "pro:ject", "pro~ject", "pro?ject", "pro*ject", "pro<ject", "pro>ject", "pro|ject", "pro%ject", "pro\"ject" }; for (String invalidProjectName : invalidProjectNames) { Assert.assertNotNull(client.checkProjectName(invalidProjectName)); try { client.createProject(invalidProjectName, null, null, null, false, DESCRIPTION, null, false, false, true); Assert.fail(String.format("No error for project name '%s'.", invalidProjectName)); } catch (GerritClientException e) { LOG.debug(MessageFormat.format("'{0}' is identified as an invalid project name.", invalidProjectName)); } catch (IllegalArgumentException e) { LOG.debug(MessageFormat.format("'{0}' is identified as an invalid project name.", invalidProjectName)); } } } @Test public void testInvalidGroupNames() throws Exception { String[] invalidGroupNames = new String[] { null, "", " ", " a", "b ", "\ta", "b\t", "a\tb", "<b>html</b>" }; for (String invalidGroupName : invalidGroupNames) { Assert.assertNotNull(client.checkGroupName(invalidGroupName)); try { client.createGroup(invalidGroupName, null, DESCRIPTION, null); Assert.fail(String.format("No error for group name '%s'.", invalidGroupName)); } catch (GerritClientException e) { LOG.debug(MessageFormat.format("'{0}' is identified as an invalid group name.", invalidGroupName)); } catch (IllegalArgumentException e) { LOG.debug(MessageFormat.format("'{0}' is identified as an invalid group name.", invalidGroupName)); } } } @Test public void testGetKnownAccounts() throws Exception { String unknownAccount = System.currentTimeMillis() + ""; Set<String> variousAccounts = new HashSet<String>(Arrays.asList(TEST_ACCOUNT, "", null, unknownAccount)); Set<String> knownAccounts = client.getKnownAccounts(variousAccounts); GerritVersion version = GerritVersion.asGerritVersion(TEST_VERSION); if (version.supports(GerritFeature.ACCOUNT_CHECK_OBSOLETE)) { Assert.assertEquals(2, knownAccounts.size()); Assert.assertTrue(knownAccounts.contains(TEST_ACCOUNT)); Assert.assertTrue(knownAccounts.contains(unknownAccount)); } else { Assert.assertEquals(1, knownAccounts.size()); Assert.assertTrue(knownAccounts.contains(TEST_ACCOUNT)); Assert.assertFalse(knownAccounts.contains(unknownAccount)); } } @Test public void testGetKnownAccountsEmpty() throws Exception { Set<String> knownAccounts = client.getKnownAccounts(Collections.<String> emptySet()); Assert.assertEquals(0, knownAccounts.size()); } @Test public void testALotOfUnknownAccounts() throws Exception { Set<String> variousAccounts = new HashSet<String>(); // don't use an even number. challenge the system :) for (int i = 0; i < 123; i++) { String unknownAccount = System.currentTimeMillis() + "_" + i; variousAccounts.add(unknownAccount); } variousAccounts.add(TEST_ACCOUNT); Set<String> knownAccounts = client.getKnownAccounts(variousAccounts); GerritVersion version = GerritVersion.asGerritVersion(TEST_VERSION); if (version.supports(GerritFeature.ACCOUNT_CHECK_OBSOLETE)) { Assert.assertEquals(variousAccounts.size(), knownAccounts.size()); } else { Assert.assertEquals(1, knownAccounts.size()); } } @Test(expected = ConnectionException.class) public void testInvalidSettingsHost() throws Exception { GerritServerConfig gerritConfig = getGerritConfig(); gerritConfig.setHost("some.host.corp"); GerritClient invalidClient = new GerritClientSSH(gerritConfig, TEST_ONBEHALFOF); invalidClient.connect(); } @Test(expected = ConnectionException.class) public void testInvalidSettingsPort() throws Exception { GerritServerConfig gerritConfig = getGerritConfig(); gerritConfig.setHost("50000"); GerritClient invalidClient = new GerritClientSSH(gerritConfig, TEST_ONBEHALFOF); invalidClient.connect(); } @Test(expected = ConnectionException.class) public void testInvalidSettingsUser() throws Exception { GerritServerConfig gerritConfig = getGerritConfig(); gerritConfig.setUser("LoremIpsum"); GerritClient invalidClient = new GerritClientSSH(gerritConfig, TEST_ONBEHALFOF); invalidClient.connect(); } @Test(expected = ConnectionException.class) public void testInvalidSettingsPrivateKey() throws Exception { GerritServerConfig gerritConfig = getGerritConfig(); gerritConfig.setPrivateKey("foobar"); GerritClient invalidClient = new GerritClientSSH(gerritConfig, TEST_ONBEHALFOF); invalidClient.connect(); } @Test(expected = ConnectionException.class) public void testInvalidSettingsPassphrase() throws Exception { GerritServerConfig gerritConfig = getGerritConfig(); gerritConfig.setPassphrase("foobar"); GerritClient invalidClient = new GerritClientSSH(gerritConfig, TEST_ONBEHALFOF); invalidClient.connect(); } @Test public void testUnsupportedGSQLCommands() throws Exception { String[] invalidCommands = new String[] { "show", "insert", "update", "delete", "merge", "create", "alter", "rename", "truncate", "drop" }; for (String invalidCommand : invalidCommands) { try { client.gsql("SELECT * FROM unknown_table_xyz;" + invalidCommand + " bla bla", ResultFormat.JSON); } catch (UnsupportedOperationException e) { continue; } Assert.fail("Command '" + invalidCommand + "' should not work on gsql(), but was not detected."); } } /** * Due to the fact that project names can only be used once (won't be deleted from Git) we * generate new names for each run. * * @param prefix a fixed prefix * @return a random name (using the date & time) starting with the fixed prefix */ private static String generateName(String name) { return "junit/" + df.format(new Date()) + "/" + name; } /** * YOU MUST NOT USE THIS ON PRODUCTIVE GERRIT INSTANCES! * (utility to clean up a test Gerrit instance) */ private static class EvilGerritClient extends GerritClientSSH { private EvilGerritClient(GerritServerConfig gerritConfig, String onBehalfOf) { super(gerritConfig, onBehalfOf); } @Override @SuppressWarnings("unchecked") List<String> gsql(String query, ResultFormat format) throws ConnectionException, CommandException { final StringBuffer sb = new StringBuffer("gerrit gsql"); sb.append(" --format ").append(format.name()); sb.append(" -c \"").append(query).append("\""); // Here comes the evil part: Overriding gsql(), allowing modifying commands and using reflection to call a private method... try { Method sshCommand = this.getClass().getSuperclass().getDeclaredMethod("sshCommand", String.class); sshCommand.setAccessible(true); return (List<String>) sshCommand.invoke(this, sb.toString()); } catch (Exception e) { throw new RuntimeException(e); } } } }