diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-06-23 21:31:56 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-06-25 23:42:50 +0200 |
commit | 70b6899988da0d2ba0a39b846e4f1bd3fa27304f (patch) | |
tree | 1ac093a87e0fba6b07c6feb6aceae89bdd9663cf /tests/src/test | |
parent | 5dd574819854e9ce7e2f4e181e78153a7ecbf828 (diff) | |
download | sonarqube-70b6899988da0d2ba0a39b846e4f1bd3fa27304f.tar.gz sonarqube-70b6899988da0d2ba0a39b846e4f1bd3fa27304f.zip |
Move integration tests to directory tests/
Diffstat (limited to 'tests/src/test')
446 files changed, 42830 insertions, 0 deletions
diff --git a/tests/src/test/java/org/sonarqube/pageobjects/BackgroundTaskItem.java b/tests/src/test/java/org/sonarqube/pageobjects/BackgroundTaskItem.java new file mode 100644 index 00000000000..aedd45ed305 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/BackgroundTaskItem.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class BackgroundTaskItem { + + private final SelenideElement elt; + + public BackgroundTaskItem(SelenideElement elt) { + this.elt = elt; + } + + public SelenideElement getComponent() { + return elt.$("td:nth-child(2)"); + } + + public BackgroundTaskItem openActions() { + elt.$(".js-task-action > .dropdown-toggle").click(); + elt.$(".js-task-action > .dropdown-menu").shouldBe(visible); + return this; + } + + public BackgroundTaskItem openScannerContext () { + elt.$(".js-task-show-scanner-context").click(); + $(".js-task-scanner-context").shouldBe(visible); + return this; + } + + public BackgroundTaskItem assertScannerContextContains(String text) { + $(".js-task-scanner-context").should(hasText(text)); + return this; + } + + public BackgroundTaskItem openErrorStacktrace () { + elt.$(".js-task-show-stacktrace").click(); + $(".js-task-stacktrace").shouldBe(visible); + return this; + } + + public BackgroundTaskItem assertErrorStacktraceContains(String text) { + $(".js-task-stacktrace").should(hasText(text)); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/BackgroundTasksPage.java b/tests/src/test/java/org/sonarqube/pageobjects/BackgroundTasksPage.java new file mode 100644 index 00000000000..432e1addd8c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/BackgroundTasksPage.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.ElementsCollection; +import java.util.List; +import java.util.stream.Collectors; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class BackgroundTasksPage { + + public BackgroundTasksPage() { + $(By.cssSelector(".background-tasks")).should(exist); + } + + public ElementsCollection getTasks() { + return $$(".background-tasks > tbody > tr"); + } + + public List<BackgroundTaskItem> getTasksAsItems() { + return getTasks() + .stream() + .map(BackgroundTaskItem::new) + .collect(Collectors.toList()); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/EncryptionPage.java b/tests/src/test/java/org/sonarqube/pageobjects/EncryptionPage.java new file mode 100644 index 00000000000..f0c68849bfe --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/EncryptionPage.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class EncryptionPage extends Navigation { + + public EncryptionPage() { + $("#encryption-page").should(exist); + } + + public SelenideElement generationForm() { + return $("#generate-secret-key-form"); + } + + public SelenideElement newSecretKey() { + return $("#secret-key"); + } + + public String encryptValue(String value) { + $("#encryption-form-value").val(value); + $("#encryption-form").submit(); + return $("#encrypted-value").shouldBe(visible).val(); + } + + public EncryptionPage generateNewKey() { + $("#encryption-new-key-form").submit(); + $("#generate-secret-key-form").shouldBe(visible); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/LoginPage.java b/tests/src/test/java/org/sonarqube/pageobjects/LoginPage.java new file mode 100644 index 00000000000..bb17d0961e3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/LoginPage.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.page; + +public class LoginPage { + + public LoginPage() { + $("#login_form").should(Condition.exist); + } + + public Navigation submitCredentials(String login) { + return submitCredentials(login, login, Navigation.class); + } + + public Navigation submitCredentials(String login, String password) { + return submitCredentials(login, password, Navigation.class); + } + + public Navigation useOAuth2() { + $(".oauth-providers a").click(); + return page(Navigation.class); + } + + public LoginPage submitWrongCredentials(String login, String password) { + $("#login").val(login); + $("#password").val(password); + $(By.name("commit")).click(); + return page(LoginPage.class); + } + + public SelenideElement getErrorMessage() { + return $(".process-spinner-failed"); + } + + private <T> T submitCredentials(String login, String password, Class<T> expectedResultPage) { + $("#login").val(login); + $("#password").val(password); + $(By.name("commit")).click(); + $("#login").should(Condition.disappear); + return page(expectedResultPage); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java b/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java new file mode 100644 index 00000000000..54363129fec --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/Navigation.java @@ -0,0 +1,234 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.SelenideElement; +import com.codeborne.selenide.WebDriverRunner; +import com.sonar.orchestrator.Orchestrator; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.html5.WebStorage; +import org.sonarqube.tests.Tester; +import org.sonarqube.pageobjects.issues.IssuesPage; +import org.sonarqube.pageobjects.licenses.LicensesPage; +import org.sonarqube.pageobjects.organization.MembersPage; +import org.sonarqube.pageobjects.projects.ProjectsPage; +import org.sonarqube.pageobjects.settings.SettingsPage; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.clearBrowserLocalStorage; +import static com.codeborne.selenide.Selenide.page; + +public class Navigation { + + public Navigation() { + $("#content").shouldBe(Condition.exist); + } + + /** + * @deprecated use {@link Tester#openBrowser()} + */ + @Deprecated + public static Navigation create(Orchestrator orchestrator) { + WebDriver driver = SelenideConfig.configure(orchestrator); + driver.manage().deleteAllCookies(); + clearStorage(d -> d.getLocalStorage().clear()); + clearStorage(d -> d.getSessionStorage().clear()); + clearStorage(d -> clearBrowserLocalStorage()); + return Selenide.open("/", Navigation.class); + } + + private static void clearStorage(Consumer<WebStorage> cleaner) { + try { + cleaner.accept((WebStorage) WebDriverRunner.getWebDriver()); + } catch (Exception e) { + // ignore, it may occur when the first test opens browser. No pages are loaded + // and local/session storages are not available yet. + // Example with Chrome: "Failed to read the 'localStorage' property from 'Window': Storage is disabled inside 'data:' URLs." + } + } + + public Navigation openHome() { + return open("/", Navigation.class); + } + + public ProjectsPage openProjects() { + return open("/projects", ProjectsPage.class); + } + + public ProjectsPage openProjects(String organization) { + return open("/organizations/" + organization + "/projects", ProjectsPage.class); + } + + public IssuesPage openIssues() { + return open("/issues", IssuesPage.class); + } + + public IssuesPage openComponentIssues(String component) { + return open("/component_issues?id=" + component, IssuesPage.class); + } + + public ProjectDashboardPage openProjectDashboard(String projectKey) { + // TODO encode projectKey + String url = "/dashboard?id=" + projectKey; + return open(url, ProjectDashboardPage.class); + } + + public ProjectLinksPage openProjectLinks(String projectKey) { + // TODO encode projectKey + String url = "/project/links?id=" + projectKey; + return open(url, ProjectLinksPage.class); + } + + public ProjectQualityGatePage openProjectQualityGate(String projectKey) { + // TODO encode projectKey + String url = "/project/quality_gate?id=" + projectKey; + return open(url, ProjectQualityGatePage.class); + } + + public ProjectKeyPage openProjectKey(String projectKey) { + // TODO encode projectKey + String url = "/project/key?id=" + projectKey; + return open(url, ProjectKeyPage.class); + } + + public ProjectActivityPage openProjectActivity(String projectKey) { + // TODO encode projectKey + String url = "/project/activity?id=" + projectKey; + return open(url, ProjectActivityPage.class); + } + + public MembersPage openOrganizationMembers(String orgKey) { + String url = "/organizations/" + orgKey + "/members"; + return open(url, MembersPage.class); + } + + public BackgroundTasksPage openBackgroundTasksPage() { + return open("/background_tasks", BackgroundTasksPage.class); + } + + public SettingsPage openSettings(@Nullable String projectKey) throws UnsupportedEncodingException { + String url = projectKey != null ? "/project/settings?id=" + URLEncoder.encode(projectKey, "UTF-8") : "/settings"; + return open(url, SettingsPage.class); + } + + public LicensesPage openLicenses() { + return open("/settings/licenses", LicensesPage.class); + } + + public EncryptionPage openEncryption() { + return open("/settings/encryption", EncryptionPage.class); + } + + public ServerIdPage openServerId() { + return open("/settings/server_id", ServerIdPage.class); + } + + public NotificationsPage openNotifications() { + return open("/account/notifications", NotificationsPage.class); + } + + public ProjectPermissionsPage openProjectPermissions(String projectKey) { + String url = "/project_roles?id=" + projectKey; + return open(url, ProjectPermissionsPage.class); + } + + public ProjectsManagementPage openProjectsManagement() { + return open("/projects_admin", ProjectsManagementPage.class); + } + + public LoginPage openLogin() { + return open("/sessions/login", LoginPage.class); + } + + public void open(String relativeUrl) { + Selenide.open(relativeUrl); + } + + public <P> P open(String relativeUrl, Class<P> pageObjectClassClass) { + return Selenide.open(relativeUrl, pageObjectClassClass); + } + + public Navigation shouldBeLoggedIn() { + loggedInDropdown().should(visible); + return this; + } + + public Navigation shouldNotBeLoggedIn() { + logInLink().should(visible); + return this; + } + + public LoginPage logIn() { + logInLink().click(); + return page(LoginPage.class); + } + + public Navigation logOut() { + SelenideElement dropdown = loggedInDropdown(); + // click must be on the <a> but not on the dropdown <li> + // for compatibility with phantomjs + dropdown.find(".dropdown-toggle").click(); + dropdown.find(By.linkText("Log out")).click(); + return this; + } + + public RulesPage clickOnRules() { + $(By.linkText("Rules")).click(); + return page(RulesPage.class); + } + + public SelenideElement clickOnQualityProfiles() { + return $(By.linkText("Quality Profiles")); + } + + public SelenideElement getRightBar() { + return $("#global-navigation .navbar-right"); + } + + public SelenideElement getFooter() { + return $("#footer"); + } + + public SelenideElement getErrorMessage() { + return $("#error"); + } + + private SelenideElement logInLink() { + return $(By.linkText("Log in")); + } + + private SelenideElement loggedInDropdown() { + return $(".js-user-authenticated"); + } + + public Navigation shouldBeRedirectedToLogin() { + $("#login_form").should(visible); + return this; + } + +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/NotificationsPage.java b/tests/src/test/java/org/sonarqube/pageobjects/NotificationsPage.java new file mode 100644 index 00000000000..456f08ca957 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/NotificationsPage.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import static com.codeborne.selenide.Condition.cssClass; +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class NotificationsPage extends Navigation { + + private final String EMAIL = "EmailNotificationChannel"; + + public NotificationsPage() { + $("#account-page").shouldHave(text("Overall notifications")); + } + + public NotificationsPage shouldHaveGlobalNotification(String type) { + return shouldHaveGlobalNotification(type, EMAIL); + } + + public NotificationsPage shouldHaveGlobalNotification(String type, String channel) { + return shouldBeChecked(globalCheckboxSelector(type, channel)); + } + + public NotificationsPage shouldNotHaveGlobalNotification(String type) { + return shouldNotHaveGlobalNotification(type, EMAIL); + } + + public NotificationsPage shouldNotHaveGlobalNotification(String type, String channel) { + return shouldNotBeChecked(globalCheckboxSelector(type, channel)); + } + + public NotificationsPage shouldHaveProjectNotification(String project, String type, String channel) { + return shouldBeChecked(projectCheckboxSelector(project, type, channel)); + } + + public NotificationsPage shouldNotHaveProjectNotification(String project, String type, String channel) { + return shouldNotBeChecked(projectCheckboxSelector(project, type, channel)); + } + + public NotificationsPage addGlobalNotification(String type) { + return addGlobalNotification(type, EMAIL); + } + + public NotificationsPage addGlobalNotification(String type, String channel) { + shouldNotHaveGlobalNotification(type, channel); + toggleCheckbox(globalCheckboxSelector(type, channel)); + shouldHaveGlobalNotification(type, channel); + return this; + } + + public NotificationsPage removeGlobalNotification(String type) { + return removeGlobalNotification(type, EMAIL); + } + + public NotificationsPage removeGlobalNotification(String type, String channel) { + shouldHaveGlobalNotification(type, channel); + toggleCheckbox(globalCheckboxSelector(type, channel)); + shouldNotHaveGlobalNotification(type, channel); + return this; + } + + public NotificationsPage addProjectNotification(String project, String type, String channel) { + shouldNotHaveProjectNotification(project, type, channel); + toggleCheckbox(projectCheckboxSelector(project, type, channel)); + shouldHaveProjectNotification(project, type, channel); + return this; + } + + public NotificationsPage removeProjectNotification(String project, String type, String channel) { + shouldHaveProjectNotification(project, type, channel); + toggleCheckbox(projectCheckboxSelector(project, type, channel)); + shouldNotHaveProjectNotification(project, type, channel); + return this; + } + + private String globalCheckboxSelector(String type, String channel) { + return "#global-notification-" + type + "-" + channel; + } + + private String projectCheckboxSelector(String project, String type, String channel) { + return "#project-notification-" + project + "-" + type + "-" + channel; + } + + private NotificationsPage shouldBeChecked(String selector) { + $(selector) + .shouldBe(visible) + .shouldHave(cssClass("icon-checkbox-checked")); + return this; + } + + private NotificationsPage shouldNotBeChecked(String selector) { + $(selector) + .shouldBe(visible) + .shouldNotHave(cssClass("icon-checkbox-checked")); + return this; + } + + private void toggleCheckbox(String selector) { + $(selector).click(); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectActivityPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectActivityPage.java new file mode 100644 index 00000000000..05479cb6275 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectActivityPage.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import java.util.List; +import java.util.stream.Collectors; + +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class ProjectActivityPage { + + public ProjectActivityPage() { + $("#project-activity").should(Condition.exist); + } + + public ElementsCollection getAnalyses() { + return $$(".project-activity-analysis"); + } + + public List<ProjectAnalysisItem> getAnalysesAsItems() { + return getAnalyses() + .stream() + .map(ProjectAnalysisItem::new) + .collect(Collectors.toList()); + } + + public ProjectAnalysisItem getLastAnalysis() { + return new ProjectAnalysisItem($(".project-activity-analysis")); + } + + public ProjectAnalysisItem getFirstAnalysis() { + return new ProjectAnalysisItem($$(".project-activity-analysis").last()); + } + + public ProjectActivityPage assertFirstAnalysisOfTheDayHasText(String day, String text) { + $("#project-activity") + .find(".project-activity-day[data-day=\"" + day + "\"]") + .find(".project-activity-analysis") + .should(hasText(text)); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectAnalysisItem.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectAnalysisItem.java new file mode 100644 index 00000000000..2589fa20f02 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectAnalysisItem.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class ProjectAnalysisItem { + + private final SelenideElement elt; + + public ProjectAnalysisItem(SelenideElement elt) { + this.elt = elt; + } + + public ProjectAnalysisItem shouldHaveEventWithText(String text) { + elt.find(".project-activity-events").shouldHave(text(text)); + return this; + } + + public ProjectAnalysisItem shouldHaveDeleteButton() { + elt.find(".js-delete-analysis").shouldBe(visible); + return this; + } + + public ProjectAnalysisItem shouldNotHaveDeleteButton() { + elt.find(".js-delete-analysis").shouldNotBe(visible); + return this; + } + + public void delete() { + elt.find(".js-delete-analysis").click(); + + SelenideElement modal = $(".modal"); + modal.shouldBe(visible); + modal.find("button[type=\"submit\"]").click(); + + elt.shouldNotBe(visible); + } + + public ProjectAnalysisItem addCustomEvent(String name) { + elt.find(".js-create").click(); + elt.find(".js-add-event").click(); + + SelenideElement modal = $(".modal"); + modal.shouldBe(visible); + modal.find("input").setValue(name); + modal.find("button[type=\"submit\"]").click(); + + elt.find(".project-activity-event:last-child").shouldHave(text(name)); + + return this; + } + + public ProjectAnalysisItem changeLastEvent(String newName) { + SelenideElement lastEvent = elt.find(".project-activity-event:last-child"); + lastEvent.find(".js-change-event").click(); + + SelenideElement modal = $(".modal"); + modal.shouldBe(visible); + modal.find("input").setValue(newName); + modal.find("button[type=\"submit\"]").click(); + + lastEvent.shouldHave(text(newName)); + + return this; + } + + public ProjectAnalysisItem deleteLastEvent() { + int eventsCount = elt.findAll(".project-activity-event").size(); + + SelenideElement lastEvent = elt.find(".project-activity-event:last-child"); + lastEvent.find(".js-delete-event").click(); + + SelenideElement modal = $(".modal"); + modal.shouldBe(visible); + modal.find("button[type=\"submit\"]").click(); + + elt.findAll(".project-activity-event").shouldHaveSize(eventsCount - 1); + + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java new file mode 100644 index 00000000000..f576ea4cc32 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; +import java.util.Arrays; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class ProjectDashboardPage { + + public ProjectDashboardPage() { + $(".overview").shouldBe(visible); + } + + public SelenideElement getLinesOfCode() { + SelenideElement element = $("#overview-ncloc"); + element.shouldBe(visible); + return element; + } + + public SelenideElement getLanguageDistribution() { + SelenideElement element = $("#overview-language-distribution"); + element.shouldBe(visible); + return element; + } + + private SelenideElement getTagsMeta() { + SelenideElement element = $(".overview-meta-tags"); + element.shouldBe(visible); + return element; + } + + public ProjectDashboardPage shouldHaveTags(String... tags) { + String tagsList = String.join(", ", Arrays.asList(tags)); + this.getTagsMeta().$(".tags-list > span").should(hasText(tagsList)); + return this; + } + + public ProjectDashboardPage shouldNotBeEditable() { + SelenideElement tagsElem = this.getTagsMeta(); + tagsElem.$("button").shouldNot(exist); + tagsElem.$("div.multi-select").shouldNot(exist); + return this; + } + + public ProjectDashboardPage shouldBeEditable() { + SelenideElement tagsElem = this.getTagsMeta(); + tagsElem.$("button").shouldBe(visible); + return this; + } + + public ProjectDashboardPage openTagEditor() { + SelenideElement tagsElem = this.getTagsMeta(); + tagsElem.$("button").shouldBe(visible).click(); + tagsElem.$("div.multi-select").shouldBe(visible); + return this; + } + + public SelenideElement getTagAtIdx(Integer idx) { + SelenideElement tagsElem = this.getTagsMeta(); + tagsElem.$("div.multi-select").shouldBe(visible); + return tagsElem.$$("ul.menu a").get(idx); + } + + public ProjectDashboardPage sendKeysToTagsInput(CharSequence... charSequences) { + SelenideElement tagsInput = this.getTagsMeta().find("input"); + tagsInput.sendKeys(charSequences); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectKeyPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectKeyPage.java new file mode 100644 index 00000000000..cf72bdadbe5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectKeyPage.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class ProjectKeyPage { + + public ProjectKeyPage() { + $("#project-key").should(exist); + } + + public ProjectKeyPage assertSimpleUpdate() { + $("#update-key-new-key").shouldBe(visible); + $("#update-key-submit").shouldBe(visible); + return this; + } + + public ProjectKeyPage trySimpleUpdate(String newKey) { + $("#update-key-new-key").val(newKey); + $("#update-key-submit").click(); + $("#update-key-confirm").click(); + return this; + } + + public ProjectKeyPage openFineGrainedUpdate() { + $("#update-key-tab-fine").click(); + $("#project-key-fine-grained-update").shouldBe(visible); + return this; + } + + public ProjectKeyPage tryFineGrainedUpdate(String key, String newKey) { + SelenideElement form = $(".js-fine-grained-update[data-key=\"" + key + "\"]"); + form.shouldBe(visible); + + form.$("input").val(newKey); + form.$("button").click(); + + $("#update-key-confirm").click(); + return this; + } + + public ProjectKeyPage assertBulkChange() { + $("#bulk-update-replace").shouldBe(visible); + $("#bulk-update-by").shouldBe(visible); + $("#bulk-update-see-results").shouldBe(visible); + return this; + } + + public ProjectKeyPage simulateBulkChange(String replace, String by) { + $("#bulk-update-replace").val(replace); + $("#bulk-update-by").val(by); + $("#bulk-update-see-results").click(); + + $("#bulk-update-simulation").shouldBe(visible); + return this; + } + + public ProjectKeyPage assertBulkChangeSimulationResult(String oldKey, String newKey) { + SelenideElement row = $("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]"); + row.$(".js-old-key").should(hasText(oldKey)); + row.$(".js-new-key").should(hasText(newKey)); + return this; + } + + public ProjectKeyPage assertDuplicated(String oldKey) { + SelenideElement row = $("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]"); + row.$(".js-new-key").$(".badge-danger").shouldBe(visible); + return this; + } + + public ProjectKeyPage confirmBulkUpdate() { + $("#bulk-update-confirm").click(); + return this; + } + + public ProjectKeyPage assertSuccessfulBulkUpdate() { + $("#project-key-bulk-update").$(".alert.alert-success").shouldBe(visible); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectLinkItem.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectLinkItem.java new file mode 100644 index 00000000000..c652e018e0d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectLinkItem.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; +import org.openqa.selenium.NoSuchElementException; + +public class ProjectLinkItem { + + private final SelenideElement elt; + + public ProjectLinkItem(SelenideElement elt) { + this.elt = elt; + } + + public SelenideElement getName() { + return elt.$(".js-name"); + } + + public SelenideElement getType() { + try { + return elt.$(".js-type"); + } catch (NoSuchElementException e) { + return null; + } + } + + public SelenideElement getUrl() { + return elt.$(".js-url"); + } + + public SelenideElement getDeleteButton() { + return elt.$(".js-delete-button"); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectLinksPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectLinksPage.java new file mode 100644 index 00000000000..a4adbf396f3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectLinksPage.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class ProjectLinksPage { + + public ProjectLinksPage() { + $("#project-links").should(Condition.exist); + } + + public ElementsCollection getLinks() { + return $$("#project-links tr[data-name]"); + } + + public List<ProjectLinkItem> getLinksAsItems() { + return getLinks() + .stream() + .map(ProjectLinkItem::new) + .collect(Collectors.toList()); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectPermissionsPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectPermissionsPage.java new file mode 100644 index 00000000000..954ad779a07 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectPermissionsPage.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import static com.codeborne.selenide.Condition.cssClass; +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class ProjectPermissionsPage { + + public ProjectPermissionsPage() { + $("#project-permissions-page").should(exist); + } + + public ProjectPermissionsPage shouldBePublic() { + $("#visibility-public .icon-radio.is-checked").shouldBe(visible); + return this; + } + + public ProjectPermissionsPage shouldBePrivate() { + $("#visibility-private .icon-radio.is-checked").shouldBe(visible); + return this; + } + + public ProjectPermissionsPage turnToPublic() { + $("#visibility-public").click(); + $("#confirm-turn-to-public").click(); + shouldBePublic(); + return this; + } + + public ProjectPermissionsPage turnToPrivate() { + $("#visibility-private").click(); + shouldBePrivate(); + return this; + } + + public ProjectPermissionsPage shouldNotAllowPrivate() { + $("#visibility-private").shouldHave(cssClass("text-muted")); + $(".upgrade-organization-box").shouldBe(visible); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectQualityGatePage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectQualityGatePage.java new file mode 100644 index 00000000000..58e28acaacf --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectQualityGatePage.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Selenide.$; + +public class ProjectQualityGatePage { + + public ProjectQualityGatePage() { + $("#project-quality-gate").should(exist); + } + + public SelenideElement getSelectedQualityGate() { + return $(".Select-value-label"); + } + + public void assertNotSelected() { + $(".Select-placeholder").should(exist); + $(".Select-value-label").shouldNot(exist); + } + + public void setQualityGate(String name) { + $(".Select-input input").val(name).pressEnter(); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java new file mode 100644 index 00000000000..ea5ff5c06db --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class ProjectsManagementPage { + + public ProjectsManagementPage() { + $("#projects-management-page").should(exist); + } + + public ProjectsManagementPage shouldHaveProjectsCount(int count) { + $$("#projects-management-page-projects tr").shouldHaveSize(count); + return this; + } + + public ProjectsManagementPage shouldHaveProject(String key) { + $("#projects-management-page-projects").shouldHave(text(key)); + return this; + } + + public ProjectsManagementPage createProject(String key, String name, String visibility) { + $("#create-project").click(); + $("#create-project-name").val(key); + $("#create-project-key").val(name); + $("#visibility-" + visibility).click(); + $("#create-project-submit").submit(); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/RuleItem.java b/tests/src/test/java/org/sonarqube/pageobjects/RuleItem.java new file mode 100644 index 00000000000..b311f3555b8 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/RuleItem.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +public class RuleItem { + + private final SelenideElement elt; + + public RuleItem(SelenideElement elt) { + this.elt = elt; + } + + public SelenideElement getTitle() { + return elt.$(".coding-rule-title"); + } + + public SelenideElement getMetadata() { + return elt.$(".coding-rule-meta"); + } + + +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/RulesPage.java b/tests/src/test/java/org/sonarqube/pageobjects/RulesPage.java new file mode 100644 index 00000000000..35eda0b68e3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/RulesPage.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import java.util.List; +import java.util.stream.Collectors; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class RulesPage extends Navigation { + + public RulesPage() { + $(By.cssSelector(".coding-rules")).should(Condition.exist); + } + + public ElementsCollection getRules() { + return $$(".coding-rules .coding-rule"); + } + + public List<RuleItem> getRulesAsItems() { + return getRules() + .stream() + .map(elt -> new RuleItem(elt)) + .collect(Collectors.toList()); + } + + public int getTotal() { + // warning - number is localized + return Integer.parseInt($("#coding-rules-total").text()); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/SelenideConfig.java b/tests/src/test/java/org/sonarqube/pageobjects/SelenideConfig.java new file mode 100644 index 00000000000..32c6850f917 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/SelenideConfig.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.Configuration; +import com.codeborne.selenide.WebDriverRunner; +import com.sonar.orchestrator.Orchestrator; +import java.util.stream.Collectors; +import org.openqa.selenium.WebDriver; + +import static java.util.Arrays.stream; + +public class SelenideConfig { + + private enum Browser { + firefox("(v46 and lower)"), + marionette("(recent Firefox, require Geckodriver)"), + chrome("(require Chromedriver)"); + + private final String label; + + Browser(String label) { + this.label = label; + } + + static Browser of(String s) { + try { + return Browser.valueOf(s); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid browser: " + s + ". Supported values are " + + stream(values()).map(b -> b.name() + " " + b.label).collect(Collectors.joining(", "))); + } + } + } + + public static WebDriver configure(Orchestrator orchestrator) { + String browserKey = orchestrator.getConfiguration().getString("orchestrator.browser", Browser.firefox.name()); + Browser browser = Browser.of(browserKey); + Configuration.browser = browser.name(); + Configuration.baseUrl = orchestrator.getServer().getUrl(); + Configuration.timeout = 8_000; + Configuration.reportsFolder = "target/screenshots"; + Configuration.screenshots = true; + Configuration.captureJavascriptErrors = true; + Configuration.savePageSource = true; + Configuration.browserSize = "1280x1024"; + return getWebDriver(); + } + + static WebDriver getWebDriver() { + return WebDriverRunner.getWebDriver(); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/ServerIdPage.java b/tests/src/test/java/org/sonarqube/pageobjects/ServerIdPage.java new file mode 100644 index 00000000000..e65171c8425 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/ServerIdPage.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class ServerIdPage { + + public ServerIdPage() { + $("#server-id-page").shouldBe(visible); + } + + public SelenideElement serverIdInput() { + return $("#server-id-result").shouldBe(visible); + } + + private SelenideElement organizationInput() { + return $("#server-id-organization").shouldBe(visible); + } + + private SelenideElement ipAddressInput() { + return $("#server-id-ip").shouldBe(visible); + } + + public ServerIdPage assertError() { + $(".process-spinner-failed").shouldBe(visible); + return this; + } + + public ServerIdPage setOrganization(String organization) { + organizationInput().val(organization); + return this; + } + + public ServerIdPage setIpAddress(String ipAddress) { + ipAddressInput().val(ipAddress); + return this; + } + + public ServerIdPage submitForm() { + $("#server-id-form").submit(); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/issues/Issue.java b/tests/src/test/java/org/sonarqube/pageobjects/issues/Issue.java new file mode 100644 index 00000000000..a2c6e64648e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/issues/Issue.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.issues; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class Issue { + + private final SelenideElement elt; + + public Issue(SelenideElement elt) { + this.elt = elt; + } + + public Issue shouldAllowAssign() { + elt.find(".js-issue-assign").shouldBe(visible); + return this; + } + + public Issue shouldAllowChangeType() { + elt.find(".js-issue-set-type").shouldBe(visible); + return this; + } + + public Issue shouldNotAllowAssign() { + elt.find(".js-issue-assign").shouldNotBe(visible); + return this; + } + + public Issue shouldNotAllowChangeType() { + elt.find(".js-issue-set-type").shouldNotBe(visible); + return this; + } + + public Issue assigneeSearchResultCount(String query, Integer count) { + SelenideElement assignLink = elt.find(".js-issue-assign"); + assignLink.click(); + SelenideElement popupMenu = $(".bubble-popup ul.menu").shouldBe(visible); + $(".bubble-popup input.search-box-input").shouldBe(visible).val("").sendKeys(query); + popupMenu.$("li a[data-text='Not assigned']").shouldNot(exist); + popupMenu.$$("li").shouldHaveSize(count); + assignLink.click(); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java b/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java new file mode 100644 index 00000000000..d09c894f9d9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/issues/IssuesPage.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.issues; + +import com.codeborne.selenide.ElementsCollection; +import java.util.List; +import java.util.stream.Collectors; + +import static com.codeborne.selenide.CollectionCondition.sizeGreaterThan; +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class IssuesPage { + + public IssuesPage() { + $(".issues").should(exist); + } + + private ElementsCollection getIssuesElements() { + return $$(".issues .issue"); + } + + public List<Issue> getIssues() { + return getIssuesElements() + .stream() + .map(Issue::new) + .collect(Collectors.toList()); + } + + public Issue getFirstIssue() { + getIssuesElements().shouldHave(sizeGreaterThan(0)); + return new Issue(getIssuesElements().first()); + } + + public IssuesPage bulkChangeOpen() { + $("#issues-bulk-change").shouldBe(visible).click(); + $("#bulk-change-form").shouldBe(visible); + return this; + } + + public IssuesPage bulkChangeAssigneeSearchCount(String query, Integer count) { + $("#issues-bulk-change-assignee .Select-input input").val(query); + $$("#issues-bulk-change-assignee .Select-option").shouldHaveSize(count); + $("#issues-bulk-change-assignee .Select-input input").pressEscape(); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/licenses/LicenseItem.java b/tests/src/test/java/org/sonarqube/pageobjects/licenses/LicenseItem.java new file mode 100644 index 00000000000..61b7753cc26 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/licenses/LicenseItem.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.licenses; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class LicenseItem { + + private final SelenideElement elt; + + public LicenseItem(SelenideElement elt) { + this.elt = elt; + } + + public SelenideElement getName() { + return elt.find(".js-product"); + } + + public LicenseItem setLicense(String value) { + elt.find(".js-change").click(); + $("#license-input").shouldBe(visible).val(value); + $(".js-modal-submit").click(); + $("#license-input").shouldNotBe(visible); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/licenses/LicensesPage.java b/tests/src/test/java/org/sonarqube/pageobjects/licenses/LicensesPage.java new file mode 100644 index 00000000000..95e1f438f30 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/licenses/LicensesPage.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.licenses; + +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; +import java.util.List; +import java.util.stream.Collectors; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class LicensesPage { + + public LicensesPage() { + $("#licenses-page").shouldBe(visible); + } + + public ElementsCollection getLicenses() { + return $$(".js-license"); + } + + public List<LicenseItem> getLicensesAsItems() { + return getLicenses() + .stream() + .map(LicenseItem::new) + .collect(Collectors.toList()); + } + + public LicenseItem getLicenseByKey(String key) { + SelenideElement element = $(".js-license[data-license-key=\"" + key + "\"]"); + return new LicenseItem(element); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/organization/MemberItem.java b/tests/src/test/java/org/sonarqube/pageobjects/organization/MemberItem.java new file mode 100644 index 00000000000..0de4be4c937 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/organization/MemberItem.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.pageobjects.organization; + +import com.codeborne.selenide.CollectionCondition; +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Selenide.$; + +public class MemberItem { + private final SelenideElement elt; + + public MemberItem(SelenideElement elt) { + this.elt = elt; + } + + public MemberItem shouldBeNamed(String login, String name) { + ElementsCollection tds = this.elt.$$("td"); + tds.get(1).$("strong").shouldHave(Condition.text(name)); + tds.get(1).$("span").shouldHave(Condition.text(login)); + return this; + } + + public MemberItem shouldHaveGroups(Integer groups) { + ElementsCollection tds = this.elt.$$("td"); + tds.get(2).should(Condition.exist); + tds.get(2).shouldHave(Condition.text(groups.toString())); + return this; + } + + public MemberItem shouldNotHaveActions() { + this.elt.$$("td").shouldHave(CollectionCondition.sizeLessThan(3)); + return this; + } + + public MemberItem removeMembership() { + ElementsCollection tds = this.elt.$$("td"); + tds.shouldHave(CollectionCondition.sizeGreaterThan(3)); + SelenideElement actionTd = tds.get(3); + actionTd.$("button").should(Condition.exist).click(); + actionTd.$$(".dropdown-menu > li").get(2).shouldBe(Condition.visible).click(); + SelenideElement modal = getModal("Remove user"); + modal.$("button.button-red").shouldBe(Condition.visible).click(); + return this; + } + + public MemberItem manageGroupsOpen() { + ElementsCollection tds = this.elt.$$("td"); + tds.shouldHave(CollectionCondition.sizeGreaterThan(3)); + SelenideElement actionTd = tds.get(3); + actionTd.$("button").should(Condition.exist).click(); + actionTd.$$(".dropdown-menu > li").get(0).shouldBe(Condition.visible).click(); + getModal("Manage groups"); + return this; + } + + public MemberItem manageGroupsSelect(String group) { + SelenideElement modal = getModal("Manage groups"); + modal.$$("li").find(Condition.text(group)).shouldBe(Condition.visible).click(); + return this; + } + + public MemberItem manageGroupsSave() { + SelenideElement modal = getModal("Manage groups"); + modal.$("button[type='submit']").shouldBe(Condition.visible).click(); + return this; + } + + private SelenideElement getModal(String title) { + $(".modal-head").should(Condition.exist).shouldHave(Condition.text(title)); + SelenideElement form = $(".ReactModalPortal form"); + form.should(Condition.exist); + return form; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/organization/MembersPage.java b/tests/src/test/java/org/sonarqube/pageobjects/organization/MembersPage.java new file mode 100644 index 00000000000..76d091305f6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/organization/MembersPage.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.pageobjects.organization; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +public class MembersPage { + + public MembersPage() { + $(".nav-tabs a.active").shouldBe(visible).shouldHave(text("Members")); + } + + public ElementsCollection getMembers() { + return $$("table.data tr"); + } + + public MemberItem getMembersByIdx(Integer idx) { + return new MemberItem(getMembers().get(idx)); + } + + public MembersPage shouldHaveTotal(int total) { + $(".panel-vertical > span > strong").shouldHave(text(String.valueOf(total))); + return this; + } + + public MembersPage searchForMember(String query) { + $("input.search-box-input").shouldBe(visible).val("").sendKeys(query); + return this; + } + + public MembersPage canAddMember() { + $(".page-actions").shouldBe(visible); + return this; + } + + public MembersPage canNotAddMember() { + $(".page-actions").shouldNot(Condition.exist); + return this; + } + + public MembersPage addMember(String login) { + this.canAddMember(); + $(".page-actions button").click(); + + SelenideElement modal = this.getModal("Add user"); + SelenideElement input = modal.$(".Select-input input"); + input.val(login); + modal.$("div.Select-option.is-focused").should(Condition.exist); + input.pressEnter(); + modal.$("button[type='submit']").click(); + return this; + } + + private SelenideElement getModal(String title) { + $(".modal-head").should(Condition.exist).shouldHave(text(title)); + SelenideElement form = $(".ReactModalPortal form"); + form.should(Condition.exist); + return form; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/projects/FacetItem.java b/tests/src/test/java/org/sonarqube/pageobjects/projects/FacetItem.java new file mode 100644 index 00000000000..ec3197c5a62 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/projects/FacetItem.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.projects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +public class FacetItem { + + private final SelenideElement elt; + + public FacetItem(SelenideElement elt) { + this.elt = elt; + } + + public FacetItem shouldHaveValue(String key, String value) { + this.elt.$(".facet[data-key=\"" + key + "\"] .facet-stat").shouldHave(Condition.text(value)); + return this; + } + + public void selectValue(String key) { + this.elt.$(".facet[data-key=\"" + key + "\"]").click(); + } + + public FacetItem selectOptionItem(String value) { + SelenideElement selectInput = this.elt.$(".Select-input input"); + selectInput.val(value); + this.elt.$("div.Select-option.is-focused").should(Condition.exist); + selectInput.pressEnter(); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/projects/ProjectItem.java b/tests/src/test/java/org/sonarqube/pageobjects/projects/ProjectItem.java new file mode 100644 index 00000000000..6261efa9129 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/projects/ProjectItem.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.projects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +public class ProjectItem { + + private final SelenideElement elt; + + public ProjectItem(SelenideElement elt) { + this.elt = elt; + } + + public ProjectItem shouldHaveMeasure(String metricKey, String value) { + this.elt.$(".project-card-measure[data-key=\"" + metricKey + "\"]").shouldHave(Condition.text(value)); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/projects/ProjectsPage.java b/tests/src/test/java/org/sonarqube/pageobjects/projects/ProjectsPage.java new file mode 100644 index 00000000000..d019dcd74c8 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/projects/ProjectsPage.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.projects; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; +import static com.codeborne.selenide.WebDriverRunner.url; +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectsPage { + + public ProjectsPage() { + $("#projects-page").shouldBe(visible); + } + + public ElementsCollection getProjects() { + return $$(".projects-list > .boxed-group"); + } + + public ElementsCollection getFacets() { + return $$(".search-navigator-facet-box"); + } + + public ProjectItem getProjectByKey(String projectKey) { + SelenideElement element = getProjects().find(Condition.attribute("data-key", projectKey)); + return new ProjectItem(element); + } + + public ProjectItem getProjectByIdx(Integer idx) { + return new ProjectItem(getProjects().get(idx)); + } + + public FacetItem getFacetByProperty(String facetProperty) { + SelenideElement element = getFacets().find(Condition.attribute("data-key", facetProperty)); + return new FacetItem(element); + } + + public ProjectsPage shouldHaveTotal(int total) { + // warning - number is localized + $("#projects-total").shouldHave(text(String.valueOf(total))); + return this; + } + + public ProjectsPage shouldDisplayAllProjects() { + assertThat(url()).endsWith("/projects"); + return this; + } + + public ProjectsPage shouldDisplayAllProjectsWidthSort(String sort) { + assertThat(url()).endsWith("/projects?sort=" + sort); + return this; + } + + public ProjectsPage shouldDisplayFavoriteProjects() { + assertThat(url()).endsWith("/projects/favorite"); + return this; + } + + public ProjectsPage selectAllProjects() { + $("#all-projects").click(); + return shouldDisplayAllProjects(); + } + + public ProjectsPage selectFavoriteProjects() { + $("#favorite-projects").click(); + return shouldDisplayFavoriteProjects(); + } + + public ProjectsPage searchProject(String search) { + SelenideElement searchInput = $(".projects-topbar-item-search input"); + searchInput.setValue("").sendKeys(search); + return this; + } + + public ProjectsPage changePerspective(String perspective) { + SelenideElement sortSelect = getOpenTopBar().$(".js-projects-perspective-select"); + sortSelect.$(".Select-value").should(Condition.exist).click(); + sortSelect.$(".Select-option[title='" + perspective + "']").should(Condition.exist).click(); + return this; + } + + public ProjectsPage sortProjects(String sort) { + SelenideElement sortSelect = getOpenTopBar().$(".js-projects-sorting-select"); + sortSelect.$(".Select-value").should(Condition.exist).click(); + sortSelect.$(".Select-option[title='" + sort + "']").should(Condition.exist).click(); + return this; + } + + public ProjectsPage invertSorting() { + getOpenTopBar().$(".js-projects-sorting-select a.button-icon").should(Condition.exist).click(); + return this; + } + + private SelenideElement getOpenTopBar() { + return $(".projects-topbar-items").should(Condition.exist); + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/settings/PropertySetInput.java b/tests/src/test/java/org/sonarqube/pageobjects/settings/PropertySetInput.java new file mode 100644 index 00000000000..4f1c7db3a32 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/settings/PropertySetInput.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.settings; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.exist; + +public class PropertySetInput { + + private final SelenideElement elt; + + public PropertySetInput(SelenideElement elt) { + this.elt = elt; + } + + public PropertySetInput setFieldValue(int index, String fieldKey, String value) { + elt.findAll("input[name$=\"[" + fieldKey + "]\"]").get(index).val(value); + return this; + } + + public PropertySetInput setFieldValue(String fieldKey, String value) { + return setFieldValue(0, fieldKey, value); + } + + public PropertySetInput save() { + elt.find(".js-save-changes").click(); + elt.find(".js-save-changes").shouldNot(exist); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/pageobjects/settings/SettingsPage.java b/tests/src/test/java/org/sonarqube/pageobjects/settings/SettingsPage.java new file mode 100644 index 00000000000..1d1af886d1e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/pageobjects/settings/SettingsPage.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.pageobjects.settings; + +import com.codeborne.selenide.SelenideElement; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Condition.cssClass; +import static com.codeborne.selenide.Condition.exactValue; +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class SettingsPage { + + public SettingsPage() { + $("#settings-page").shouldBe(visible); + } + + public SettingsPage assertMenuContains(String categoryName) { + $(".side-tabs-menu").$(By.linkText(categoryName)).shouldBe(visible); + return this; + } + + public SettingsPage assertSettingDisplayed(String settingKey) { + $(".settings-definition[data-key='" + settingKey + "']").shouldBe(visible); + return this; + } + + public SettingsPage assertSettingNotDisplayed(String settingKey) { + $(".settings-definition[data-key='" + settingKey + "']").shouldNotBe(visible); + return this; + } + + public SettingsPage openCategory(String categoryName) { + $(".side-tabs-menu").$(By.linkText(categoryName)).click(); + return this; + } + + public SettingsPage assertStringSettingValue(String settingKey, String value) { + $("input[name=\"settings[" + settingKey + "]\"]").shouldHave(exactValue(value)); + return this; + } + + public SettingsPage assertBooleanSettingValue(String settingKey, boolean value) { + SelenideElement toggle = $("button[name=\"settings[" + settingKey + "]\"]"); + if (value) { + toggle.shouldHave(cssClass("boolean-toggle-on")); + } else { + toggle.shouldNotHave(cssClass("boolean-toggle-on")); + } + return this; + } + + public SettingsPage setStringValue(String settingKey, String value) { + SelenideElement setting = $(".settings-definition[data-key=\"" + settingKey + "\"]"); + setting.find("input").val(value); + setting.find(".js-save-changes").click(); + setting.find(".js-save-changes").shouldNot(exist); + return this; + } + + public PropertySetInput getPropertySetInput(String settingKey) { + SelenideElement setting = $(".settings-definition[data-key=\"" + settingKey + "\"]"); + return new PropertySetInput(setting); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category1Suite.java b/tests/src/test/java/org/sonarqube/tests/Category1Suite.java new file mode 100644 index 00000000000..b48b1283b8a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Category1Suite.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.projectAdministration.ProjectVisibilityTest; +import org.sonarqube.tests.user.UsersPageTest; +import org.sonarqube.tests.authorisation.ExecuteAnalysisPermissionTest; +import org.sonarqube.tests.authorisation.IssuePermissionTest; +import org.sonarqube.tests.authorisation.PermissionSearchTest; +import org.sonarqube.tests.authorisation.ProvisioningPermissionTest; +import org.sonarqube.tests.authorisation.QualityProfileAdminPermissionTest; +import org.sonarqube.tests.complexity.ComplexityMeasuresTest; +import org.sonarqube.tests.customMeasure.CustomMeasuresTest; +import org.sonarqube.tests.i18n.I18nTest; +import org.sonarqube.tests.measure.MeasuresWsTest; +import org.sonarqube.tests.measure.ProjectDashboardTest; +import org.sonarqube.tests.measure.ProjectMeasuresPageTest; +import org.sonarqube.tests.measure.DifferentialPeriodsTest; +import org.sonarqube.tests.measure.SincePreviousVersionHistoryTest; +import org.sonarqube.tests.measure.SinceXDaysHistoryTest; +import org.sonarqube.tests.measure.TimeMachineTest; +import org.sonarqube.tests.projectAdministration.BackgroundTasksTest; +import org.sonarqube.tests.projectAdministration.BulkDeletionTest; +import org.sonarqube.tests.projectAdministration.ProjectAdministrationTest; +import org.sonarqube.tests.projectAdministration.ProjectLinksPageTest; +import org.sonarqube.tests.projectSearch.ProjectsPageTest; +import org.sonarqube.tests.qualityGate.QualityGateNotificationTest; +import org.sonarqube.tests.qualityGate.QualityGateTest; +import org.sonarqube.tests.qualityGate.QualityGateUiTest; +import org.sonarqube.tests.settings.DeprecatedPropertiesWsTest; +import org.sonarqube.tests.settings.EmailsTest; +import org.sonarqube.tests.settings.PropertySetsTest; +import org.sonarqube.tests.settings.SettingsTest; +import org.sonarqube.tests.sourceCode.EncodingTest; +import org.sonarqube.tests.sourceCode.HighlightingTest; +import org.sonarqube.tests.sourceCode.ProjectCodeTest; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.xooPlugin; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + // administration + UsersPageTest.class, + ProjectVisibilityTest.class, + // project administration + BulkDeletionTest.class, + ProjectAdministrationTest.class, + ProjectLinksPageTest.class, + BackgroundTasksTest.class, + // settings + DeprecatedPropertiesWsTest.class, + EmailsTest.class, + PropertySetsTest.class, + SettingsTest.class, + // i18n + I18nTest.class, + // quality gate + QualityGateTest.class, + QualityGateUiTest.class, + QualityGateNotificationTest.class, + // authorisation + ExecuteAnalysisPermissionTest.class, + IssuePermissionTest.class, + PermissionSearchTest.class, + ProvisioningPermissionTest.class, + QualityProfileAdminPermissionTest.class, + // custom measure + CustomMeasuresTest.class, + // measure + ProjectMeasuresPageTest.class, + ProjectDashboardTest.class, + ProjectsPageTest.class, + MeasuresWsTest.class, + // measure history + DifferentialPeriodsTest.class, + SincePreviousVersionHistoryTest.class, + SinceXDaysHistoryTest.class, + TimeMachineTest.class, + // source code + EncodingTest.class, + HighlightingTest.class, + ProjectCodeTest.class, + // complexity + ComplexityMeasuresTest.class +}) +public class Category1Suite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv() + .setServerProperty("sonar.notifications.delay", "1") + .addPlugin(pluginArtifact("property-sets-plugin")) + .addPlugin(pluginArtifact("sonar-subcategories-plugin")) + + // Used in I18nTest + .addPlugin(pluginArtifact("l10n-fr-pack")) + + // 1 second. Required for notification test. + .setServerProperty("sonar.notifications.delay", "1") + + // Used in SettingsTest.global_property_change_extension_point + .addPlugin(pluginArtifact("global-property-change-plugin")) + + // Used in SettingsTest.should_get_settings_default_value + .addPlugin(pluginArtifact("server-plugin")) + + .addPlugin(pluginArtifact("posttask-plugin")) + + .addPlugin(xooPlugin()) + .build(); + +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category2Suite.java b/tests/src/test/java/org/sonarqube/tests/Category2Suite.java new file mode 100644 index 00000000000..c101253c84a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Category2Suite.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.issue.AutoAssignTest; +import org.sonarqube.tests.issue.CommonRulesTest; +import org.sonarqube.tests.issue.CustomRulesTest; +import org.sonarqube.tests.issue.IssueActionTest; +import org.sonarqube.tests.issue.IssueBulkChangeTest; +import org.sonarqube.tests.issue.IssueChangelogTest; +import org.sonarqube.tests.issue.IssueCreationTest; +import org.sonarqube.tests.issue.IssueFilterExtensionTest; +import org.sonarqube.tests.issue.IssueFilterOnCommonRulesTest; +import org.sonarqube.tests.issue.IssueFilterTest; +import org.sonarqube.tests.issue.IssueMeasureTest; +import org.sonarqube.tests.issue.IssueNotificationsTest; +import org.sonarqube.tests.issue.IssuePurgeTest; +import org.sonarqube.tests.issue.IssueSearchTest; +import org.sonarqube.tests.issue.IssueTrackingTest; +import org.sonarqube.tests.issue.IssueWorkflowTest; +import org.sonarqube.tests.issue.IssuesPageTest; +import org.sonarqube.tests.issue.NewIssuesMeasureTest; +import org.sonarqube.tests.qualityModel.MaintainabilityMeasureTest; +import org.sonarqube.tests.qualityModel.MaintainabilityRatingMeasureTest; +import org.sonarqube.tests.qualityModel.NewDebtRatioMeasureTest; +import org.sonarqube.tests.qualityModel.ReliabilityMeasureTest; +import org.sonarqube.tests.qualityModel.SecurityMeasureTest; +import org.sonarqube.tests.qualityModel.TechnicalDebtInIssueChangelogTest; +import org.sonarqube.tests.qualityModel.TechnicalDebtMeasureVariationTest; +import org.sonarqube.tests.qualityModel.TechnicalDebtTest; +import org.sonarqube.tests.scm.ScmTest; +import org.sonarqube.tests.test.CoverageTest; +import org.sonarqube.tests.test.CoverageTrackingTest; +import org.sonarqube.tests.test.NewCoverageTest; +import org.sonarqube.tests.test.TestExecutionTest; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.xooPlugin; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + // test + CoverageTrackingTest.class, + CoverageTest.class, + NewCoverageTest.class, + TestExecutionTest.class, + // scm + ScmTest.class, + // issue + AutoAssignTest.class, + CommonRulesTest.class, + CustomRulesTest.class, + IssueActionTest.class, + IssueBulkChangeTest.class, + IssueChangelogTest.class, + IssueCreationTest.class, + IssueFilterOnCommonRulesTest.class, + IssueFilterTest.class, + IssueFilterExtensionTest.class, + IssueMeasureTest.class, + IssueNotificationsTest.class, + IssuePurgeTest.class, + IssueSearchTest.class, + IssueTrackingTest.class, + IssueWorkflowTest.class, + NewIssuesMeasureTest.class, + // debt + MaintainabilityMeasureTest.class, + MaintainabilityRatingMeasureTest.class, + NewDebtRatioMeasureTest.class, + ReliabilityMeasureTest.class, + SecurityMeasureTest.class, + TechnicalDebtInIssueChangelogTest.class, + TechnicalDebtMeasureVariationTest.class, + TechnicalDebtTest.class, + // ui + IssuesPageTest.class +}) +public class Category2Suite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv() + .addPlugin(xooPlugin()) + + // issue + .addPlugin(pluginArtifact("issue-filter-plugin")) + + // 1 second. Required for notification test. + .setServerProperty("sonar.notifications.delay", "1") + + .build(); + +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category3Suite.java b/tests/src/test/java/org/sonarqube/tests/Category3Suite.java new file mode 100644 index 00000000000..25dd138a78e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Category3Suite.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.analysis.ExtensionLifecycleTest; +import org.sonarqube.tests.analysis.FavoriteTest; +import org.sonarqube.tests.analysis.IssueJsonReportTest; +import org.sonarqube.tests.analysis.IssuesModeTest; +import org.sonarqube.tests.analysis.LinksTest; +import org.sonarqube.tests.analysis.MultiLanguageTest; +import org.sonarqube.tests.analysis.PermissionTest; +import org.sonarqube.tests.analysis.ProjectBuilderTest; +import org.sonarqube.tests.analysis.ReportDumpTest; +import org.sonarqube.tests.analysis.SSLTest; +import org.sonarqube.tests.analysis.ScannerTest; +import org.sonarqube.tests.analysis.SettingsEncryptionTest; +import org.sonarqube.tests.analysis.TempFolderTest; +import org.sonarqube.tests.measure.DecimalScaleMetricTest; +import org.sonarqube.tests.plugins.VersionPluginTest; +import org.sonarqube.tests.webhook.WebhooksTest; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.xooPlugin; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + // analysis + PermissionTest.class, + ExtensionLifecycleTest.class, + LinksTest.class, + ProjectBuilderTest.class, + TempFolderTest.class, + MultiLanguageTest.class, + IssueJsonReportTest.class, + ScannerTest.class, + IssuesModeTest.class, + VersionPluginTest.class, + SettingsEncryptionTest.class, + ReportDumpTest.class, + SSLTest.class, + FavoriteTest.class, + // measures + DecimalScaleMetricTest.class, + WebhooksTest.class +}) +public class Category3Suite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv() + .addPlugin(xooPlugin()) + .setOrchestratorProperty("javaVersion", "LATEST_RELEASE").addPlugin("java") + + // Used by SettingsEncryptionTest + .addPlugin(pluginArtifact("settings-encryption-plugin")) + + // Used by IssuesModeTest + .addPlugin(pluginArtifact("access-secured-props-plugin")) + + // used by TempFolderTest and DecimalScaleMetricTest + .addPlugin(pluginArtifact("batch-plugin")) + + // used by ExtensionLifecycleTest + .addPlugin(pluginArtifact("extension-lifecycle-plugin")) + + // used by ProjectBuilderTest + .addPlugin(pluginArtifact("project-builder-plugin")) + + .build(); +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category4Suite.java b/tests/src/test/java/org/sonarqube/tests/Category4Suite.java new file mode 100644 index 00000000000..79eebb60fa6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Category4Suite.java @@ -0,0 +1,124 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.analysis.FileExclusionsTest; +import org.sonarqube.tests.analysis.IssueExclusionsTest; +import org.sonarqube.tests.component.ComponentsWsTest; +import org.sonarqube.tests.component.ProjectsWsTest; +import org.sonarqube.tests.dbCleaner.PurgeTest; +import org.sonarqube.tests.duplication.CrossProjectDuplicationsOnRemoveFileTest; +import org.sonarqube.tests.duplication.CrossProjectDuplicationsTest; +import org.sonarqube.tests.duplication.DuplicationsTest; +import org.sonarqube.tests.duplication.NewDuplicationsTest; +import org.sonarqube.tests.organization.RootUserTest; +import org.sonarqube.tests.projectEvent.EventTest; +import org.sonarqube.tests.projectEvent.ProjectActivityPageTest; +import org.sonarqube.tests.qualityProfile.QualityProfilesUiTest; +import org.sonarqube.tests.serverSystem.HttpHeadersTest; +import org.sonarqube.tests.serverSystem.LogsTest; +import org.sonarqube.tests.serverSystem.PingTest; +import org.sonarqube.tests.serverSystem.ServerSystemTest; +import org.sonarqube.tests.ui.SourceViewerTest; +import org.sonarqube.tests.ui.UiTest; +import org.sonarqube.tests.ui.UiExtensionsTest; +import org.sonarqube.tests.user.BaseIdentityProviderTest; +import org.sonarqube.tests.user.FavoritesWsTest; +import org.sonarqube.tests.user.ForceAuthenticationTest; +import org.sonarqube.tests.user.LocalAuthenticationTest; +import org.sonarqube.tests.user.MyAccountPageTest; +import org.sonarqube.tests.user.OAuth2IdentityProviderTest; +import org.sonarqube.tests.ws.WsLocalCallTest; +import org.sonarqube.tests.ws.WsTest; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.xooPlugin; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + // organization + RootUserTest.class, + // server system + ServerSystemTest.class, + PingTest.class, + // user + MyAccountPageTest.class, + FavoritesWsTest.class, + // authentication + ForceAuthenticationTest.class, + LocalAuthenticationTest.class, + BaseIdentityProviderTest.class, + OAuth2IdentityProviderTest.class, + // component search + ProjectsWsTest.class, + ComponentsWsTest.class, + // analysis exclusion + FileExclusionsTest.class, + IssueExclusionsTest.class, + // duplication + CrossProjectDuplicationsTest.class, + CrossProjectDuplicationsOnRemoveFileTest.class, + DuplicationsTest.class, + NewDuplicationsTest.class, + // db cleaner + PurgeTest.class, + // project event + EventTest.class, + ProjectActivityPageTest.class, + // http + HttpHeadersTest.class, + // ui + UiTest.class, + SourceViewerTest.class, + // ui extensions + UiExtensionsTest.class, + WsLocalCallTest.class, + WsTest.class, + // quality profiles + QualityProfilesUiTest.class, + LogsTest.class +}) +public class Category4Suite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv() + .addPlugin(xooPlugin()) + + // Used in BaseIdentityProviderTest + .addPlugin(pluginArtifact("base-auth-plugin")) + + // Used in OAuth2IdentityProviderTest + .addPlugin(pluginArtifact("oauth2-auth-plugin")) + + // Used in UiExtensionsTest + .addPlugin(pluginArtifact("ui-extensions-plugin")) + + // Used by WsLocalCallTest + .addPlugin(pluginArtifact("ws-plugin")) + + // Used by LogsTest + .setServerProperty("sonar.web.accessLogs.pattern", LogsTest.ACCESS_LOGS_PATTERN) + + .build(); +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java new file mode 100644 index 00000000000..283aea49471 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import org.sonarqube.tests.serverSystem.ClusterTest; +import org.sonarqube.tests.serverSystem.RestartTest; +import org.sonarqube.tests.serverSystem.ServerSystemRestartingOrchestrator; +import org.sonarqube.tests.settings.LicensesPageTest; +import org.sonarqube.tests.settings.SettingsTestRestartingOrchestrator; +import org.sonarqube.tests.updateCenter.UpdateCenterTest; +import org.sonarqube.tests.user.OnboardingTest; +import org.sonarqube.tests.user.RealmAuthenticationTest; +import org.sonarqube.tests.user.SsoAuthenticationTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * This suite is reserved to the tests that start their own instance of Orchestrator. + * Indeed multiple instances of Orchestrator can't be started in parallel, so this + * suite does not declare a shared Orchestrator. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ClusterTest.class, + ServerSystemRestartingOrchestrator.class, + RestartTest.class, + SettingsTestRestartingOrchestrator.class, + LicensesPageTest.class, + // update center + UpdateCenterTest.class, + RealmAuthenticationTest.class, + SsoAuthenticationTest.class, + OnboardingTest.class +}) +public class Category5Suite { + +} diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java new file mode 100644 index 00000000000..96983945555 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.issue.IssueTagsTest; +import org.sonarqube.tests.issue.OrganizationIssueAssignTest; +import org.sonarqube.tests.organization.BillingTest; +import org.sonarqube.tests.organization.OrganizationMembershipTest; +import org.sonarqube.tests.organization.OrganizationMembershipUiTest; +import org.sonarqube.tests.organization.OrganizationTest; +import org.sonarqube.tests.organization.PersonalOrganizationTest; +import org.sonarqube.tests.organization.RootUserOnOrganizationTest; +import org.sonarqube.tests.projectSearch.LeakProjectsPageTest; +import org.sonarqube.tests.projectSearch.SearchProjectsTest; +import org.sonarqube.tests.qualityProfile.BuiltInQualityProfilesTest; +import org.sonarqube.tests.qualityProfile.CustomQualityProfilesTest; +import org.sonarqube.tests.qualityProfile.OrganizationQualityProfilesUiTest; +import org.sonarqube.tests.ui.OrganizationUiExtensionsTest; +import org.sonarqube.tests.user.OrganizationIdentityProviderTest; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.xooPlugin; + +/** + * This category is used only when organizations feature is activated + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + OrganizationIdentityProviderTest.class, + OrganizationIssueAssignTest.class, + OrganizationMembershipTest.class, + OrganizationMembershipUiTest.class, + OrganizationQualityProfilesUiTest.class, + OrganizationTest.class, + RootUserOnOrganizationTest.class, + OrganizationUiExtensionsTest.class, + PersonalOrganizationTest.class, + BuiltInQualityProfilesTest.class, + CustomQualityProfilesTest.class, + BillingTest.class, + IssueTagsTest.class, + LeakProjectsPageTest.class, + SearchProjectsTest.class +}) +public class Category6Suite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv() + .addPlugin(xooPlugin()) + .addPlugin(pluginArtifact("base-auth-plugin")) + .addPlugin(pluginArtifact("fake-billing-plugin")) + .addPlugin(pluginArtifact("ui-extensions-plugin")) + .build(); +} diff --git a/tests/src/test/java/org/sonarqube/tests/GroupTester.java b/tests/src/test/java/org/sonarqube/tests/GroupTester.java new file mode 100644 index 00000000000..14568da774b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/GroupTester.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUserGroups; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.WsUsers.GroupsWsResponse.Group; +import org.sonarqube.ws.client.user.GroupsRequest; +import org.sonarqube.ws.client.usergroup.AddUserWsRequest; +import org.sonarqube.ws.client.usergroup.CreateWsRequest; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; + +public class GroupTester { + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + private final Session session; + + GroupTester(Session session) { + this.session = session; + } + + @SafeVarargs + public final WsUserGroups.Group generate(@Nullable Organizations.Organization organization, Consumer<CreateWsRequest.Builder>... populators) { + int id = ID_GENERATOR.getAndIncrement(); + CreateWsRequest.Builder request = CreateWsRequest.builder() + .setName("Group" + id) + .setDescription("Description " + id) + .setOrganization(organization != null ? organization.getKey() : null); + stream(populators).forEach(p -> p.accept(request)); + return session.wsClient().userGroups().create(request.build()).getGroup(); + } + + public List<Group> getGroupsOfUser(@Nullable Organizations.Organization organization, String userLogin) { + GroupsRequest request = GroupsRequest.builder() + .setOrganization(organization != null ? organization.getKey() : null) + .setLogin(userLogin) + .build(); + WsUsers.GroupsWsResponse response = session.users().service().groups(request); + return response.getGroupsList(); + } + + public GroupTester addMemberToGroups(Organizations.Organization organization, String userLogin, String... groups) { + for (String group : groups) { + AddUserWsRequest request = AddUserWsRequest.builder() + .setLogin(userLogin) + .setOrganization(organization.getKey()) + .setName(group) + .build(); + session.wsClient().userGroups().addUser(request); + } + return this; + } + + public GroupTester assertThatUserIsMemberOf(@Nullable Organizations.Organization organization, String userLogin, String expectedGroup, String... otherExpectedGroups) { + List<String> groups = getGroupsOfUser(organization, userLogin) + .stream() + .map(Group::getName) + .collect(Collectors.toList()); + + assertThat(groups).contains(expectedGroup); + if (otherExpectedGroups.length > 0) { + assertThat(groups).contains(otherExpectedGroups); + } + return this; + } + + public GroupTester assertThatUserIsOnlyMemberOf(@Nullable Organizations.Organization organization, String userLogin, String... expectedGroups) { + Set<String> groups = getGroupsOfUser(organization, userLogin) + .stream() + .map(Group::getName) + .collect(Collectors.toSet()); + assertThat(groups).containsExactlyInAnyOrder(expectedGroups); + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/OrganizationTester.java b/tests/src/test/java/org/sonarqube/tests/OrganizationTester.java new file mode 100644 index 00000000000..2072e87822a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/OrganizationTester.java @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.organization.CreateWsRequest; +import org.sonarqube.ws.client.organization.OrganizationService; +import org.sonarqube.ws.client.organization.SearchMembersWsRequest; +import org.sonarqube.ws.client.organization.SearchWsRequest; +import org.sonarqube.ws.client.user.GroupsRequest; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; + +public class OrganizationTester { + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + private final Session session; + + OrganizationTester(Session session) { + this.session = session; + } + + void enableSupport() { + session.wsClient().wsConnector().call(new PostRequest("api/organizations/enable_support")); + } + + void deleteNonGuardedOrganizations() { + service().search(SearchWsRequest.builder().build()).getOrganizationsList() + .stream() + .filter(o -> !o.getKey().equals("default-organization")) + .forEach(organization -> service().delete(organization.getKey())); + } + + @SafeVarargs + public final Organizations.Organization generate(Consumer<CreateWsRequest.Builder>... populators) { + int id = ID_GENERATOR.getAndIncrement(); + CreateWsRequest.Builder request = new CreateWsRequest.Builder() + .setKey("org" + id) + .setName("Org " + id) + .setDescription("Description " + id) + .setUrl("http://test" + id); + stream(populators).forEach(p -> p.accept(request)); + return service().create(request.build()).getOrganization(); + } + + public OrganizationTester addMember(Organizations.Organization organization, WsUsers.CreateWsResponse.User user) { + service().addMember(organization.getKey(), user.getLogin()); + return this; + } + + public OrganizationTester assertThatOrganizationDoesNotExist(String organizationKey) { + SearchWsRequest request = new SearchWsRequest.Builder().setOrganizations(organizationKey).build(); + Organizations.SearchWsResponse searchWsResponse = service().search(request); + assertThat(searchWsResponse.getOrganizationsList()).isEmpty(); + return this; + } + + public OrganizationTester assertThatMemberOf(Organizations.Organization organization, WsUsers.CreateWsResponse.User user) { + return assertThatMemberOf(organization, user.getLogin()); + } + + public OrganizationTester assertThatMemberOf(Organizations.Organization organization, String userLogin) { + verifyOrganizationMembership(organization, userLogin, true); + verifyMembersGroupMembership(userLogin, organization, true); + return this; + } + + public OrganizationTester assertThatNotMemberOf(Organizations.Organization organization, WsUsers.CreateWsResponse.User user) { + return assertThatNotMemberOf(organization, user.getLogin()); + } + + public OrganizationTester assertThatNotMemberOf(Organizations.Organization organization, String userLogin) { + verifyOrganizationMembership(organization, userLogin, false); + try { + verifyMembersGroupMembership(userLogin, organization, false); + } catch (HttpException e) { + // do not fail if user does not exist + if (e.code() != 404) { + throw e; + } + } + return this; + } + + private void verifyOrganizationMembership(@Nullable Organizations.Organization organization, String userLogin, boolean isMember) { + List<Organizations.User> users = service().searchMembers(new SearchMembersWsRequest() + .setQuery(userLogin) + .setSelected("selected") + .setOrganization(organization != null ? organization.getKey() : null)) + .getUsersList(); + assertThat(users).hasSize(isMember ? 1 : 0); + } + + private void verifyMembersGroupMembership(String userLogin, @Nullable Organizations.Organization organization, boolean isMember) { + List<WsUsers.GroupsWsResponse.Group> groups = session.wsClient().users().groups(GroupsRequest.builder() + .setLogin(userLogin) + .setOrganization(organization != null ? organization.getKey() : null) + .setQuery("Members") + .setSelected("selected") + .build()) + .getGroupsList(); + assertThat(groups).hasSize(isMember ? 1 : 0); + } + + public OrganizationService service() { + return session.wsClient().organizations(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/ProjectTester.java b/tests/src/test/java/org/sonarqube/tests/ProjectTester.java new file mode 100644 index 00000000000..781500aa8c0 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ProjectTester.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsProjects; +import org.sonarqube.ws.client.project.CreateRequest; +import org.sonarqube.ws.client.project.DeleteRequest; +import org.sonarqube.ws.client.project.ProjectsService; +import org.sonarqube.ws.client.project.SearchWsRequest; + +import static java.util.Arrays.stream; +import static java.util.Collections.singletonList; + +public class ProjectTester { + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + private final Session session; + + ProjectTester(Session session) { + this.session = session; + } + + void deleteAll() { + ProjectsService service = session.wsClient().projects(); + service.search(SearchWsRequest.builder().setQualifiers(singletonList("TRK")).build()).getComponentsList().forEach(p -> { + service.delete(DeleteRequest.builder().setKey(p.getKey()).build()); + }); + } + + @SafeVarargs + public final WsProjects.CreateWsResponse.Project generate(@Nullable Organizations.Organization organization, Consumer<CreateRequest.Builder>... populators) { + int id = ID_GENERATOR.getAndIncrement(); + CreateRequest.Builder request = CreateRequest.builder() + .setKey("key" + id) + .setName("Name " + id) + .setOrganization(organization != null ? organization.getKey() : null); + stream(populators).forEach(p -> p.accept(request)); + + return session.wsClient().projects().create(request.build()).getProject(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/QProfileTester.java b/tests/src/test/java/org/sonarqube/tests/QProfileTester.java new file mode 100644 index 00000000000..764b80d6955 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/QProfileTester.java @@ -0,0 +1,118 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.sonarqube.ws.Common; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.QualityProfiles.CreateWsResponse.QualityProfile; +import org.sonarqube.ws.Rules; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.qualityprofile.ActivateRuleWsRequest; +import org.sonarqube.ws.client.qualityprofile.CreateRequest; +import org.sonarqube.ws.client.qualityprofile.QualityProfilesService; +import org.sonarqube.ws.client.rule.SearchWsRequest; + +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class QProfileTester { + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + private final Session session; + + QProfileTester(Session session) { + this.session = session; + } + + public QualityProfilesService service() { + return session.wsClient().qualityProfiles(); + } + + @SafeVarargs + public final QualityProfile createXooProfile(Organization organization, Consumer<CreateRequest.Builder>... populators) { + int id = ID_GENERATOR.getAndIncrement(); + CreateRequest.Builder request = CreateRequest.builder() + .setOrganizationKey(organization.getKey()) + .setLanguage("xoo") + .setProfileName("Profile" + id); + stream(populators).forEach(p -> p.accept(request)); + return service().create(request.build()).getProfile(); + } + + public QProfileTester activateRule(QualityProfile profile, String ruleKey) { + return activateRule(profile.getKey(), ruleKey); + } + + public QProfileTester activateRule(String profileKey, String ruleKey) { + ActivateRuleWsRequest request = ActivateRuleWsRequest.builder() + .setProfileKey(profileKey) + .setRuleKey(ruleKey) + .build(); + service().activateRule(request); + return this; + } + + public QProfileTester deactivateRule(QualityProfile profile, String ruleKey) { + service().deactivateRule(profile.getKey(), ruleKey); + return this; + } + + public QProfileTester assertThatNumberOfActiveRulesEqualsTo(QualityProfile profile, int expectedActiveRules) { + return assertThatNumberOfActiveRulesEqualsTo(profile.getKey(), expectedActiveRules); + } + + public QProfileTester assertThatNumberOfActiveRulesEqualsTo(String profileKey, int expectedActiveRules) { + try { + List<String> facetIds = asList("active_severities", "repositories", "languages", "severities", "statuses", "types"); + SearchWsRequest request = new SearchWsRequest() + .setQProfile(profileKey) + .setActivation(true) + .setFacets(facetIds) + .setFields(singletonList("actives")); + Rules.SearchResponse response = session.wsClient().rules().search(request); + + // assume that expectedActiveRules fits in first page of results + assertThat(response.getRulesCount()).isEqualTo(expectedActiveRules); + assertThat(response.getTotal()).isEqualTo(expectedActiveRules); + assertThat(response.getActives().getActives()).as(response.toString()).hasSize(expectedActiveRules); + + // verify facets + assertThat(response.getFacets().getFacetsCount()).isEqualTo(facetIds.size()); + response.getFacets().getFacetsList().forEach(facet -> { + long total = facet.getValuesList().stream() + .mapToLong(Common.FacetValue::getCount) + .sum(); + assertThat(total).as("Facet " + facet.getProperty()).isEqualTo((long) expectedActiveRules); + }); + } catch (HttpException e) { + if (expectedActiveRules == 0 && e.code() == 404) { + // profile does not exist, do nothing + return this; + } + throw e; + } + return this; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/Session.java b/tests/src/test/java/org/sonarqube/tests/Session.java new file mode 100644 index 00000000000..6889f873b7e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Session.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import org.sonarqube.ws.client.WsClient; + +public interface Session { + + WsClient wsClient(); + + GroupTester groups(); + + OrganizationTester organizations(); + + ProjectTester projects(); + + QProfileTester qProfiles(); + + UserTester users(); + +} diff --git a/tests/src/test/java/org/sonarqube/tests/Tester.java b/tests/src/test/java/org/sonarqube/tests/Tester.java new file mode 100644 index 00000000000..f6a8f55162a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/Tester.java @@ -0,0 +1,201 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import com.sonar.orchestrator.Orchestrator; +import javax.annotation.Nullable; +import org.junit.rules.ExternalResource; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.pageobjects.Navigation; +import util.selenium.Selenese; + +import static util.ItUtils.newUserWsClient; + +/** + * This JUnit rule wraps an {@link Orchestrator} instance and provides : + * <ul> + * <li>enabling the organization feature by default</li> + * <li>clean-up of organizations between tests</li> + * <li>clean-up of users between tests</li> + * <li>clean-up of session when opening a browser (cookies, local storage)</li> + * <li>quick access to {@link WsClient} instances</li> + * <li>helpers to generate organizations and users</li> + * </ul> + * + * Recommendation is to define a {@code @Rule} instance. If not possible, then + * {@code @ClassRule} must be used through a {@link org.junit.rules.RuleChain} + * around {@link Orchestrator}. + */ +public class Tester extends ExternalResource implements Session { + + private final Orchestrator orchestrator; + + // configuration before startup + private boolean disableOrganizations = false; + + // initialized in #before() + private boolean beforeCalled = false; + private Session rootSession; + + public Tester(Orchestrator orchestrator) { + this.orchestrator = orchestrator; + } + + public Tester disableOrganizations() { + verifyNotStarted(); + disableOrganizations = true; + return this; + } + + @Override + protected void before() { + verifyNotStarted(); + rootSession = new SessionImpl(orchestrator, "admin", "admin"); + + if (!disableOrganizations) { + organizations().enableSupport(); + } + + beforeCalled = true; + } + + @Override + protected void after() { + if (!disableOrganizations) { + organizations().deleteNonGuardedOrganizations(); + } + users().deleteAll(); + projects().deleteAll(); + } + + public Session asAnonymous() { + return as(null, null); + } + + public Session as(String login) { + return as(login, login); + } + + public Session as(String login, String password) { + verifyStarted(); + return new SessionImpl(orchestrator, login, password); + } + + /** + * Open a new browser session. Cookies are deleted. + */ + public Navigation openBrowser() { + verifyStarted(); + return Navigation.create(orchestrator); + } + + /** + * @deprecated use Selenide tests with {@link #openBrowser()} + */ + @Deprecated + public Tester runHtmlTests(String... htmlTests) { + Selenese.runSelenese(orchestrator, htmlTests); + return this; + } + + private void verifyNotStarted() { + if (beforeCalled) { + throw new IllegalStateException("Orchestrator should not be already started"); + } + } + + private void verifyStarted() { + if (!beforeCalled) { + throw new IllegalStateException("Orchestrator is not started yet"); + } + } + + /** + * Web service client configured with root access + */ + @Override + public WsClient wsClient() { + verifyStarted(); + return rootSession.wsClient(); + } + + @Override + public GroupTester groups() { + return rootSession.groups(); + } + + @Override + public OrganizationTester organizations() { + return rootSession.organizations(); + } + + @Override + public ProjectTester projects() { + return rootSession.projects(); + } + + @Override + public QProfileTester qProfiles() { + return rootSession.qProfiles(); + } + + @Override + public UserTester users() { + return rootSession.users(); + } + + private static class SessionImpl implements Session { + private final WsClient client; + + private SessionImpl(Orchestrator orchestrator, @Nullable String login, @Nullable String password) { + this.client = newUserWsClient(orchestrator, login, password); + } + + @Override + public WsClient wsClient() { + return client; + } + + @Override + public GroupTester groups() { + return new GroupTester(this); + } + + @Override + public OrganizationTester organizations() { + return new OrganizationTester(this); + } + + @Override + public ProjectTester projects() { + return new ProjectTester(this); + } + + @Override + public QProfileTester qProfiles() { + return new QProfileTester(this); + } + + @Override + public UserTester users() { + return new UserTester(this); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/UserTester.java b/tests/src/test/java/org/sonarqube/tests/UserTester.java new file mode 100644 index 00000000000..9be2a5d512c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/UserTester.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.user.CreateRequest; +import org.sonarqube.ws.client.user.SearchRequest; +import org.sonarqube.ws.client.user.UsersService; +import org.sonarqube.ws.client.usergroup.AddUserWsRequest; + +import static java.util.Arrays.stream; + +public class UserTester { + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + private final Session session; + + UserTester(Session session) { + this.session = session; + } + + void deleteAll() { + session.wsClient().users().search(SearchRequest.builder().build()).getUsersList() + .stream() + .filter(u -> !"admin".equals(u.getLogin())) + .forEach(u -> { + PostRequest request = new PostRequest("api/users/deactivate").setParam("login", u.getLogin()); + session.wsClient().wsConnector().call(request); + }); + } + + @SafeVarargs + public final User generate(Consumer<CreateRequest.Builder>... populators) { + int id = ID_GENERATOR.getAndIncrement(); + String login = "login" + id; + CreateRequest.Builder request = CreateRequest.builder() + .setLogin(login) + .setPassword(login) + .setName("name" + id) + .setEmail(id + "@test.com"); + stream(populators).forEach(p -> p.accept(request)); + return service().create(request.build()).getUser(); + } + + @SafeVarargs + public final User generateAdministrator(Consumer<CreateRequest.Builder>... populators) { + User user = generate(populators); + session.wsClient().permissions().addUser(new org.sonarqube.ws.client.permission.AddUserWsRequest().setLogin(user.getLogin()).setPermission("admin")); + session.wsClient().userGroups().addUser(org.sonarqube.ws.client.usergroup.AddUserWsRequest.builder().setLogin(user.getLogin()).setName("sonar-administrators").build()); + return user; + } + + @SafeVarargs + public final User generateAdministrator(Organizations.Organization organization, Consumer<CreateRequest.Builder>... populators) { + User user = generate(populators); + session.wsClient().organizations().addMember(organization.getKey(), user.getLogin()); + session.wsClient().userGroups().addUser(AddUserWsRequest.builder() + .setOrganization(organization.getKey()) + .setLogin(user.getLogin()) + .setName("Owners") + .build()); + return user; + } + + public UsersService service() { + return session.wsClient().users(); + } + + public Optional<WsUsers.SearchWsResponse.User> getByLogin(String login) { + List<WsUsers.SearchWsResponse.User> users = session.wsClient().users().search(SearchRequest.builder().setQuery(login).build()).getUsersList(); + if (users.size() == 1) { + return Optional.of(users.get(0)); + } + return Optional.empty(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/ExtensionLifecycleTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/ExtensionLifecycleTest.java new file mode 100644 index 00000000000..09c9a18fdea --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/ExtensionLifecycleTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.MavenBuild; +import org.sonarqube.tests.Category3Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +public class ExtensionLifecycleTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Before + public void cleanup() { + orchestrator.resetData(); + } + + @Test + public void testInstantiationStrategyAndLifecycleOfBatchExtensions() { + MavenBuild build = MavenBuild.create(ItUtils.projectPom("analysis/extension-lifecycle")) + .setCleanSonarGoals() + .setProperty("extension.lifecycle", "true") + .setProperty("sonar.dynamicAnalysis", "false"); + + // Build fails if the extensions provided in the extension-lifecycle-plugin are not correctly + // managed. + orchestrator.executeBuild(build); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/FavoriteTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/FavoriteTest.java new file mode 100644 index 00000000000..f94f6aa2833 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/FavoriteTest.java @@ -0,0 +1,124 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.Favorites; +import org.sonarqube.ws.Favorites.Favorite; +import org.sonarqube.ws.WsPermissions; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.favorite.SearchRequest; +import org.sonarqube.ws.client.permission.AddProjectCreatorToTemplateWsRequest; +import org.sonarqube.ws.client.permission.RemoveProjectCreatorFromTemplateWsRequest; +import org.sonarqube.ws.client.permission.SearchTemplatesWsRequest; + +import static com.sonar.orchestrator.container.Server.ADMIN_LOGIN; +import static com.sonar.orchestrator.container.Server.ADMIN_PASSWORD; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +public class FavoriteTest { + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + private static WsClient adminWsClient; + + private static final String PROJECT_KEY = "sample"; + + @Before + public void setUp() { + orchestrator.resetData(); + } + + @After + public void tearDown() { + removeProjectCreatorPermission(); + } + + @BeforeClass + public static void classSetUp() { + adminWsClient = newAdminWsClient(orchestrator); + } + + @Test + public void project_as_favorite_when_authenticated_and_first_analysis_and_a_project_creator_permission() { + SonarScanner sampleProject = createScannerWithUserCredentials(); + addProjectCreatorPermission(); + + orchestrator.executeBuild(sampleProject); + + Favorites.SearchResponse response = adminWsClient.favorites().search(new SearchRequest()); + assertThat(response.getFavoritesList()).extracting(Favorite::getKey).contains(PROJECT_KEY); + } + + @Test + public void no_project_as_favorite_when_no_project_creator_permission() { + SonarScanner sampleProject = createScannerWithUserCredentials(); + + orchestrator.executeBuild(sampleProject); + + Favorites.SearchResponse response = adminWsClient.favorites().search(new SearchRequest()); + assertThat(response.getFavoritesList()).extracting(Favorite::getKey).doesNotContain(PROJECT_KEY); + } + + @Test + public void no_project_as_favorite_when_second_analysis() { + SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-sample")); + orchestrator.executeBuild(sampleProject); + sampleProject = createScannerWithUserCredentials(); + addProjectCreatorPermission(); + + orchestrator.executeBuild(sampleProject); + + Favorites.SearchResponse response = adminWsClient.favorites().search(new SearchRequest()); + assertThat(response.getFavoritesList()).extracting(Favorite::getKey).doesNotContain(PROJECT_KEY); + } + + private static SonarScanner createScannerWithUserCredentials() { + return SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.login", ADMIN_LOGIN) + .setProperty("sonar.password", ADMIN_PASSWORD); + } + + private void addProjectCreatorPermission() { + WsPermissions.SearchTemplatesWsResponse permissionTemplates = adminWsClient.permissions().searchTemplates(new SearchTemplatesWsRequest()); + assertThat(permissionTemplates.getDefaultTemplatesCount()).isEqualTo(1); + adminWsClient.permissions().addProjectCreatorToTemplate(AddProjectCreatorToTemplateWsRequest.builder() + .setTemplateId(permissionTemplates.getDefaultTemplates(0).getTemplateId()) + .setPermission("admin") + .build()); + } + + private void removeProjectCreatorPermission() { + WsPermissions.SearchTemplatesWsResponse permissionTemplates = adminWsClient.permissions().searchTemplates(new SearchTemplatesWsRequest()); + assertThat(permissionTemplates.getDefaultTemplatesCount()).isEqualTo(1); + adminWsClient.permissions().removeProjectCreatorFromTemplate(RemoveProjectCreatorFromTemplateWsRequest.builder() + .setTemplateId(permissionTemplates.getDefaultTemplates(0).getTemplateId()) + .setPermission("admin") + .build()); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/FileExclusionsTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/FileExclusionsTest.java new file mode 100644 index 00000000000..c5e11147036 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/FileExclusionsTest.java @@ -0,0 +1,139 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsComponents.Component; +import org.sonarqube.ws.client.component.TreeWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newWsClient; + +public class FileExclusionsTest { + static final String PROJECT = "exclusions"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Before + public void resetData() { + orchestrator.resetData(); + } + + @Test + public void exclude_source_files() { + scan( + "sonar.global.exclusions", "**/*Ignore*.xoo", + "sonar.exclusions", "**/*Exclude*.xoo,src/main/xoo/org/sonar/tests/packageToExclude/**", + "sonar.test.exclusions", "**/ClassTwoTest.xoo"); + + Map<String, Double> measures = getMeasuresAsDouble("ncloc", "files", "directories"); + assertThat(measures.get("files").intValue()).isEqualTo(4); + assertThat(measures.get("ncloc").intValue()).isEqualTo(60); + assertThat(measures.get("directories").intValue()).isEqualTo(2); + } + + /** + * SONAR-2444 / SONAR-3758 + */ + @Test + public void exclude_test_files() { + scan( + "sonar.global.exclusions", "**/*Ignore*.xoo", + "sonar.exclusions", "**/*Exclude*.xoo,org/sonar/tests/packageToExclude/**", + "sonar.test.exclusions", "**/ClassTwoTest.xoo"); + + List<Component> testFiles = getComponents("UTS"); + assertThat(testFiles).hasSize(2); + assertThat(testFiles).extracting(Component::getName).doesNotContain("ClassTwoTest.xoo"); + } + + /** + * SONAR-1896 + */ + @Test + public void include_source_files() { + scan( + "sonar.dynamicAnalysis", "false", + "sonar.inclusions", "**/*One.xoo,**/*Two.xoo"); + + assertThat(getMeasuresAsDouble("files").get("files")).isEqualTo(2); + + List<Component> sourceFiles = getComponents("FIL"); + assertThat(sourceFiles).hasSize(2); + assertThat(sourceFiles).extracting(Component::getName).containsOnly("ClassOne.xoo", "ClassTwo.xoo"); + } + + /** + * SONAR-1896 + */ + @Test + public void include_test_files() { + scan("sonar.test.inclusions", "src/test/xoo/**/*One*.xoo,src/test/xoo/**/*Two*.xoo"); + + assertThat(getMeasuresAsDouble("tests").get("tests")).isEqualTo(2); + + List<Component> testFiles = getComponents("UTS"); + assertThat(testFiles).hasSize(2); + assertThat(testFiles).extracting(Component::getName).containsOnly("ClassOneTest.xoo", "ClassTwoTest.xoo"); + } + + /** + * SONAR-2760 + */ + @Test + public void include_and_exclude_files_by_absolute_path() { + scan( + // includes everything except ClassOnDefaultPackage + "sonar.inclusions", "file:**/src/main/xoo/org/**/*.xoo", + + // exclude ClassThree and ClassToExclude + "sonar.exclusions", "file:**/src/main/xoo/org/**/packageToExclude/*.xoo,file:**/src/main/xoo/org/**/*Exclude.xoo"); + + List<Component> sourceFiles = getComponents("FIL"); + assertThat(sourceFiles).hasSize(4); + assertThat(sourceFiles).extracting(Component::getName).containsOnly("ClassOne.xoo", "ClassToIgnoreGlobally.xoo", "ClassTwo.xoo", "NoSonarComment.xoo"); + } + + static Map<String, Double> getMeasuresAsDouble(String... metricKeys) { + return getMeasuresAsDoubleByMetricKey(orchestrator, PROJECT, metricKeys); + } + + private void scan(String... properties) { + SonarScanner build = SonarScanner + .create(ItUtils.projectDir("exclusions/exclusions")) + .setProperties(properties); + orchestrator.executeBuild(build); + } + + public static List<Component> getComponents(String qualifier) { + return newWsClient(orchestrator).components().tree(new TreeWsRequest().setBaseComponentKey(PROJECT).setQualifiers(singletonList(qualifier))).getComponentsList(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/IssueExclusionsTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/IssueExclusionsTest.java new file mode 100644 index 00000000000..615a5a2aeb6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/IssueExclusionsTest.java @@ -0,0 +1,263 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.Map; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; + +public class IssueExclusionsTest { + + private static final String PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-exclusions"; + private static final String PROJECT_DIR = "exclusions/xoo-multi-modules"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Before + public void resetData() { + orchestrator.resetData(); + } + + @Test + public void should_not_exclude_anything() { + scan(); + + checkIssueCountBySeverity(67, 2, 57, 4, 0, 4); + } + + @Test + public void should_ignore_all_files() { + scan( + "sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.resourceKey", "**/*.xoo", + "sonar.issue.ignore.multicriteria.1.ruleKey", "*"); + + checkIssueCountBySeverity(4, 0, 0, 0, 0, 4); + } + + @Test + public void should_enforce_only_on_one_file() { + scan( + "sonar.issue.enforce.multicriteria", "1", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/HelloA1.xoo", + "sonar.issue.enforce.multicriteria.1.ruleKey", "*"); + + checkIssueCountBySeverity( + 1 /* tag */ + 18 /* lines in HelloA1.xoo */ + 1 /* file */, + 0 + 1, + 0 + 18, + 0 + 1, + 0, + 0); + } + + @Test + public void should_enforce_on_two_files_with_same_rule() { + scan( + "sonar.issue.enforce.multicriteria", "1,2", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/HelloA1.xoo", + "sonar.issue.enforce.multicriteria.1.ruleKey", "*", + "sonar.issue.enforce.multicriteria.2.resourceKey", "**/HelloA2.xoo", + "sonar.issue.enforce.multicriteria.2.ruleKey", "*"); + + checkIssueCountBySeverity( + 2 /* tags */ + 18 /* lines in HelloA1.xoo */ + 15 /* lines in HelloA2.xoo */ + 2 /* files */, + 0 + 2, + 0 + 18 + 15, + 0 + 2, + 0, + 0); + } + + @Test + public void should_enforce_on_two_files_with_different_rule() { + scan( + "sonar.issue.enforce.multicriteria", "1,2", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/HelloA1.xoo", + "sonar.issue.enforce.multicriteria.1.ruleKey", "xoo:OneIssuePerLine", + "sonar.issue.enforce.multicriteria.2.resourceKey", "**/HelloA2.xoo", + "sonar.issue.enforce.multicriteria.2.ruleKey", "xoo:HasTag"); + + checkIssueCountBySeverity( + 1 /* tag in HelloA2 */ + 18 /* lines in HelloA1.xoo */ + 4 /* files */ + 4 /* modules */, + 0 + 1, + 0 + 18, + 4, + 0, + 4); + } + + @Test + public void should_ignore_files_with_regexp() { + scan( + "sonar.issue.ignore.allfile", "1", + "sonar.issue.ignore.allfile.1.fileRegexp", "EXTERMINATE-ALL-ISSUES"); + + checkIssueCountBySeverity( + 67 - 1 /* tag */ - 18 /* lines in HelloA1.xoo */ - 1 /* file */, + 2 - 1, + 57 - 18, + 4 - 1, + 0, + 4); + } + + @Test + public void should_ignore_block_with_regexp() { + scan( + "sonar.issue.ignore.block", "1", + "sonar.issue.ignore.block.1.beginBlockRegexp", "MUTE-SONAR", + "sonar.issue.ignore.block.1.endBlockRegexp", "UNMUTE-SONAR"); + + checkIssueCountBySeverity( + 67 - 1 /* tag */ - 5 /* lines in HelloA2.xoo */, + 2 - 1, + 57 - 5, + 4, + 0, + 4); + } + + @Test + public void should_ignore_to_end_of_file() { + scan( + "sonar.issue.ignore.block", "1", + "sonar.issue.ignore.block.1.beginBlockRegexp", "MUTE-SONAR", + "sonar.issue.ignore.block.1.endBlockRegexp", ""); + + checkIssueCountBySeverity( + 67 - 1 /* tag */ - 7 /* remaining lines in HelloA2.xoo */, + 2 - 1, + 57 - 7, + 4, + 0, + 4); + } + + @Test + public void should_ignore_one_per_line_on_single_package() { + scan( + "sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.resourceKey", "**/com/sonar/it/samples/modules/a1/*", + "sonar.issue.ignore.multicriteria.1.ruleKey", "xoo:OneIssuePerLine"); + + checkIssueCountBySeverity( + 67 - 18 /* lines in HelloA1.xoo */, + 2, + 57 - 18, + 4, + 0, + 4); + } + + @Test + public void should_apply_exclusions_from_multiple_sources() { + scan( + "sonar.issue.ignore.allfile", "1", + "sonar.issue.ignore.allfile.1.fileRegexp", "EXTERMINATE-ALL-ISSUES", + "sonar.issue.ignore.block", "1", + "sonar.issue.ignore.block.1.beginBlockRegexp", "MUTE-SONAR", + "sonar.issue.ignore.block.1.endBlockRegexp", "UNMUTE-SONAR", + "sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.resourceKey", "**/com/sonar/it/samples/modules/b1/*", + "sonar.issue.ignore.multicriteria.1.ruleKey", "xoo:OneIssuePerLine"); + + checkIssueCountBySeverity( + 67 - 1 /* tag in HelloA1.xoo */ - 1 /* tag in HelloA2.xoo */ + - 18 /* lines in HelloA1.xoo */ - 5 /* lines in HelloA2.xoo */ - 12 /* lines in HelloB1.xoo */ + - 1 /* HelloA1.xoo file */, + 0, + 57 - 18 - 5 - 12, + 4 - 1, + 0, + 4); + } + + @Test + public void should_log_missing_resource_key() { + checkAnalysisFails( + "sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.resourceKey", "", + "sonar.issue.ignore.multicriteria.1.ruleKey", "*"); + } + + @Test + public void should_log_missing_rule_key() { + checkAnalysisFails( + "sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.resourceKey", "*", + "sonar.issue.ignore.multicriteria.1.ruleKey", ""); + } + + @Test + public void should_log_missing_block_start() { + checkAnalysisFails( + "sonar.issue.ignore.block", "1", + "sonar.issue.ignore.block.1.beginBlockRegexp", "", + "sonar.issue.ignore.block.1.endBlockRegexp", "UNMUTE-SONAR"); + } + + @Test + public void should_log_missing_whole_file_regexp() { + checkAnalysisFails( + "sonar.issue.ignore.allfile", "1", + "sonar.issue.ignore.allfile.1.fileRegexp", ""); + } + + protected BuildResult scan(String... properties) { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/exclusions/IssueExclusionsTest/with-many-rules.xml")); + orchestrator.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-exclusions", + "Sonar :: Integration Tests :: Multi-modules With Exclusions"); + orchestrator.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-exclusions", "xoo", "with-many-rules"); + + SonarScanner scan = SonarScanner.create(ItUtils.projectDir(PROJECT_DIR)) + .setProperty("sonar.cpd.exclusions", "**/*") + .setProperties(properties) + .setProperty("sonar.verbose", "true"); + return orchestrator.executeBuildQuietly(scan); + } + + private void checkIssueCountBySeverity(int total, int taggedXoo, int perLine, int perFile, int blocker, int perModule) { + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, PROJECT_KEY, "violations", "info_violations", "minor_violations", "major_violations", + "blocker_violations", "critical_violations"); + assertThat(measures.get("violations").intValue()).isEqualTo(total); + assertThat(measures.get("info_violations").intValue()).isEqualTo(taggedXoo); // Has tag 'xoo' + assertThat(measures.get("minor_violations").intValue()).isEqualTo(perLine); // One per line + assertThat(measures.get("major_violations").intValue()).isEqualTo(perFile); // One per file + assertThat(measures.get("blocker_violations").intValue()).isEqualTo(blocker); + assertThat(measures.get("critical_violations").intValue()).isEqualTo(perModule); // One per module + } + + private void checkAnalysisFails(String... properties) { + BuildResult buildResult = scan(properties); + assertThat(buildResult.getStatus()).isNotEqualTo(0); + assertThat(buildResult.getLogs().indexOf("IllegalStateException")).isGreaterThan(0); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/IssueJsonReportTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/IssueJsonReportTest.java new file mode 100644 index 00000000000..13ccd9ac4c5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/IssueJsonReportTest.java @@ -0,0 +1,313 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.locator.FileLocation; +import com.sonar.orchestrator.locator.ResourceLocation; +import org.sonarqube.tests.Category3Suite; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.skyscreamer.jsonassert.JSONAssert; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newWsClient; + +public class IssueJsonReportTest { + + private static final String SONAR_VERSION_PLACEHOLDER = "<SONAR_VERSION>"; + private static final String RESOURCE_PATH = "/analysis/IssueJsonReportTest/"; + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void resetData() { + orchestrator.resetData(); + } + + @Test + public void issue_line() throws IOException { + ItUtils.restoreProfile(orchestrator, getClass().getResource(RESOURCE_PATH + "one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + File projectDir = ItUtils.projectDir("shared/xoo-sample"); + SonarScanner runner = SonarScanner.create(projectDir, + "sonar.analysis.mode", "issues", + "sonar.verbose", "true", + "sonar.report.export.path", "sonar-report.json"); + BuildResult result = orchestrator.executeBuild(runner); + assertThat(ItUtils.countIssuesInJsonReport(result, true)).isEqualTo(17); + + JSONObject obj = ItUtils.getJSONReport(result); + JSONArray issues = (JSONArray) obj.get("issues"); + for (Object issue : issues) { + JSONObject jsonIssue = (JSONObject) issue; + assertThat(jsonIssue.get("startLine")).isNotNull(); + assertThat(jsonIssue.get("line")).isEqualTo(jsonIssue.get("startLine")); + assertThat(jsonIssue.get("endLine")).isEqualTo(jsonIssue.get("startLine")); + + assertThat(jsonIssue.get("endOffset")).isNotNull(); + assertThat(jsonIssue.get("startOffset")).isNotNull(); + } + + List<Long> lineNumbers = new ArrayList<>(16); + for (long i = 1L; i < 18; i++) { + lineNumbers.add(i); + } + assertThat(issues).extracting("startLine").containsAll(lineNumbers); + assertThat(issues).extracting("endLine").containsAll(lineNumbers); + } + + @Test + public void precise_issue_location() throws IOException { + ItUtils.restoreProfile(orchestrator, getClass().getResource(RESOURCE_PATH + "multiline.xml")); + orchestrator.getServer().provisionProject("sample-multiline", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample-multiline", "xoo", "multiline"); + + File projectDir = ItUtils.projectDir("shared/xoo-precise-issues"); + SonarScanner runner = SonarScanner.create(projectDir, + "sonar.analysis.mode", "issues", + "sonar.verbose", "true", + "sonar.report.export.path", "sonar-report.json"); + BuildResult result = orchestrator.executeBuild(runner); + assertThat(ItUtils.countIssuesInJsonReport(result, true)).isEqualTo(2); + + JSONObject obj = ItUtils.getJSONReport(result); + JSONArray issues = (JSONArray) obj.get("issues"); + + JSONObject issue1 = (JSONObject) issues.get(0); + JSONObject issue2 = (JSONObject) issues.get(1); + + assertThat(issue1.get("startLine")).isIn(6L); + assertThat(issue1.get("line")).isIn(6L); + assertThat(issue1.get("endLine")).isIn(6L); + assertThat(issue1.get("startOffset")).isIn(27L); + assertThat(issue1.get("endOffset")).isIn(32L); + + assertThat(issue2.get("startLine")).isIn(9L); + assertThat(issue2.get("line")).isIn(9L); + assertThat(issue2.get("endLine")).isIn(15L); + assertThat(issue2.get("startOffset")).isIn(20L); + assertThat(issue2.get("endOffset")).isIn(2L); + + } + + @Test + public void test_json_report_no_server_analysis() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource(RESOURCE_PATH + "one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "tracking"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + File projectDir = ItUtils.projectDir("analysis/tracking/v1"); + SonarScanner issuesModeScan = SonarScanner.create(projectDir) + .setProperty("sonar.analysis.mode", "issues") + .setProperty("sonar.userHome", temp.newFolder().getAbsolutePath()) + .setProperty("sonar.report.export.path", "sonar-report.json") + .setProperty("sonar.projectDate", "2013-05-02"); + orchestrator.executeBuild(issuesModeScan); + + File report = new File(projectDir, ".sonar/sonar-report.json"); + assertThat(report).isFile().exists(); + + String json = sanitize(FileUtils.readFileToString(report)); + String expectedJson = expected("no-server-analysis.json"); + JSONAssert.assertEquals(expectedJson, json, false); + } + + @Test + public void test_json_report() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource(RESOURCE_PATH + "one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "tracking"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + SonarScanner scan = SonarScanner.create(ItUtils.projectDir("analysis/tracking/v1")) + .setProperty("sonar.projectDate", "2013-05-01"); + orchestrator.executeBuild(scan); + + // Issues mode scan -> 2 new issues and 13 existing issues + File projectDir = ItUtils.projectDir("analysis/tracking/v2"); + SonarScanner issuesModeScan = SonarScanner.create(projectDir) + .setProperty("sonar.analysis.mode", "issues") + .setProperty("sonar.userHome", temp.newFolder().getAbsolutePath()) + .setProperty("sonar.report.export.path", "sonar-report.json") + .setProperty("sonar.projectDate", "2013-05-02"); + orchestrator.executeBuild(issuesModeScan); + + File report = new File(projectDir, ".sonar/sonar-report.json"); + assertThat(report).isFile().exists(); + + String json = sanitize(FileUtils.readFileToString(report)); + String expectedJson = expected("report-on-single-module.json"); + JSONAssert.assertEquals(expectedJson, json, false); + } + + @Test + public void test_json_report_on_branch() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource(getResource("one-issue-per-line.xml").getPath())); + orchestrator.getServer().provisionProject("sample:mybranch", "Sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample:mybranch", "xoo", "one-issue-per-line"); + + SonarScanner scan = SonarScanner.create(ItUtils.projectDir("analysis/tracking/v1")) + .setProperty("sonar.projectDate", "2013-05-01") + .setProperty("sonar.branch", "mybranch"); + orchestrator.executeBuild(scan); + + // issues mode scan -> 2 new issues and 13 existing issues + File projectDir = ItUtils.projectDir("analysis/tracking/v2"); + SonarScanner issuesModeScan = SonarScanner.create(projectDir) + .setProperty("sonar.analysis.mode", "issues") + .setProperty("sonar.userHome", temp.newFolder().getAbsolutePath()) + .setProperty("sonar.report.export.path", "sonar-report.json") + .setProperty("sonar.issuesReport.console.enable", "true") + .setProperty("sonar.projectDate", "2013-05-02") + .setProperty("sonar.verbose", "true") + .setProperty("sonar.branch", "mybranch"); + orchestrator.executeBuild(issuesModeScan); + + File report = new File(projectDir, ".sonar/sonar-report.json"); + assertThat(report).isFile().exists(); + + String json = sanitize(FileUtils.readFileToString(report)); + String expectedJson = expected("report-on-single-module-branch.json"); + JSONAssert.assertEquals(expectedJson, json, false); + } + + /** + * Multi-modules project but Eclipse scans only a single module + */ + @Test + public void test_json_report_on_sub_module() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource(RESOURCE_PATH + "one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "Multi-module sample"); + orchestrator.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "one-issue-per-line"); + + File rootDir = ItUtils.projectDir("shared/xoo-multi-modules-sample"); + SonarScanner scan = SonarScanner.create(rootDir) + .setProperty("sonar.projectDate", "2013-05-01"); + orchestrator.executeBuild(scan); + + // Issues mode scan on a module -> no new issues + File moduleDir = ItUtils.projectDir("shared/xoo-multi-modules-sample/module_a/module_a1"); + SonarScanner issuesModeScan = SonarScanner.create(moduleDir) + .setProperty("sonar.projectKey", "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1") + .setProperty("sonar.projectVersion", "1.0-SNAPSHOT") + .setProperty("sonar.projectName", "ModuleA1") + .setProperty("sonar.sources", "src/main/xoo") + .setProperty("sonar.language", "xoo") + .setProperty("sonar.analysis.mode", "issues") + .setProperty("sonar.userHome", temp.newFolder().getAbsolutePath()) + .setProperty("sonar.report.export.path", "sonar-report.json") + .setProperty("sonar.projectDate", "2013-05-02"); + orchestrator.executeBuild(issuesModeScan); + + File report = new File(moduleDir, ".sonar/sonar-report.json"); + assertThat(report).isFile().exists(); + + String json = sanitize(FileUtils.readFileToString(report)); + // SONAR-5218 All issues are updated as their root project id has changed (it's now the sub module) + String expectedJson = expected("report-on-sub-module.json"); + JSONAssert.assertEquals(expectedJson, json, false); + } + + /** + * Multi-modules project + */ + @Test + public void test_json_report_on_root_module() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource(RESOURCE_PATH + "one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "Sonar :: Integration Tests :: Multi-modules Sample"); + orchestrator.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "one-issue-per-line"); + + File rootDir = ItUtils.projectDir("shared/xoo-multi-modules-sample"); + SonarScanner scan = SonarScanner.create(rootDir) + .setProperty("sonar.projectDate", "2013-05-01"); + orchestrator.executeBuild(scan); + + // issues mode scan -> no new issues + SonarScanner issuesModeScan = SonarScanner.create(rootDir) + .setProperty("sonar.analysis.mode", "issues") + .setProperty("sonar.userHome", temp.newFolder().getAbsolutePath()) + .setProperty("sonar.report.export.path", "sonar-report.json") + .setProperty("sonar.projectDate", "2013-05-02"); + orchestrator.executeBuild(issuesModeScan); + + File report = new File(rootDir, ".sonar/sonar-report.json"); + assertThat(report).isFile().exists(); + + String json = sanitize(FileUtils.readFileToString(report)); + String expectedJson = expected("report-on-root-module.json"); + JSONAssert.assertEquals(expectedJson, json, false); + } + + private String expected(String path) throws IOException { + try (WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/server/version"))) { + String version = response.content(); + + return sanitize(IOUtils.toString(getResourceInputStream(path))) + .replaceAll(Pattern.quote(SONAR_VERSION_PLACEHOLDER), version); + } + } + + @Test + public void issueSanityCheck() { + assertThat(sanitize("s\"0150F1EBDB8E000003\"f")).isEqualTo("s<ISSUE_KEY>f"); + } + + private static String sanitize(String s) { + // sanitize issue uuid keys + s = s.replaceAll("\"[a-zA-Z_0-9\\-]{15,20}\"", "<ISSUE_KEY>"); + + return ItUtils.sanitizeTimezones(s); + } + + private InputStream getResourceInputStream(String resource) throws FileNotFoundException { + ResourceLocation res = getResource(resource); + return getClass().getResourceAsStream(res.getPath()); + } + + private ResourceLocation getResource(String resource) { + return FileLocation.ofClasspath(RESOURCE_PATH + resource); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/IssuesModeTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/IssuesModeTest.java new file mode 100644 index 00000000000..527f0970a7b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/IssuesModeTest.java @@ -0,0 +1,461 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.google.common.collect.Maps; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildFailureException; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.build.SonarScannerInstaller; +import com.sonar.orchestrator.config.FileSystem; +import com.sonar.orchestrator.version.Version; +import org.sonarqube.tests.Category3Suite; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.ObjectUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.wsclient.SonarClient; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueClient; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.issue.Issues; +import org.sonar.wsclient.user.UserParameters; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.getComponent; + +public class IssuesModeTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void deleteData() throws IOException { + orchestrator.resetData(); + } + + @Test + public void issues_analysis_on_new_project() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + SonarScanner runner = configureRunnerIssues("shared/xoo-sample", null, "sonar.verbose", "true"); + BuildResult result = orchestrator.executeBuild(runner); + assertThat(ItUtils.countIssuesInJsonReport(result, true)).isEqualTo(17); + } + + @Test + public void invalid_incremental_mode() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + SonarScanner runner = configureRunner("shared/xoo-sample"); + runner.setProperty("sonar.analysis.mode", "incremental"); + + thrown.expect(BuildFailureException.class); + BuildResult res = orchestrator.executeBuild(runner); + + assertThat(res.getLogs()).contains("Invalid analysis mode: incremental. This mode was removed in SonarQube 5.2"); + } + + @Test + public void project_key_with_slash() throws IOException { + restoreProfile("one-issue-per-line.xml"); + setDefaultQualityProfile("xoo", "one-issue-per-line"); + + SonarScanner runner = configureRunner("shared/xoo-sample"); + runner.setProjectKey("sample/project"); + runner.setProperty("sonar.analysis.mode", "issues"); + BuildResult result = orchestrator.executeBuild(runner); + assertThat(ItUtils.countIssuesInJsonReport(result, true)).isEqualTo(17); + } + + // SONAR-6931 + @Test + public void only_scan_changed_files_qps() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + SonarScanner runner = configureRunner("shared/xoo-sample", "sonar.verbose", "true"); + BuildResult result = orchestrator.executeBuild(runner); + List<Issue> serverIssues = ItUtils.getAllServerIssues(orchestrator); + for (Issue i : serverIssues) { + assertThat(i.status()).isEqualTo("OPEN"); + } + assertThat(serverIssues).hasSize(17); + + // change quality profile + restoreProfile("with-many-rules.xml"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "with-many-rules"); + + // do it again, scanning nothing (all files should be unchanged) + runner = configureRunnerIssues("shared/xoo-sample", null, + "sonar.verbose", "true"); + result = orchestrator.executeBuild(runner); + assertThat(result.getLogs()).contains("Scanning only changed files"); + assertThat(result.getLogs()).contains("'One Issue Per Line' skipped because there is no related file in current project"); + ItUtils.assertIssuesInJsonReport(result, 0, 0, 17); + } + + // SONAR-6931 + @Test + public void only_scan_changed_files_transitions() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + SonarScanner runner = configureRunner("shared/xoo-sample", "sonar.verbose", "true"); + BuildResult result = orchestrator.executeBuild(runner); + List<Issue> serverIssues = ItUtils.getAllServerIssues(orchestrator); + for (Issue i : serverIssues) { + assertThat(i.status()).isEqualTo("OPEN"); + } + assertThat(serverIssues).hasSize(17); + + // resolve 2 issues + IssueClient issueClient = orchestrator.getServer().wsClient("admin", "admin").issueClient(); + issueClient.doTransition(serverIssues.get(0).key(), "wontfix"); + issueClient.doTransition(serverIssues.get(1).key(), "wontfix"); + + // do it again, scanning nothing (all files should be unchanged) + runner = configureRunnerIssues("shared/xoo-sample", null, + "sonar.verbose", "true"); + result = orchestrator.executeBuild(runner); + assertThat(result.getLogs()).contains("Scanning only changed files"); + assertThat(result.getLogs()).contains("'One Issue Per Line' skipped because there is no related file in current project"); + ItUtils.assertIssuesInJsonReport(result, 0, 0, 15); + } + + // SONAR-6931 + @Test + public void only_scan_changed_files_on_change() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + SonarScanner runner = configureRunner("shared/xoo-sample", "sonar.verbose", "true"); + BuildResult result = orchestrator.executeBuild(runner); + + // change QP + restoreProfile("with-many-rules.xml"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "with-many-rules"); + + // now change file hash in a temporary location + File tmpProjectDir = temp.newFolder(); + FileUtils.copyDirectory(ItUtils.projectDir("shared/xoo-sample"), tmpProjectDir); + File srcFile = new File(tmpProjectDir, "src/main/xoo/sample/Sample.xoo"); + FileUtils.write(srcFile, "\n", StandardCharsets.UTF_8, true); + + // scan again, with different QP + runner = SonarScanner.create(tmpProjectDir, + "sonar.working.directory", temp.newFolder().getAbsolutePath(), + "sonar.analysis.mode", "issues", + "sonar.report.export.path", "sonar-report.json", + "sonar.userHome", temp.newFolder().getAbsolutePath(), + "sonar.verbose", "true", + "sonar.scanChangedFilesOnly", "true"); + result = orchestrator.executeBuild(runner); + assertThat(result.getLogs()).contains("Scanning only changed files"); + assertThat(result.getLogs()).doesNotContain("'One Issue Per Line' skipped because there is no related file in current project"); + ItUtils.assertIssuesInJsonReport(result, 3, 0, 17); + } + + // SONAR-8518 + @Test + public void should_support_sonar_profile_prop() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "empty"); + + SonarScanner runner = configureRunner("shared/xoo-sample", + "sonar.verbose", "true", + "sonar.analysis.mode", "issues", + "sonar.profile", "one-issue-per-line"); + BuildResult result = orchestrator.executeBuild(runner); + ItUtils.assertIssuesInJsonReport(result, 17, 0, 0); + } + + // SONAR-5715 + @Test + public void test_issues_mode_on_project_with_space_in_filename() throws IOException { + restoreProfile("with-many-rules.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample-with-spaces"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "with-many-rules"); + + SonarScanner runner = configureRunner("analysis/xoo-sample-with-spaces/v2"); + orchestrator.executeBuild(runner); + assertThat(getComponent(orchestrator, "sample:my sources/main/xoo/sample/My Sample.xoo")).isNotNull(); + + runner = configureRunnerIssues("analysis/xoo-sample-with-spaces/v2", null); + BuildResult result = orchestrator.executeBuild(runner); + // Analysis is not persisted in database + assertThat(getComponent(orchestrator, "com.sonarsource.it.samples:simple-sample")).isNull(); + assertThat(result.getLogs()).contains("Issues"); + assertThat(result.getLogs()).contains("ANALYSIS SUCCESSFUL"); + } + + @Test + public void should_not_fail_on_resources_that_have_existed_before() throws IOException { + restoreProfile("with-many-rules.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-history"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "with-many-rules"); + + // First real scan with source + SonarScanner runner = configureRunner("shared/xoo-history-v2"); + orchestrator.executeBuild(runner); + assertThat(getComponent(orchestrator, "sample:src/main/xoo/sample/ClassAdded.xoo")).isNotNull(); + + // Second scan should remove ClassAdded.xoo + runner = configureRunner("shared/xoo-history-v1"); + orchestrator.executeBuild(runner); + assertThat(getComponent(orchestrator, "sample:src/main/xoo/sample/ClassAdded.xoo")).isNull(); + + // Re-add ClassAdded.xoo in local workspace + runner = configureRunnerIssues("shared/xoo-history-v2", null); + BuildResult result = orchestrator.executeBuild(runner); + + assertThat(getComponent(orchestrator, "sample:src/main/xoo/sample/ClassAdded.xoo")).isNull(); + assertThat(result.getLogs()).contains("Issues"); + assertThat(result.getLogs()).contains("ANALYSIS SUCCESSFUL"); + } + + @Test + public void should_fail_if_plugin_access_secured_properties() throws IOException { + // Test access from task (ie BatchSettings) + SonarScanner runner = configureRunnerIssues("shared/xoo-sample", null, + "accessSecuredFromTask", "true"); + BuildResult result = orchestrator.executeBuildQuietly(runner); + + assertThat(result.getLogs()).contains("Access to the secured property 'foo.bar.secured' is not possible in issues mode. " + + "The SonarQube plugin which requires this property must be deactivated in issues mode."); + + // Test access from sensor (ie ModuleSettings) + runner = configureRunnerIssues("shared/xoo-sample", null, + "accessSecuredFromSensor", "true"); + result = orchestrator.executeBuildQuietly(runner); + + assertThat(result.getLogs()).contains("Access to the secured property 'foo.bar.secured' is not possible in issues mode. " + + "The SonarQube plugin which requires this property must be deactivated in issues mode."); + } + + // SONAR-4602 + @Test + public void no_issues_mode_cache_by_default() throws Exception { + File homeDir = runFirstAnalysisAndFlagIssueAsWontFix(); + + // Second issues mode using same cache dir but cache disabled by default + SonarScanner runner = configureRunnerIssues("shared/xoo-sample", homeDir); + BuildResult result = orchestrator.executeBuild(runner); + + // False positive is not returned + assertThat(ItUtils.countIssuesInJsonReport(result, false)).isEqualTo(16); + } + + private File runFirstAnalysisAndFlagIssueAsWontFix() throws IOException { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + // First run (publish mode) + SonarScanner runner = configureRunner("shared/xoo-sample"); + orchestrator.executeBuild(runner); + + // First issues mode + File homeDir = temp.newFolder(); + runner = configureRunnerIssues("shared/xoo-sample", homeDir); + BuildResult result = orchestrator.executeBuild(runner); + + // 17 issues + assertThat(ItUtils.countIssuesInJsonReport(result, false)).isEqualTo(17); + + // Flag one issue as false positive + JSONObject obj = ItUtils.getJSONReport(result); + String key = ((JSONObject) ((JSONArray) obj.get("issues")).get(0)).get("key").toString(); + orchestrator.getServer().adminWsClient().issueClient().doTransition(key, "falsepositive"); + return homeDir; + } + + // SONAR-6522 + @Test + public void load_user_name_in_json_report() throws Exception { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + // First run (publish mode) + SonarScanner runner = configureRunner("shared/xoo-sample"); + orchestrator.executeBuild(runner); + + SonarClient client = orchestrator.getServer().adminWsClient(); + + Issues issues = client.issueClient().find(IssueQuery.create()); + Issue issue = issues.list().get(0); + + UserParameters creationParameters = UserParameters.create().login("julien").name("Julien H") + .password("password").passwordConfirmation("password"); + client.userClient().create(creationParameters); + + // Assign issue + client.issueClient().assign(issue.key(), "julien"); + + // Issues + runner = configureRunnerIssues("shared/xoo-sample", null, "sonar.login", "julien", "sonar.password", "password"); + BuildResult result = orchestrator.executeBuild(runner); + + JSONObject obj = ItUtils.getJSONReport(result); + + Map<String, String> userNameByLogin = Maps.newHashMap(); + final JSONArray users = (JSONArray) obj.get("users"); + if (users != null) { + for (Object user : users) { + String login = ObjectUtils.toString(((JSONObject) user).get("login")); + String name = ObjectUtils.toString(((JSONObject) user).get("name")); + userNameByLogin.put(login, name); + } + } + assertThat(userNameByLogin.get("julien")).isEqualTo("julien"); + + for (Object issueJson : (JSONArray) obj.get("issues")) { + JSONObject jsonObject = (JSONObject) issueJson; + if (issue.key().equals(jsonObject.get("key"))) { + assertThat(jsonObject.get("assignee")).isEqualTo("julien"); + return; + } + } + fail("Issue not found"); + } + + @Test + public void concurrent_issue_mode_on_existing_project() throws Exception { + restoreProfile("one-issue-per-line.xml"); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + // use same working dir, because lock file is in it + String workDirPath = temp.newFolder().getAbsolutePath(); + SonarScanner runner = configureRunner("shared/xoo-sample", + "sonar.working.directory", workDirPath); + + orchestrator.executeBuild(runner); + + runConcurrentIssues(workDirPath); + } + + private void runConcurrentIssues(final String workDirPath) throws Exception { + // Install sonar-runner in advance to avoid concurrent unzip issues + FileSystem fileSystem = orchestrator.getConfiguration().fileSystem(); + new SonarScannerInstaller(fileSystem).install(Version.create(SonarScanner.DEFAULT_SCANNER_VERSION), fileSystem.workspace(), true); + final int nThreads = 3; + ExecutorService executorService = Executors.newFixedThreadPool(nThreads); + List<Callable<BuildResult>> tasks = new ArrayList<>(); + final File homeDir = temp.newFolder(); + for (int i = 0; i < nThreads; i++) { + tasks.add(new Callable<BuildResult>() { + + public BuildResult call() throws Exception { + SonarScanner runner = configureRunnerIssues("shared/xoo-sample", homeDir, + "sonar.it.enableWaitingSensor", "true", + "sonar.working.directory", workDirPath); + return orchestrator.executeBuild(runner); + } + }); + } + + boolean expectedError = false; + for (Future<BuildResult> result : executorService.invokeAll(tasks)) { + try { + result.get(); + } catch (ExecutionException e) { + if (e.getCause() instanceof BuildFailureException) { + BuildFailureException bfe = (BuildFailureException) e.getCause(); + assertThat(bfe.getResult().getLogs()).contains("Another SonarQube analysis is already in progress for this project"); + expectedError = true; + } else { + throw e; + } + } + } + if (!expectedError) { + fail("At least one of the threads should have failed"); + } + } + + private void restoreProfile(String fileName) { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/analysis/IssuesModeTest/" + fileName)); + } + + private SonarScanner configureRunnerIssues(String projectDir, @Nullable File homeDir, String... props) throws IOException { + SonarScanner scanner = SonarScanner.create(ItUtils.projectDir(projectDir), + "sonar.working.directory", temp.newFolder().getAbsolutePath(), + "sonar.analysis.mode", "issues", + "sonar.report.export.path", "sonar-report.json"); + if (homeDir != null) { + scanner.setProperty("sonar.userHome", homeDir.getAbsolutePath()); + } else { + scanner.setProperty("sonar.userHome", temp.newFolder().getAbsolutePath()); + } + scanner.setProperties(props); + return scanner; + } + + private SonarScanner configureRunner(String projectDir, String... props) throws IOException { + SonarScanner runner = SonarScanner.create(ItUtils.projectDir(projectDir), + "sonar.working.directory", temp.newFolder().getAbsolutePath(), + "sonar.report.export.path", "sonar-report.json", + "sonar.userHome", temp.newFolder().getAbsolutePath()); + runner.setProperties(props); + return runner; + } + + private void setDefaultQualityProfile(String languageKey, String profileName) { + orchestrator.getServer().adminWsClient().post("api/qualityprofiles/set_default", + "language", languageKey, + "profileName", profileName); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/LinksTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/LinksTest.java new file mode 100644 index 00000000000..eb14836b2c1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/LinksTest.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.google.common.collect.ImmutableMap; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.MavenBuild; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import java.util.List; +import java.util.Optional; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsProjectLinks; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.projectlinks.SearchWsRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LinksTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + private static final String PROJECT_KEY = "com.sonarsource.it.samples:simple-sample"; + + @After + public void cleanProjectLinksTable() { + orchestrator.getServer().post("api/projects/delete", ImmutableMap.of("key", PROJECT_KEY)); + } + + /** + * SONAR-3676 + */ + @Test + public void shouldUseLinkProperties() { + SonarScanner runner = SonarScanner.create(ItUtils.projectDir("analysis/links-project")) + .setProperty("sonar.scm.disabled", "true"); + orchestrator.executeBuild(runner); + + verifyLinks(); + } + + /** + * SONAR-3676 + */ + @Test + public void shouldUseLinkPropertiesOverPomLinksInMaven() { + MavenBuild build = MavenBuild.create(ItUtils.projectPom("analysis/links-project")) + .setCleanPackageSonarGoals() + .setProperty("sonar.scm.disabled", "true"); + orchestrator.executeBuild(build); + + verifyLinks(); + } + + private void verifyLinks() { + WsClient wsClient = ItUtils.newWsClient(orchestrator); + List<WsProjectLinks.Link> links = wsClient.projectLinks().search(new SearchWsRequest().setProjectKey(PROJECT_KEY)).getLinksList(); + verifyLink(links, "homepage", "http://www.simplesample.org_OVERRIDDEN"); + verifyLink(links, "ci", "http://bamboo.ci.codehaus.org/browse/SIMPLESAMPLE"); + verifyLink(links, "issue", "http://jira.codehaus.org/browse/SIMPLESAMPLE"); + verifyLink(links, "scm", "https://github.com/SonarSource/simplesample"); + verifyLink(links, "scm_dev", "scm:git:git@github.com:SonarSource/simplesample.git"); + } + + private void verifyLink(List<WsProjectLinks.Link> links, String expectedType, String expectedUrl) { + Optional<WsProjectLinks.Link> link = links.stream() + .filter(l -> l.getType().equals(expectedType)) + .findFirst(); + assertThat(link).isPresent(); + assertThat(link.get().getUrl()).isEqualTo(expectedUrl); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/MultiLanguageTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/MultiLanguageTest.java new file mode 100644 index 00000000000..fa33ed7ea08 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/MultiLanguageTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import java.util.Map; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasureAsDouble; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; + +public class MultiLanguageTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @After + public void cleanDatabase() { + orchestrator.resetData(); + } + + /** + * SONAR-926 + * SONAR-5069 + */ + @Test + public void test_sonar_runner_inspection() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/analysis/MultiLanguageTest/one-issue-per-line.xml")); + ItUtils.restoreProfile(orchestrator, getClass().getResource("/analysis/MultiLanguageTest/one-issue-per-line-xoo2.xml")); + + orchestrator.getServer().provisionProject("multi-language-sample", "multi-language-sample"); + + orchestrator.getServer().associateProjectToQualityProfile("multi-language-sample", "xoo", "one-issue-per-line"); + orchestrator.getServer().associateProjectToQualityProfile("multi-language-sample", "xoo2", "one-issue-per-line-xoo2"); + + SonarScanner build = SonarScanner.create().setProjectDir(ItUtils.projectDir("analysis/xoo-multi-languages")); + BuildResult result = orchestrator.executeBuild(build); + + // 4 files: 1 .xoo, 1.xoo2, 2 .measures + assertThat(result.getLogs()).contains("4 files indexed"); + assertThat(result.getLogs()).contains("Quality profile for xoo: one-issue-per-line"); + assertThat(result.getLogs()).contains("Quality profile for xoo2: one-issue-per-line-xoo2"); + + // modules + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "multi-language-sample", "files", "violations"); + assertThat(measures.get("files")).isEqualTo(2); + assertThat(measures.get("violations")).isEqualTo(26); + + assertThat(getMeasureAsDouble(orchestrator, "multi-language-sample:src/sample/Sample.xoo", "violations")).isEqualTo(13); + assertThat(getMeasureAsDouble(orchestrator, "multi-language-sample:src/sample/Sample.xoo2", "violations")).isEqualTo(13); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/PermissionTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/PermissionTest.java new file mode 100644 index 00000000000..79663eec478 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/PermissionTest.java @@ -0,0 +1,177 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import javax.annotation.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.WsUserTokens; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.usertoken.GenerateWsRequest; +import org.sonarqube.ws.client.usertoken.RevokeWsRequest; +import org.sonarqube.ws.client.usertoken.UserTokensService; +import util.user.UserRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class PermissionTest { + + private static final String A_LOGIN = "a_login"; + private static final String A_PASSWORD = "a_password"; + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private WsClient adminWsClient; + private UserTokensService userTokensWsClient; + + @Before + public void setUp() { + orchestrator.resetData(); + + // enforce scanners to be authenticated + setServerProperty(orchestrator, "sonar.forceAuthentication", "true"); + + adminWsClient = newAdminWsClient(orchestrator); + userTokensWsClient = adminWsClient.userTokens(); + } + + @After + public void tearDown() throws Exception { + resetSettings(orchestrator, null, "sonar.forceAuthentication"); + userRule.resetUsers(); + } + + @Test + public void scanner_can_authenticate_with_authentication_token() { + createUserWithProvisioningAndScanPermissions(); + + String tokenName = "For test"; + WsUserTokens.GenerateWsResponse generateWsResponse = userTokensWsClient.generate(new GenerateWsRequest() + .setLogin(A_LOGIN) + .setName(tokenName)); + SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-sample")); + sampleProject.setProperties( + "sonar.login", generateWsResponse.getToken(), + "sonar.password", ""); + + BuildResult buildResult = orchestrator.executeBuild(sampleProject); + + assertThat(buildResult.isSuccess()).isTrue(); + userTokensWsClient.revoke(new RevokeWsRequest().setLogin(A_LOGIN).setName(tokenName)); + } + + @Test + public void scanner_fails_if_authentication_token_is_not_valid() { + SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-sample")); + sampleProject.setProperties( + "sonar.login", "unknown-token", + "sonar.password", ""); + + BuildResult buildResult = orchestrator.executeBuildQuietly(sampleProject); + + assertThat(buildResult.isSuccess()).isFalse(); + } + + /** + * SONAR-4211 Test Sonar Runner when server requires authentication + */ + @Test + public void scanner_can_authenticate_with_login_password() { + createUserWithProvisioningAndScanPermissions(); + + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + + BuildResult buildResult = scanQuietly("shared/xoo-sample", + "sonar.login", "", + "sonar.password", ""); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains( + "Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password."); + + // SONAR-4048 + buildResult = scanQuietly("shared/xoo-sample", + "sonar.login", "wrong_login", + "sonar.password", "wrong_password"); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains( + "Not authorized. Please check the properties sonar.login and sonar.password."); + + buildResult = scan("shared/xoo-sample", + "sonar.login", A_LOGIN, + "sonar.password", A_PASSWORD); + assertThat(buildResult.getLastStatus()).isEqualTo(0); + } + + @Test + public void run_scanner_with_user_having_scan_permission_only_on_project() throws Exception { + userRule.createUser(A_LOGIN, A_PASSWORD); + orchestrator.getServer().provisionProject("sample", "sample"); + addUserPermission(A_LOGIN, "scan", "sample"); + + BuildResult buildResult = scanQuietly("shared/xoo-sample", "sonar.login", A_LOGIN, "sonar.password", A_PASSWORD); + + assertThat(buildResult.isSuccess()).isTrue(); + } + + private void addUserPermission(String login, String permission, @Nullable String projectKey) { + adminWsClient.permissions().addUser(new AddUserWsRequest() + .setLogin(login) + .setPermission(permission) + .setProjectKey(projectKey)); + } + + private BuildResult scan(String projectPath, String... props) { + SonarScanner scanner = configureScanner(projectPath, props); + return orchestrator.executeBuild(scanner); + } + + private BuildResult scanQuietly(String projectPath, String... props) { + SonarScanner scanner = configureScanner(projectPath, props); + return orchestrator.executeBuildQuietly(scanner); + } + + private SonarScanner configureScanner(String projectPath, String... props) { + return SonarScanner.create(projectDir(projectPath)) + .setProperties(props); + } + + private void createUserWithProvisioningAndScanPermissions() { + userRule.createUser(A_LOGIN, A_PASSWORD); + addUserPermission(A_LOGIN, "provisioning", null); + addUserPermission(A_LOGIN, "scan", null); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/ProjectBuilderTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/ProjectBuilderTest.java new file mode 100644 index 00000000000..53b5a7059f9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/ProjectBuilderTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.MavenBuild; +import org.sonarqube.tests.Category3Suite; +import java.util.Map; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getComponent; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; + +/** + * Test the extension point org.sonar.api.batch.bootstrap.ProjectBuilder + * <p/> + * A Sonar plugin can override the project definition injected by build-tool. + * Example: C# plugin loads project structure and modules from Visual Studio metadata file. + * + * @since 2.9 + */ +public class ProjectBuilderTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Test + public void shouldDefineProjectFromPlugin() { + MavenBuild build = MavenBuild.create(ItUtils.projectPom("analysis/project-builder")) + .setCleanSonarGoals() + .setProperty("sonar.enableProjectBuilder", "true") + .setProperty("sonar.dynamicAnalysis", "false"); + orchestrator.executeBuild(build); + + checkProject(); + checkSubProject("project-builder-module-a"); + checkSubProject("project-builder-module-b"); + checkFile("project-builder-module-a", "src/HelloA.java"); + checkFile("project-builder-module-b", "src/HelloB.java"); + assertThat(getComponent(orchestrator, "com.sonarsource.it.projects.batch:project-builder-module-b:src/IgnoredFile.java")).isNull(); + } + + private void checkProject() { + // name has been changed by plugin + assertThat(getComponent(orchestrator, "com.sonarsource.it.projects.batch:project-builder").getName()).isEqualTo("Name changed by plugin"); + + Map<String, Double> measures = getMeasures("com.sonarsource.it.projects.batch:project-builder"); + assertThat(measures.get("files")).isEqualTo(2); + assertThat(measures.get("lines")).isGreaterThan(10); + } + + private void checkSubProject(String subProjectKey) { + Map<String, Double> measures = getMeasures("com.sonarsource.it.projects.batch:" + subProjectKey); + assertThat(measures.get("files")).isEqualTo(1); + assertThat(measures.get("lines")).isGreaterThan(5); + } + + private void checkFile(String subProjectKey, String fileKey) { + Map<String, Double> measures = getMeasures("com.sonarsource.it.projects.batch:" + subProjectKey + ":" + fileKey); + assertThat(measures.get("lines")).isGreaterThan(5); + } + + private Map<String, Double> getMeasures(String key) { + return getMeasuresAsDoubleByMetricKey(orchestrator, key, "lines", "files"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/ReportDumpTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/ReportDumpTest.java new file mode 100644 index 00000000000..952a6a057e6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/ReportDumpTest.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.http.HttpResponse; +import org.sonarqube.tests.Category3Suite; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.Properties; +import org.apache.commons.io.FileUtils; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ReportDumpTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + /** + * SONAR-6905 + */ + @Test + public void dump_metadata_of_uploaded_report() throws Exception { + File projectDir = ItUtils.projectDir("shared/xoo-sample"); + orchestrator.executeBuild(SonarScanner.create(projectDir, "sonar.projectKey", "dump_metadata_of_uploaded_report", "sonar.projectName", "dump_metadata_of_uploaded_report")); + + File metadata = new File(projectDir, ".sonar/report-task.txt"); + assertThat(metadata).exists().isFile(); + + // verify properties + Properties props = new Properties(); + props.load(new StringReader(FileUtils.readFileToString(metadata, StandardCharsets.UTF_8))); + assertThat(props).hasSize(6); + assertThat(props.getProperty("projectKey")).isEqualTo("dump_metadata_of_uploaded_report"); + assertThat(props.getProperty("ceTaskId")).isNotEmpty(); + String serverVersion = orchestrator.getServer().newHttpCall("api/server/version").execute().getBodyAsString(); + assertThat(props.getProperty("serverVersion")).isEqualTo(serverVersion); + verifyUrl(props.getProperty("serverUrl")); + verifyUrl(props.getProperty("dashboardUrl")); + verifyUrl(props.getProperty("ceTaskUrl")); + } + + private void verifyUrl(String url) throws IOException { + HttpResponse response = orchestrator.getServer().newHttpCall(url).execute(); + assertThat(response.isSuccessful()).as(url).isTrue(); + assertThat(response.getBodyAsString()).as(url).isNotEmpty(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/SSLTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/SSLTest.java new file mode 100644 index 00000000000..be2f8804ae5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/SSLTest.java @@ -0,0 +1,197 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.util.NetworkUtils; +import org.sonarqube.tests.Category3Suite; +import java.net.InetAddress; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.proxy.ProxyServlet; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SSLTest { + + private static final String CLIENT_KEYSTORE = "/analysis/SSLTest/clientkeystore.jks"; + private static final String CLIENT_KEYSTORE_PWD = "clientp12pwd"; + + private static final String CLIENT_TRUSTSTORE = "/analysis/SSLTest/clienttruststore.jks"; + private static final String CLIENT_TRUSTSTORE_PWD = "clienttruststorepwd"; + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + private static Server server; + private static int httpsPort; + + @Before + public void deleteData() { + orchestrator.resetData(); + } + + public static void startSSLTransparentReverseProxy(boolean requireClientAuth) throws Exception { + int httpPort = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress()); + httpsPort = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress()); + + // Setup Threadpool + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setMaxThreads(500); + + server = new Server(threadPool); + + // HTTP Configuration + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSecureScheme("https"); + httpConfig.setSecurePort(httpsPort); + httpConfig.setSendServerVersion(true); + httpConfig.setSendDateHeader(false); + + // Handler Structure + HandlerCollection handlers = new HandlerCollection(); + handlers.setHandlers(new Handler[] {proxyHandler(), new DefaultHandler()}); + server.setHandler(handlers); + + ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); + http.setPort(httpPort); + server.addConnector(http); + + Path serverKeyStore = Paths.get(SSLTest.class.getResource("/analysis/SSLTest/serverkeystore.jks").toURI()).toAbsolutePath(); + String keyStorePassword = "serverkeystorepwd"; + String serverKeyPassword = "serverp12pwd"; + Path serverTrustStore = Paths.get(SSLTest.class.getResource("/analysis/SSLTest/servertruststore.jks").toURI()).toAbsolutePath(); + String trustStorePassword = "servertruststorepwd"; + + // SSL Context Factory + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(serverKeyStore.toString()); + sslContextFactory.setKeyStorePassword(keyStorePassword); + sslContextFactory.setKeyManagerPassword(serverKeyPassword); + sslContextFactory.setTrustStorePath(serverTrustStore.toString()); + sslContextFactory.setTrustStorePassword(trustStorePassword); + sslContextFactory.setNeedClientAuth(requireClientAuth); + sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA", + "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA", + "SSL_RSA_EXPORT_WITH_RC4_40_MD5", + "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"); + + // SSL HTTP Configuration + HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); + + // SSL Connector + ServerConnector sslConnector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), + new HttpConnectionFactory(httpsConfig)); + sslConnector.setPort(httpsPort); + server.addConnector(sslConnector); + + server.start(); + } + + private static ServletContextHandler proxyHandler() { + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setServletHandler(newServletHandler()); + return contextHandler; + } + + private static ServletHandler newServletHandler() { + ServletHandler handler = new ServletHandler(); + ServletHolder holder = handler.addServletWithMapping(ProxyServlet.Transparent.class, "/*"); + holder.setInitParameter("proxyTo", orchestrator.getServer().getUrl()); + return handler; + } + + @After + public void stopProxy() throws Exception { + if (server != null && server.isStarted()) { + server.stop(); + } + } + + @Test + public void simple_analysis_with_server_and_client_certificate() throws Exception { + startSSLTransparentReverseProxy(true); + + SonarScanner sonarScanner = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setScannerVersion("2.7") + .setProperty("sonar.host.url", "https://localhost:" + httpsPort); + + BuildResult buildResult = orchestrator.executeBuildQuietly(sonarScanner); + assertThat(buildResult.getLastStatus()).isNotEqualTo(0); + assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException"); + + Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE).toURI()).toAbsolutePath(); + String truststorePassword = CLIENT_TRUSTSTORE_PWD; + Path clientKeystore = Paths.get(SSLTest.class.getResource(CLIENT_KEYSTORE).toURI()).toAbsolutePath(); + String keystorePassword = CLIENT_KEYSTORE_PWD; + + buildResult = orchestrator.executeBuild(sonarScanner + .setEnvironmentVariable("SONAR_SCANNER_OPTS", + "-Djavax.net.ssl.trustStore=" + clientTruststore.toString() + + " -Djavax.net.ssl.trustStorePassword=" + truststorePassword + + " -Djavax.net.ssl.keyStore=" + clientKeystore.toString() + + " -Djavax.net.ssl.keyStorePassword=" + keystorePassword)); + } + + @Test + public void simple_analysis_with_server_certificate() throws Exception { + startSSLTransparentReverseProxy(false); + + SonarScanner sonarScanner = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setScannerVersion("2.7") + .setProperty("sonar.host.url", "https://localhost:" + httpsPort); + + BuildResult buildResult = orchestrator.executeBuildQuietly(sonarScanner); + assertThat(buildResult.getLastStatus()).isNotEqualTo(0); + assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException"); + + Path clientTruststore = Paths.get(SSLTest.class.getResource(CLIENT_TRUSTSTORE).toURI()).toAbsolutePath(); + String truststorePassword = CLIENT_TRUSTSTORE_PWD; + + buildResult = orchestrator.executeBuild(sonarScanner + .setEnvironmentVariable("SONAR_SCANNER_OPTS", + "-Djavax.net.ssl.trustStore=" + clientTruststore.toString() + + " -Djavax.net.ssl.trustStorePassword=" + truststorePassword)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/ScannerTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/ScannerTest.java new file mode 100644 index 00000000000..a175bbf6294 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/ScannerTest.java @@ -0,0 +1,395 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.junit.Assume; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.ws.client.component.SearchWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getComponent; +import static util.ItUtils.getComponentNavigation; +import static util.ItUtils.getMeasureAsDouble; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class ScannerTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void deleteData() { + orchestrator.resetData(); + ItUtils.restoreProfile(orchestrator, getClass().getResource("/analysis/BatchTest/one-issue-per-line.xml")); + } + + /** + * SONAR-3718 + */ + @Test + public void should_scan_branch_with_forward_slash() { + scan("shared/xoo-multi-modules-sample"); + scan("shared/xoo-multi-modules-sample", "sonar.branch", "branch/0.x"); + + assertThat(newAdminWsClient(orchestrator).components().search(new SearchWsRequest().setQualifiers(singletonList("TRK"))).getComponentsList()).hasSize(2); + assertThat(getComponent(orchestrator, "com.sonarsource.it.samples:multi-modules-sample").getName()).isEqualTo("Sonar :: Integration Tests :: Multi-modules Sample"); + assertThat(getComponent(orchestrator, "com.sonarsource.it.samples:multi-modules-sample:branch/0.x").getName()) + .isEqualTo("Sonar :: Integration Tests :: Multi-modules Sample branch/0.x"); + } + + @Test + public void use_sonar_profile_without_provisioning_project() { + scan("shared/xoo-multi-modules-sample", + "sonar.profile", "one-issue-per-line", + "sonar.verbose", "true"); + assertThat(getMeasureAsDouble(orchestrator, "com.sonarsource.it.samples:multi-modules-sample", "violations")).isEqualTo(61); + } + + // SONAR-4680 + @Test + public void module_should_load_own_settings_from_database() { + String rootModuleKey = "com.sonarsource.it.samples:multi-modules-sample"; + orchestrator.getServer().provisionProject(rootModuleKey, "Sonar :: Integration Tests :: Multi-modules Sample"); + + String propKey = "myFakeProperty"; + String moduleBKey = rootModuleKey + ":module_b"; + resetSettings(orchestrator, rootModuleKey, propKey); + + BuildResult result = scan("shared/xoo-multi-modules-sample", "sonar.showSettings", propKey); + + assertThat(result.getLogs()).doesNotContain(rootModuleKey + ":" + propKey); + assertThat(result.getLogs()).doesNotContain(moduleBKey + ":" + propKey); + + // Set property only on root project + setServerProperty(orchestrator, rootModuleKey, propKey, "project"); + + result = scan("shared/xoo-multi-modules-sample", "sonar.showSettings", propKey); + + assertThat(result.getLogs()).contains(rootModuleKey + ":" + propKey + " = project"); + assertThat(result.getLogs()).contains(moduleBKey + ":" + propKey + " = project"); + + // Override property on moduleB + setServerProperty(orchestrator, moduleBKey, propKey, "moduleB"); + + result = scan("shared/xoo-multi-modules-sample", "sonar.showSettings", propKey); + + assertThat(result.getLogs()).contains(rootModuleKey + ":" + propKey + " = project"); + assertThat(result.getLogs()).contains(moduleBKey + ":" + propKey + " = moduleB"); + } + + // SONAR-4680 + @Test + public void module_should_load_settings_from_parent() { + String rootModuleKey = "com.sonarsource.it.samples:multi-modules-sample"; + orchestrator.getServer().provisionProject(rootModuleKey, "Sonar :: Integration Tests :: Multi-modules Sample"); + + String propKey = "myFakeProperty"; + String moduleBKey = rootModuleKey + ":module_b"; + + // Set property on provisioned project + setServerProperty(orchestrator, rootModuleKey, propKey, "project"); + + BuildResult result = scan("shared/xoo-multi-modules-sample", "sonar.showSettings", propKey); + + assertThat(result.getLogs()).contains(rootModuleKey + ":" + propKey + " = project"); + // Module should inherit from parent + assertThat(result.getLogs()).contains(moduleBKey + ":" + propKey + " = project"); + } + + /** + * SONAR-3024 + */ + @Test + public void should_support_source_files_with_same_deprecated_key() { + orchestrator.getServer().provisionProject("com.sonarsource.it.projects.batch:duplicate-source", "exclusions"); + orchestrator.getServer().associateProjectToQualityProfile("com.sonarsource.it.projects.batch:duplicate-source", "xoo", "one-issue-per-line"); + scan("analysis/duplicate-source"); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "com.sonarsource.it.projects.batch:duplicate-source", "files", "directories"); + // 2 main files and 1 test file all with same deprecated key + assertThat(measures.get("files")).isEqualTo(2); + assertThat(measures.get("directories")).isEqualTo(2); + } + + /** + * SONAR-3125 + */ + @Test + public void should_display_explicit_message_when_no_plugin_language_available() { + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + BuildResult buildResult = scanQuietly("shared/xoo-sample", + "sonar.language", "foo", + "sonar.profile", ""); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains( + "You must install a plugin that supports the language 'foo'"); + } + + @Test + public void should_display_explicit_message_when_wrong_profile() { + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + BuildResult buildResult = scanQuietly("shared/xoo-sample", + "sonar.profile", "unknow"); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains( + "sonar.profile was set to 'unknow' but didn't match any profile for any language. Please check your configuration."); + } + + @Test + public void should_create_project_without_name_version() { + // some of the sub-modules have a name defined, others don't + BuildResult buildResult = scan("shared/xoo-multi-module-sample-without-project-name-version"); + assertThat(buildResult.isSuccess()).isTrue(); + + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample", "com.sonarsource.it.samples:multi-modules-sample", "not provided"); + + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_b", "module_b", "not provided"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1", "module_b1", "not provided"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2", "Sub-module B2", "not provided"); + + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_a", "Module A", "not provided"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", "Sub-module A1", "not provided"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2", "Sub-module A2", "not provided"); + } + + @Test + public void should_analyze_project_without_name_version() { + orchestrator.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "My project name"); + BuildResult buildResult = scan("shared/xoo-multi-module-sample-without-project-name-version", + "sonar.projectName", "My project name", + "sonar.projectVersion", "1.0"); + assertThat(buildResult.isSuccess()).isTrue(); + + buildResult = scan("shared/xoo-multi-module-sample-without-project-name-version"); + assertThat(buildResult.isSuccess()).isTrue(); + + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample", "My project name", "1.0"); + + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_b", "module_b", "1.0"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1", "module_b1", "1.0"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2", "Sub-module B2", "1.0"); + + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_a", "Module A", "1.0"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", "Sub-module A1", "1.0"); + assertNameAndVersion("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2", "Sub-module A2", "1.0"); + } + + private void assertNameAndVersion(String projectKey, String expectedProjectName, String expectedProjectVersion) { + assertThat(getComponent(orchestrator, projectKey).getName()).isEqualTo(expectedProjectName); + assertThat(getComponentNavigation(orchestrator, projectKey).getVersion()).isEqualTo(expectedProjectVersion); + } + + @Test + public void should_honor_sonarUserHome() { + File userHome = temp.getRoot(); + + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + SonarScanner scanner = configureScanner("shared/xoo-sample", + "sonar.verbose", "true"); + scanner.setEnvironmentVariable("SONAR_USER_HOME", "/dev/null"); + BuildResult buildResult = orchestrator.executeBuildQuietly(scanner); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + + buildResult = scan("shared/xoo-sample", + "sonar.verbose", "true", + "sonar.userHome", userHome.getAbsolutePath()); + assertThat(buildResult.isSuccess()).isTrue(); + } + + /** + * SONAR-2291 + */ + @Test + public void batch_should_cache_plugin_jars() throws IOException { + File userHome = temp.newFolder(); + + BuildResult result = scan("shared/xoo-sample", + "sonar.userHome", userHome.getAbsolutePath()); + + File cache = new File(userHome, "cache"); + assertThat(cache).exists().isDirectory(); + int cachedFiles = FileUtils.listFiles(cache, new String[] {"jar"}, true).size(); + assertThat(cachedFiles).isGreaterThan(5); + assertThat(result.getLogs()).contains("User cache: " + cache.getAbsolutePath()); + assertThat(result.getLogs()).contains("Download sonar-xoo-plugin-"); + + result = scan("shared/xoo-sample", + "sonar.userHome", userHome.getAbsolutePath()); + assertThat(cachedFiles).isEqualTo(cachedFiles); + assertThat(result.getLogs()).contains("User cache: " + cache.getAbsolutePath()); + assertThat(result.getLogs()).doesNotContain("Download sonar-xoo-plugin-"); + } + + @Test + public void batch_should_keep_report_verbose() { + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + scanQuietly("shared/xoo-sample", "sonar.verbose", "true"); + File reportDir = new File(new File(ItUtils.projectDir("shared/xoo-sample"), ".sonar"), "batch-report"); + assertThat(reportDir).isDirectory(); + assertThat(reportDir.list()).isNotEmpty(); + } + + /** + * SONAR-4239 + */ + @Test + public void should_display_project_url_after_analysis() throws IOException { + orchestrator.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "Sonar :: Integration Tests :: Multi-modules Sample"); + orchestrator.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "one-issue-per-line"); + Assume.assumeTrue(orchestrator.getServer().version().isGreaterThanOrEquals("3.6")); + + BuildResult result = scan("shared/xoo-multi-modules-sample"); + + assertThat(result.getLogs()).contains("/dashboard/index/com.sonarsource.it.samples:multi-modules-sample"); + + result = scan("shared/xoo-multi-modules-sample", + "sonar.branch", "mybranch"); + + assertThat(result.getLogs()).contains("/dashboard/index/com.sonarsource.it.samples:multi-modules-sample:mybranch"); + + try { + setServerProperty(orchestrator, null, "sonar.core.serverBaseURL", "http://foo:123/sonar"); + result = scan("shared/xoo-multi-modules-sample"); + assertThat(result.getLogs()).contains("http://foo:123/sonar/dashboard/index/com.sonarsource.it.samples:multi-modules-sample"); + } finally { + resetSettings(orchestrator, null, "sonar.core.serverBaseURL"); + } + } + + /** + * SONAR-4188, SONAR-5178, SONAR-5915 + */ + @Test + public void should_display_explicit_message_when_invalid_project_key_or_branch() { + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + BuildResult buildResult = scanQuietly("shared/xoo-sample", + "sonar.projectKey", "ar g$l:"); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains("\"ar g$l:\" is not a valid project or module key") + .contains("Allowed characters"); + + // SONAR-4629 + buildResult = scanQuietly("shared/xoo-sample", + "sonar.projectKey", "12345"); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains("\"12345\" is not a valid project or module key") + .contains("Allowed characters"); + + buildResult = scanQuietly("shared/xoo-sample", + "sonar.branch", "ar g$l:"); + assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLogs()).contains("\"ar g$l:\" is not a valid branch") + .contains("Allowed characters"); + } + + /** + * SONAR-4547 + */ + @Test + public void display_MessageException_without_stacktrace() throws Exception { + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + BuildResult result = scanQuietly("shared/xoo-sample", "raiseMessageException", "true"); + assertThat(result.getLastStatus()).isNotEqualTo(0); + assertThat(result.getLogs()) + // message + .contains("Error message from plugin") + + // but not stacktrace + .doesNotContain("at com.sonarsource.RaiseMessageException"); + } + + /** + * SONAR-4751 + */ + @Test + public void file_extensions_are_case_insensitive() throws Exception { + orchestrator.getServer().provisionProject("case-sensitive-file-extensions", "Case Sensitive"); + orchestrator.getServer().associateProjectToQualityProfile("case-sensitive-file-extensions", "xoo", "one-issue-per-line"); + scan("analysis/case-sensitive-file-extensions"); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "case-sensitive-file-extensions", "files", "ncloc"); + assertThat(measures.get("files")).isEqualTo(2); + assertThat(measures.get("ncloc")).isEqualTo(5 + 2); + } + + /** + * SONAR-4876 + */ + @Test + public void custom_module_key() { + orchestrator.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "Sonar :: Integration Tests :: Multi-modules Sample"); + orchestrator.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "one-issue-per-line"); + scan("analysis/custom-module-key"); + assertThat(getComponent(orchestrator, "com.sonarsource.it.samples:moduleA")).isNotNull(); + assertThat(getComponent(orchestrator, "com.sonarsource.it.samples:moduleB")).isNotNull(); + } + + private BuildResult scan(String projectPath, String... props) { + SonarScanner scanner = configureScanner(projectPath, props); + return orchestrator.executeBuild(scanner); + } + + private BuildResult scanQuietly(String projectPath, String... props) { + SonarScanner scanner = configureScanner(projectPath, props); + return orchestrator.executeBuildQuietly(scanner); + } + + private SonarScanner configureScanner(String projectPath, String... props) { + SonarScanner scanner = SonarScanner.create(ItUtils.projectDir(projectPath)) + .setProperties(props); + return scanner; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/SettingsEncryptionTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/SettingsEncryptionTest.java new file mode 100644 index 00000000000..8a530680550 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/SettingsEncryptionTest.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildFailureException; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import java.io.File; +import java.net.URL; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SettingsEncryptionTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + /** + * SONAR-2084 + * SONAR-4061 + */ + @Test + public void testEncryptedProperty() throws Exception { + SonarScanner build = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setProperty("sonar.secretKeyPath", pathToValidSecretKey()) + .setProperty("sonar.login", "admin") + // wrong password + .setProperty("sonar.password", "{aes}wrongencryption==")// wrong password + // "this is a secret" encrypted with the above secret key + .setProperty("encryptedProperty", "{aes}9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); + BuildResult result = orchestrator.executeBuildQuietly(build); + assertThat(result.getStatus()).isNotEqualTo(0); + assertThat(result.getLogs()).contains("Fail to decrypt the property sonar.password. Please check your secret key"); + + build = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setProperty("sonar.secretKeyPath", pathToValidSecretKey()) + // "admin" encrypted with the above secret key + .setProperty("sonar.login", "{aes}evRHXHsEyPr5RjEuxUJcHA==") + .setProperty("sonar.password", "{aes}evRHXHsEyPr5RjEuxUJcHA==") + // "this is a secret" encrypted with the above secret key + .setProperty("encryptedProperty", "{aes}9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); + // no error + orchestrator.executeBuild(build); + } + + /** + * SONAR-2084 + */ + @Test(expected = BuildFailureException.class) + public void failIfEncryptedPropertyButNoSecretKey() throws Exception { + // path to secret key is missing + SonarScanner build = SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setProperty("encryptedProperty", "{aes}9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY="); + orchestrator.executeBuild(build); + } + + private String pathToValidSecretKey() throws Exception { + URL resource = getClass().getResource("/analysis/SettingsEncryptionTest/sonar-secret.txt"); + return new File(resource.toURI()).getCanonicalPath(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/TempFolderTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/TempFolderTest.java new file mode 100644 index 00000000000..9843ce5ce32 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/analysis/TempFolderTest.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.analysis; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category3Suite; +import java.io.File; +import java.io.IOException; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TempFolderTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void deleteData() { + orchestrator.resetData(); + ItUtils.restoreProfile(orchestrator, getClass().getResource("/analysis/TempFolderTest/one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "Sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + } + + // SONAR-4748 + @Test + public void should_create_in_temp_folder() throws IOException { + File projectDir = ItUtils.projectDir("shared/xoo-sample"); + BuildResult result = scan(); + + assertThat(result.getLogs()).doesNotContain("Creating temp directory:"); + assertThat(result.getLogs()).doesNotContain("Creating temp file:"); + + result = scan("sonar.createTempFiles", "true"); + assertThat(result.getLogs()).contains( + "Creating temp directory: " + projectDir.getCanonicalPath() + File.separator + ".sonar" + File.separator + ".sonartmp" + File.separator + "sonar-it"); + assertThat(result.getLogs()).contains( + "Creating temp file: " + projectDir.getCanonicalPath() + File.separator + ".sonar" + File.separator + ".sonartmp" + File.separator + "sonar-it"); + + // Verify temp folder is deleted after analysis + assertThat(new File(projectDir, ".sonar/.sonartmp/sonar-it")).doesNotExist(); + } + + // SONAR-4748 + @Test + public void should_not_use_system_tmp_dir() throws Exception { + File tmp = temp.newFolder(); + SonarScanner runner = configureScanner() + .setEnvironmentVariable("SONAR_RUNNER_OPTS", "-Djava.io.tmpdir=" + tmp.getAbsolutePath()); + orchestrator.executeBuild(runner); + + // temp directory is clean-up + assertThat(tmp.list()).isEmpty(); + } + + private BuildResult scan(String... props) { + SonarScanner runner = configureScanner(props); + return orchestrator.executeBuild(runner); + } + + private SonarScanner configureScanner(String... props) { + return SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setProperties(props); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/ExecuteAnalysisPermissionTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/ExecuteAnalysisPermissionTest.java new file mode 100644 index 00000000000..65f1a52bce1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/authorisation/ExecuteAnalysisPermissionTest.java @@ -0,0 +1,151 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.authorisation; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildFailureException; +import org.sonarqube.tests.Category1Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.SonarClient; +import org.sonar.wsclient.user.UserParameters; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.permission.AddGroupWsRequest; +import org.sonarqube.ws.client.permission.AddProjectCreatorToTemplateWsRequest; +import org.sonarqube.ws.client.permission.RemoveGroupWsRequest; +import org.sonarqube.ws.client.project.UpdateVisibilityRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; + +/** + * SONAR-4397 + */ +public class ExecuteAnalysisPermissionTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + private final static String USER_LOGIN = "scanperm"; + private final static String USER_PASSWORD = "thewhite"; + private final static String PROJECT_KEY = "sample"; + + private static WsClient adminWsClient; + private static SonarClient oldAdminWsClient; + + @Before + public void setUp() { + orchestrator.resetData(); + oldAdminWsClient = orchestrator.getServer().adminWsClient(); + oldAdminWsClient.userClient().create(UserParameters.create().login(USER_LOGIN).name(USER_LOGIN).password(USER_PASSWORD).passwordConfirmation(USER_PASSWORD)); + orchestrator.getServer().provisionProject(PROJECT_KEY, "Sample"); + adminWsClient = newAdminWsClient(orchestrator); + } + + @After + public void tearDown() { + addGlobalPermission("anyone", "scan"); + oldAdminWsClient.userClient().deactivate(USER_LOGIN); + } + + @Test + public void should_fail_if_logged_but_no_scan_permission() throws Exception { + executeLoggedAnalysis(); + + removeGlobalPermission("anyone", "scan"); + try { + // Execute logged analysis, but without the "Execute Analysis" permission + executeLoggedAnalysis(); + fail(); + } catch (BuildFailureException e) { + assertThat(e.getResult().getLogs()).contains( + "You're only authorized to execute a local (preview) SonarQube analysis without pushing the results to the SonarQube server. Please contact your SonarQube administrator."); + } + + newAdminWsClient(orchestrator).projects().updateVisibility(UpdateVisibilityRequest.builder().setProject(PROJECT_KEY).setVisibility("private").build()); + try { + // Execute anonymous analysis + executeAnonymousAnalysis(); + fail(); + } catch (BuildFailureException e) { + assertThat(e.getResult().getLogs()).contains( + "You're not authorized to execute any SonarQube analysis. Please contact your SonarQube administrator."); + } + } + + @Test + public void no_need_for_browse_permission_to_scan() throws Exception { + // Do a first analysis, no error + executeAnonymousAnalysis(); + + // make project private + newAdminWsClient(orchestrator).projects().updateVisibility(UpdateVisibilityRequest.builder().setProject("sample").setVisibility("private").build()); + + // still no error + executeAnonymousAnalysis(); + } + + @Test + public void execute_analysis_with_scan_permission_only_on_project() throws Exception { + removeGlobalPermission("anyone", "scan"); + addProjectPermission("anyone", PROJECT_KEY, "scan"); + + executeLoggedAnalysis(); + } + + @Test + public void execute_analysis_with_scan_on_default_template() { + removeGlobalPermission("anyone", "scan"); + adminWsClient.permissions().addProjectCreatorToTemplate(AddProjectCreatorToTemplateWsRequest.builder() + .setPermission("scan") + .setTemplateId("default_template") + .build()); + + runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.login", USER_LOGIN, "sonar.password", USER_PASSWORD, "sonar.projectKey", "ANOTHER_PROJECT_KEY"); + } + + private static void addProjectPermission(String groupName, String projectKey, String permission) { + adminWsClient.permissions().addGroup(new AddGroupWsRequest().setGroupName(groupName).setProjectKey(projectKey).setPermission(permission)); + } + + private static void addGlobalPermission(String groupName, String permission) { + adminWsClient.permissions().addGroup(new AddGroupWsRequest().setGroupName(groupName).setPermission(permission)); + } + + private static void removeProjectPermission(String groupName, String projectKey, String permission) { + adminWsClient.permissions().removeGroup(new RemoveGroupWsRequest().setGroupName(groupName).setProjectKey(projectKey).setPermission(permission)); + } + + private static void removeGlobalPermission(String groupName, String permission) { + adminWsClient.permissions().removeGroup(new RemoveGroupWsRequest().setGroupName(groupName).setPermission(permission)); + } + + private static void executeLoggedAnalysis() { + runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.login", USER_LOGIN, "sonar.password", USER_PASSWORD); + } + + private static void executeAnonymousAnalysis() { + runProjectAnalysis(orchestrator, "shared/xoo-sample"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/IssuePermissionTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/IssuePermissionTest.java new file mode 100644 index 00000000000..21f39e7d636 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/authorisation/IssuePermissionTest.java @@ -0,0 +1,276 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.authorisation; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.SonarClient; +import org.sonar.wsclient.base.HttpException; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.user.UserParameters; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.BulkChangeRequest; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.project.UpdateVisibilityRequest; +import util.ItUtils; + +import static java.util.Arrays.asList; +import static junit.framework.TestCase.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.projectDir; + +public class IssuePermissionTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + public WsClient adminWsClient = newAdminWsClient(orchestrator); + + @Before + public void init() { + orchestrator.resetData(); + + ItUtils.restoreProfile(orchestrator, getClass().getResource("/authorisation/one-issue-per-line-profile.xml")); + + orchestrator.getServer().provisionProject("privateProject", "PrivateProject"); + newAdminWsClient(orchestrator).projects().updateVisibility(UpdateVisibilityRequest.builder().setProject("privateProject").setVisibility("private").build()); + orchestrator.getServer().associateProjectToQualityProfile("privateProject", "xoo", "one-issue-per-line"); + SonarScanner privateProject = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.projectKey", "privateProject") + .setProperty("sonar.projectName", "PrivateProject"); + orchestrator.executeBuild(privateProject); + + orchestrator.getServer().provisionProject("publicProject", "PublicProject"); + orchestrator.getServer().associateProjectToQualityProfile("publicProject", "xoo", "one-issue-per-line"); + SonarScanner publicProject = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.projectKey", "publicProject") + .setProperty("sonar.projectName", "PublicProject"); + + + orchestrator.executeBuild(publicProject); + } + + @Test + public void need_user_permission_on_project_to_see_issue() { + SonarClient client = orchestrator.getServer().adminWsClient(); + + String withBrowsePermission = "with-browse-permission"; + String withoutBrowsePermission = "without-browse-permission"; + + try { + client.userClient().create(UserParameters.create().login(withBrowsePermission).name(withBrowsePermission) + .password("password").passwordConfirmation("password")); + addUserPermission(withBrowsePermission, "privateProject", "user"); + + client.userClient().create(UserParameters.create().login(withoutBrowsePermission).name(withoutBrowsePermission) + .password("password").passwordConfirmation("password")); + + // Without user permission, a user cannot see issues on the project + assertThat(orchestrator.getServer().wsClient(withoutBrowsePermission, "password").issueClient().find( + IssueQuery.create().componentRoots("privateProject")).list()).isEmpty(); + + // With user permission, a user can see issues on the project + assertThat(orchestrator.getServer().wsClient(withBrowsePermission, "password").issueClient().find( + IssueQuery.create().componentRoots("privateProject")).list()).isNotEmpty(); + + } finally { + client.userClient().deactivate(withBrowsePermission); + client.userClient().deactivate(withoutBrowsePermission); + } + } + + /** + * SONAR-4839 + */ + @Test + public void need_user_permission_on_project_to_see_issue_changelog() { + SonarClient client = orchestrator.getServer().adminWsClient(); + Issue issue = client.issueClient().find(IssueQuery.create().componentRoots("privateProject")).list().get(0); + client.issueClient().assign(issue.key(), "admin"); + + String withBrowsePermission = "with-browse-permission"; + String withoutBrowsePermission = "without-browse-permission"; + + try { + client.userClient().create(UserParameters.create().login(withBrowsePermission).name(withBrowsePermission) + .password("password").passwordConfirmation("password")); + addUserPermission(withBrowsePermission, "privateProject", "user"); + + client.userClient().create(UserParameters.create().login(withoutBrowsePermission).name(withoutBrowsePermission) + .password("password").passwordConfirmation("password")); + + // Without user permission, a user cannot see issue changelog on the project + try { + changelog(issue.key(), withoutBrowsePermission, "password"); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(org.sonarqube.ws.client.HttpException.class).describedAs("403"); + } + + // Without user permission, a user cannot see issues on the project + assertThat(changelog(issue.key(), withBrowsePermission, "password").getChangelogList()).isNotEmpty(); + + } finally { + client.userClient().deactivate(withBrowsePermission); + client.userClient().deactivate(withoutBrowsePermission); + } + } + + /** + * SONAR-2447 + */ + @Test + public void need_administer_issue_permission_on_project_to_set_severity() { + SonarClient client = orchestrator.getServer().adminWsClient(); + Issue issueOnPrivateProject = client.issueClient().find(IssueQuery.create().componentRoots("privateProject")).list().get(0); + Issue issueOnPublicProject = client.issueClient().find(IssueQuery.create().componentRoots("publicProject")).list().get(0); + + String user = "user"; + + try { + client.userClient().create(UserParameters.create().login(user).name(user).password("password").passwordConfirmation("password")); + addUserPermission(user, "publicProject", "issueadmin"); + + // Without issue admin permission, a user cannot set severity on the issue + try { + orchestrator.getServer().wsClient(user, "password").issueClient().setSeverity(issueOnPrivateProject.key(), "BLOCKER"); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(HttpException.class).describedAs("404"); + } + + // With issue admin permission, a user can set severity on the issue + assertThat(orchestrator.getServer().wsClient(user, "password").issueClient().setSeverity(issueOnPublicProject.key(), "BLOCKER").severity()).isEqualTo("BLOCKER"); + + } finally { + client.userClient().deactivate(user); + } + } + + /** + * SONAR-2447 + */ + @Test + public void need_administer_issue_permission_on_project_to_flag_as_false_positive() { + SonarClient client = orchestrator.getServer().adminWsClient(); + Issue issueOnPrivateProject = client.issueClient().find(IssueQuery.create().componentRoots("privateProject")).list().get(0); + Issue issueOnPublicProject = client.issueClient().find(IssueQuery.create().componentRoots("publicProject")).list().get(0); + + String user = "user"; + + try { + client.userClient().create(UserParameters.create().login(user).name(user).password("password").passwordConfirmation("password")); + addUserPermission(user, "publicProject", "issueadmin"); + + // Without issue admin permission, a user cannot flag an issue as false positive + try { + orchestrator.getServer().wsClient(user, "password").issueClient().doTransition(issueOnPrivateProject.key(), "falsepositive"); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(HttpException.class).describedAs("404"); + } + + // With issue admin permission, a user can flag an issue as false positive + assertThat(orchestrator.getServer().wsClient(user, "password").issueClient().doTransition(issueOnPublicProject.key(), "falsepositive").status()).isEqualTo("RESOLVED"); + + } finally { + client.userClient().deactivate(user); + } + } + + /** + * SONAR-2447 + */ + @Test + public void need_administer_issue_permission_on_project_to_bulk_change_severity_and_false_positive() { + SonarClient client = orchestrator.getServer().adminWsClient(); + Issue issueOnPrivateProject = client.issueClient().find(IssueQuery.create().componentRoots("privateProject")).list().get(0); + Issue issueOnPublicProject = client.issueClient().find(IssueQuery.create().componentRoots("publicProject")).list().get(0); + + String user = "user"; + + try { + client.userClient().create(UserParameters.create().login(user).name(user).password("password").passwordConfirmation("password")); + addUserPermission(user, "privateProject", "issueadmin"); + + Issues.BulkChangeWsResponse response = makeBlockerAndFalsePositive(user, issueOnPrivateProject, issueOnPublicProject); + + // public project but no issueadmin permission on publicProject => issue visible but not updated + // no user permission on privateproject => issue invisible and not updated + assertThat(response.getTotal()).isEqualTo(1); + assertThat(response.getSuccess()).isEqualTo(0); + assertThat(response.getIgnored()).isEqualTo(1); + + addUserPermission(user, "privateProject", "user"); + response = makeBlockerAndFalsePositive(user, issueOnPrivateProject, issueOnPublicProject); + + // public project but no issueadmin permission on publicProject => unsuccessful on issueOnPublicProject + // user and issueadmin permission on privateproject => successful and 1 more issue visible + assertThat(response.getTotal()).isEqualTo(2); + assertThat(response.getSuccess()).isEqualTo(1); + assertThat(response.getIgnored()).isEqualTo(1); + + addUserPermission(user, "publicProject", "issueadmin"); + response = makeBlockerAndFalsePositive(user, issueOnPrivateProject, issueOnPublicProject); + + // public and issueadmin permission on publicProject => successful on issueOnPublicProject + // issueOnPrivateProject already in specified state => unsuccessful + assertThat(response.getTotal()).isEqualTo(2); + assertThat(response.getSuccess()).isEqualTo(1); + assertThat(response.getIgnored()).isEqualTo(1); + + response = makeBlockerAndFalsePositive(user, issueOnPrivateProject, issueOnPublicProject); + + // issueOnPublicProject and issueOnPrivateProject already in specified state => unsuccessful + assertThat(response.getTotal()).isEqualTo(2); + assertThat(response.getSuccess()).isEqualTo(0); + assertThat(response.getIgnored()).isEqualTo(2); + } finally { + client.userClient().deactivate(user); + } + } + + private Issues.BulkChangeWsResponse makeBlockerAndFalsePositive(String user, Issue issueOnPrivateProject, Issue issueOnPublicProject) { + return newUserWsClient(orchestrator, user, "password").issues() + .bulkChange(BulkChangeRequest.builder().setIssues(asList(issueOnPrivateProject.key(), issueOnPublicProject.key())) + .setSetSeverity("BLOCKER") + .setDoTransition("falsepositive") + .build()); + } + + private void addUserPermission(String login, String projectKey, String permission) { + adminWsClient.permissions().addUser( + new AddUserWsRequest() + .setLogin(login) + .setProjectKey(projectKey) + .setPermission(permission)); + } + + private static Issues.ChangelogWsResponse changelog(String issueKey, String login, String password) { + return newUserWsClient(orchestrator, login, password).issues().changelog(issueKey); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/PermissionSearchTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/PermissionSearchTest.java new file mode 100644 index 00000000000..7698aa3bb8e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/authorisation/PermissionSearchTest.java @@ -0,0 +1,203 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.authorisation; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsPermissions; +import org.sonarqube.ws.WsPermissions.Permission; +import org.sonarqube.ws.WsPermissions.SearchTemplatesWsResponse; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.permission.AddGroupToTemplateWsRequest; +import org.sonarqube.ws.client.permission.AddGroupWsRequest; +import org.sonarqube.ws.client.permission.AddProjectCreatorToTemplateWsRequest; +import org.sonarqube.ws.client.permission.AddUserToTemplateWsRequest; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.permission.CreateTemplateWsRequest; +import org.sonarqube.ws.client.permission.GroupsWsRequest; +import org.sonarqube.ws.client.permission.PermissionsService; +import org.sonarqube.ws.client.permission.RemoveGroupFromTemplateWsRequest; +import org.sonarqube.ws.client.permission.RemoveProjectCreatorFromTemplateWsRequest; +import org.sonarqube.ws.client.permission.RemoveUserFromTemplateWsRequest; +import org.sonarqube.ws.client.permission.SearchTemplatesWsRequest; +import org.sonarqube.ws.client.permission.UsersWsRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +public class PermissionSearchTest { + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + private static WsClient adminWsClient; + private static PermissionsService permissionsWsClient; + + private static final String PROJECT_KEY = "sample"; + private static final String LOGIN = "george.orwell"; + private static final String GROUP_NAME = "1984"; + + @BeforeClass + public static void analyzeProject() { + orchestrator.resetData(); + + ItUtils.restoreProfile(orchestrator, PermissionSearchTest.class.getResource("/authorisation/one-issue-per-line-profile.xml")); + + orchestrator.getServer().provisionProject(PROJECT_KEY, "Sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-sample")); + orchestrator.executeBuild(sampleProject); + + adminWsClient = newAdminWsClient(orchestrator); + permissionsWsClient = adminWsClient.permissions(); + + createUser(LOGIN, "George Orwell"); + createGroup(GROUP_NAME); + } + + @AfterClass + public static void delete_data() { + deactivateUser(LOGIN); + deleteGroup(GROUP_NAME); + } + + @Test + public void permission_web_services() { + permissionsWsClient.addUser( + new AddUserWsRequest() + .setPermission("admin") + .setLogin(LOGIN)); + permissionsWsClient.addGroup( + new AddGroupWsRequest() + .setPermission("admin") + .setGroupName(GROUP_NAME)); + + WsPermissions.WsSearchGlobalPermissionsResponse searchGlobalPermissionsWsResponse = permissionsWsClient.searchGlobalPermissions(); + assertThat(searchGlobalPermissionsWsResponse.getPermissionsList().get(0).getKey()).isEqualTo("admin"); + assertThat(searchGlobalPermissionsWsResponse.getPermissionsList().get(0).getUsersCount()).isEqualTo(1); + // by default, a group has the global admin permission + assertThat(searchGlobalPermissionsWsResponse.getPermissionsList().get(0).getGroupsCount()).isEqualTo(2); + + WsPermissions.UsersWsResponse users = permissionsWsClient + .users(new UsersWsRequest().setPermission("admin")); + assertThat(users.getUsersList()).extracting("login").contains(LOGIN); + + WsPermissions.WsGroupsResponse groupsResponse = permissionsWsClient + .groups(new GroupsWsRequest() + .setPermission("admin")); + assertThat(groupsResponse.getGroupsList()).extracting("name").contains(GROUP_NAME); + } + + @Test + public void template_permission_web_services() { + WsPermissions.CreateTemplateWsResponse createTemplateWsResponse = permissionsWsClient.createTemplate( + new CreateTemplateWsRequest() + .setName("my-new-template") + .setDescription("template-used-in-tests")); + assertThat(createTemplateWsResponse.getPermissionTemplate().getName()).isEqualTo("my-new-template"); + + permissionsWsClient.addUserToTemplate( + new AddUserToTemplateWsRequest() + .setPermission("admin") + .setTemplateName("my-new-template") + .setLogin(LOGIN)); + + permissionsWsClient.addGroupToTemplate( + new AddGroupToTemplateWsRequest() + .setPermission("admin") + .setTemplateName("my-new-template") + .setGroupName(GROUP_NAME)); + + permissionsWsClient.addProjectCreatorToTemplate( + AddProjectCreatorToTemplateWsRequest.builder() + .setPermission("admin") + .setTemplateName("my-new-template") + .build()); + + SearchTemplatesWsResponse searchTemplatesWsResponse = permissionsWsClient.searchTemplates( + new SearchTemplatesWsRequest() + .setQuery("my-new-template")); + assertThat(searchTemplatesWsResponse.getPermissionTemplates(0).getName()).isEqualTo("my-new-template"); + assertThat(searchTemplatesWsResponse.getPermissionTemplates(0).getPermissions(0).getKey()).isEqualTo("admin"); + assertThat(searchTemplatesWsResponse.getPermissionTemplates(0).getPermissions(0).getUsersCount()).isEqualTo(1); + assertThat(searchTemplatesWsResponse.getPermissionTemplates(0).getPermissions(0).getGroupsCount()).isEqualTo(1); + assertThat(searchTemplatesWsResponse.getPermissionTemplates(0).getPermissions(0).getWithProjectCreator()).isTrue(); + + permissionsWsClient.removeGroupFromTemplate( + new RemoveGroupFromTemplateWsRequest() + .setPermission("admin") + .setTemplateName("my-new-template") + .setGroupName(GROUP_NAME)); + + permissionsWsClient.removeUserFromTemplate( + new RemoveUserFromTemplateWsRequest() + .setPermission("admin") + .setTemplateName("my-new-template") + .setLogin(LOGIN)); + + permissionsWsClient.removeProjectCreatorFromTemplate( + RemoveProjectCreatorFromTemplateWsRequest.builder() + .setPermission("admin") + .setTemplateName("my-new-template") + .build() + ); + + SearchTemplatesWsResponse clearedSearchTemplatesWsResponse = permissionsWsClient.searchTemplates( + new SearchTemplatesWsRequest() + .setQuery("my-new-template")); + assertThat(clearedSearchTemplatesWsResponse.getPermissionTemplates(0).getPermissionsList()) + .extracting(Permission::getUsersCount, Permission::getGroupsCount, Permission::getWithProjectCreator) + .hasSize(5) + .containsOnly(tuple(0, 0, false)); + } + + private static void createUser(String login, String name) { + adminWsClient.wsConnector().call( + new PostRequest("api/users/create") + .setParam("login", login) + .setParam("name", name) + .setParam("password", "123456")); + } + + private static void deactivateUser(String login) { + adminWsClient.wsConnector().call( + new PostRequest("api/users/deactivate") + .setParam("login", login)); + } + + private static void createGroup(String groupName) { + adminWsClient.wsConnector().call( + new PostRequest("api/user_groups/create") + .setParam("name", groupName)); + } + + private static void deleteGroup(String groupName) { + adminWsClient.wsConnector().call( + new PostRequest("api/user_groups/delete") + .setParam("name", groupName)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/PermissionTemplatesPageTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/PermissionTemplatesPageTest.java new file mode 100644 index 00000000000..f94a50bb0b3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/authorisation/PermissionTemplatesPageTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.authorisation; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.ClassRule; +import org.junit.Test; + +import static util.selenium.Selenese.runSelenese; + +public class PermissionTemplatesPageTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Test + public void should_display_page() throws Exception { + runSelenese(orchestrator, + "/authorisation/PermissionTemplatesPageTest/should_display_page.html", + "/authorisation/PermissionTemplatesPageTest/should_create.html"); + } + + @Test + public void should_manage_project_creators() throws Exception { + runSelenese(orchestrator, "/authorisation/PermissionTemplatesPageTest/should_manage_project_creators.html"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/ProvisioningPermissionTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/ProvisioningPermissionTest.java new file mode 100644 index 00000000000..8d3b7ac007a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/authorisation/ProvisioningPermissionTest.java @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.authorisation; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.ws.WsProjects.CreateWsResponse.Project; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.permission.AddGroupWsRequest; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.permission.PermissionsService; +import org.sonarqube.ws.client.permission.RemoveGroupWsRequest; +import org.sonarqube.ws.client.permission.RemoveUserWsRequest; +import org.sonarqube.ws.client.project.CreateRequest; +import util.user.UserRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.selenium.Selenese.runSelenese; + +public class ProvisioningPermissionTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @ClassRule + public static UserRule userRule = UserRule.from(orchestrator); + + private static final String PASSWORD = "password"; + + private static final String ADMIN_WITH_PROVISIONING = "admin-with-provisioning"; + private static final String ADMIN_WITHOUT_PROVISIONING = "admin-without-provisioning"; + private static final String USER_WITH_PROVISIONING = "user-with-provisioning"; + private static final String USER_WITHOUT_PROVISIONING = "user-without-provisioning"; + + private static PermissionsService permissionsWsClient; + + @BeforeClass + public static void init() { + permissionsWsClient = newAdminWsClient(orchestrator).permissions(); + + // remove default permission "provisioning" from anyone(); + permissionsWsClient.removeGroup(new RemoveGroupWsRequest().setGroupName("anyone").setPermission("provisioning")); + + userRule.createUser(ADMIN_WITH_PROVISIONING, PASSWORD); + addUserPermission(ADMIN_WITH_PROVISIONING, "admin"); + addUserPermission(ADMIN_WITH_PROVISIONING, "provisioning"); + + userRule.createUser(ADMIN_WITHOUT_PROVISIONING, PASSWORD); + addUserPermission(ADMIN_WITHOUT_PROVISIONING, "admin"); + removeUserPermission(ADMIN_WITHOUT_PROVISIONING, "provisioning"); + + userRule.createUser(USER_WITH_PROVISIONING, PASSWORD); + addUserPermission(USER_WITH_PROVISIONING, "provisioning"); + + userRule.createUser(USER_WITHOUT_PROVISIONING, PASSWORD); + removeUserPermission(USER_WITHOUT_PROVISIONING, "provisioning"); + } + + @AfterClass + public static void restoreData() throws Exception { + userRule.resetUsers(); + permissionsWsClient.addGroup(new AddGroupWsRequest().setGroupName("anyone").setPermission("provisioning")); + } + + /** + * SONAR-3871 + * SONAR-4709 + */ + @Test + public void organization_administrator_cannot_provision_project_if_he_doesnt_have_provisioning_permission() { + runSelenese(orchestrator, "/authorisation/ProvisioningPermissionTest/should-not-be-able-to-provision-project.html"); + } + + /** + * SONAR-3871 + * SONAR-4709 + */ + @Test + public void organization_administrator_can_provision_project_if_he_has_provisioning_permission() { + runSelenese(orchestrator, "/authorisation/ProvisioningPermissionTest/should-be-able-to-provision-project.html"); + } + + /** + * SONAR-3871 + * SONAR-4709 + */ + @Test + public void user_can_provision_project_through_ws_if_he_has_provisioning_permission() { + final String newKey = "new-project"; + final String newName = "New Project"; + + Project created = newUserWsClient(orchestrator, USER_WITH_PROVISIONING, PASSWORD).projects() + .create(CreateRequest.builder().setKey(newKey).setName(newName).build()) + .getProject(); + + assertThat(created).isNotNull(); + assertThat(created.getKey()).isEqualTo(newKey); + assertThat(created.getName()).isEqualTo(newName); + } + + /** + * SONAR-3871 + * SONAR-4709 + */ + @Test + public void user_cannot_provision_project_through_ws_if_he_doesnt_have_provisioning_permission() { + thrown.expect(HttpException.class); + thrown.expectMessage("403"); + + newUserWsClient(orchestrator, USER_WITHOUT_PROVISIONING, PASSWORD).projects() + .create(CreateRequest.builder().setKey("new-project").setName("New Project").build()) + .getProject(); + } + + private static void addUserPermission(String login, String permission) { + permissionsWsClient.addUser(new AddUserWsRequest().setLogin(login).setPermission(permission)); + } + + private static void removeUserPermission(String login, String permission) { + permissionsWsClient.removeUser(new RemoveUserWsRequest().setLogin(login).setPermission(permission)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/QualityProfileAdminPermissionTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/QualityProfileAdminPermissionTest.java new file mode 100644 index 00000000000..65193eb1bee --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/authorisation/QualityProfileAdminPermissionTest.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.authorisation; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import util.user.UserRule; + +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.selenium.Selenese.runSelenese; + +/** + * SONAR-4210 + */ +public class QualityProfileAdminPermissionTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @ClassRule + public static UserRule userRule = UserRule.from(orchestrator); + + private static WsClient adminWsClient; + + @BeforeClass + public static void init() { + orchestrator.resetData(); + adminWsClient = newAdminWsClient(orchestrator); + runProjectAnalysis(orchestrator, "shared/xoo-sample"); + } + + @AfterClass + public static void clearUsers() throws Exception { + userRule.resetUsers(); + } + + @Test + public void permission_should_grant_access_to_profile() { + userRule.createUser("not_profileadm", "userpwd"); + userRule.createUser("profileadm", "papwd"); + adminWsClient.permissions().addUser(new AddUserWsRequest().setLogin("profileadm").setPermission("profileadmin")); + createProfile("xoo", "foo"); + + runSelenese(orchestrator, + // Verify normal user is not allowed to do any modification + "/authorisation/QualityProfileAdminPermissionTest/normal-user.html", + // Verify profile admin is allowed to do modifications + "/authorisation/QualityProfileAdminPermissionTest/profile-admin.html"); + } + + private static void createProfile(String language, String name) { + adminWsClient.wsConnector().call( + new PostRequest("api/qualityprofiles/create") + .setParam("language", language) + .setParam("name", name)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/ce/CeWsTest.java b/tests/src/test/java/org/sonarqube/tests/ce/CeWsTest.java new file mode 100644 index 00000000000..e6faf6ec58c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ce/CeWsTest.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ce; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsCe; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.ce.ActivityWsRequest; +import util.ItUtils; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class CeWsTest { + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + private WsClient wsClient; + private String taskUuid; + + @Before + public void inspectProject() { + orchestrator.resetData(); + BuildResult buildResult = orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + this.taskUuid = ItUtils.extractCeTaskId(buildResult); + this.wsClient = ItUtils.newAdminWsClient(orchestrator); + } + + @Test + public void activity() { + WsCe.ActivityResponse response = wsClient.ce().activity(new ActivityWsRequest() + .setStatus(newArrayList("SUCCESS")) + .setType("REPORT") + .setOnlyCurrents(true) + .setPage(1) + .setPageSize(100)); + + assertThat(response).isNotNull(); + assertThat(response.getTasksCount()).isGreaterThan(0); + WsCe.Task firstTask = response.getTasks(0); + assertThat(firstTask.getId()).isNotEmpty(); + } + + @Test + public void task() { + WsCe.TaskResponse taskResponse = wsClient.ce().task(taskUuid); + + assertThat(taskResponse.hasTask()).isTrue(); + WsCe.Task task = taskResponse.getTask(); + assertThat(task.getId()).isEqualTo(taskUuid); + assertThat(task.hasErrorMessage()).isFalse(); + assertThat(task.hasHasScannerContext()).isTrue(); + assertThat(task.getScannerContext()).isNotNull(); + } + + @Test + public void task_types() { + WsCe.TaskTypesWsResponse response = wsClient.ce().taskTypes(); + + assertThat(response).isNotNull(); + assertThat(response.getTaskTypesCount()).isGreaterThan(0); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/complexity/ComplexityMeasuresTest.java b/tests/src/test/java/org/sonarqube/tests/complexity/ComplexityMeasuresTest.java new file mode 100644 index 00000000000..3eb28da0ccc --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/complexity/ComplexityMeasuresTest.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.complexity; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.projectDir; + +// TODO complete the test with other complexity metrics +public class ComplexityMeasuresTest { + + private static final String PROJECT = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + private static final String SUB_MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"; + private static final String DIRECTORY = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1"; + private static final String FILE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo"; + + private static final String COMPLEXITY_METRIC = "complexity"; + private static final String COGNITIVE_COMPLEXITY_METRIC = "cognitive_complexity"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @BeforeClass + public static void inspectProject() { + orchestrator.resetData(); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + } + + @Test + public void compute_complexity_metrics_on_file() { + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, FILE, COMPLEXITY_METRIC, COGNITIVE_COMPLEXITY_METRIC)).containsOnly( + entry(COMPLEXITY_METRIC, 3d), + entry(COGNITIVE_COMPLEXITY_METRIC, 4d)); + } + + @Test + public void compute_complexity_metrics_on_directory() { + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, DIRECTORY, COMPLEXITY_METRIC, COGNITIVE_COMPLEXITY_METRIC)).containsOnly( + entry(COMPLEXITY_METRIC, 3d), + entry(COGNITIVE_COMPLEXITY_METRIC, 4d)); + } + + @Test + public void compute_complexity_metrics_on_sub_module() { + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, SUB_MODULE, COMPLEXITY_METRIC, COGNITIVE_COMPLEXITY_METRIC)).containsOnly( + entry(COMPLEXITY_METRIC, 3d), + entry(COGNITIVE_COMPLEXITY_METRIC, 4d)); + } + + @Test + public void compute_complexity_metrics_on_module() { + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, MODULE, COMPLEXITY_METRIC, COGNITIVE_COMPLEXITY_METRIC)).containsOnly( + entry(COMPLEXITY_METRIC, 7d), + entry(COGNITIVE_COMPLEXITY_METRIC, 9d)); + } + + @Test + public void compute_complexity_metrics_on_project() { + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, PROJECT, COMPLEXITY_METRIC, COGNITIVE_COMPLEXITY_METRIC)).containsOnly( + entry(COMPLEXITY_METRIC, 13d), + entry(COGNITIVE_COMPLEXITY_METRIC, 17d)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/component/ComponentsWsTest.java b/tests/src/test/java/org/sonarqube/tests/component/ComponentsWsTest.java new file mode 100644 index 00000000000..69cae2a8d34 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/component/ComponentsWsTest.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.component; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.component.SearchWsRequest; +import org.sonarqube.ws.client.component.ShowWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class ComponentsWsTest { + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + private static final String FILE_KEY = "sample:src/main/xoo/sample/Sample.xoo"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + WsClient wsClient; + + @Before + public void inspectProject() { + orchestrator.resetData(); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + wsClient = ItUtils.newAdminWsClient(orchestrator); + } + + @Test + public void show() { + WsComponents.ShowWsResponse response = wsClient.components().show(new ShowWsRequest().setKey(FILE_KEY)); + + assertThat(response).isNotNull(); + assertThat(response.getComponent().getKey()).isEqualTo(FILE_KEY); + assertThat(response.getAncestorsList()).isNotEmpty(); + } + + @Test + public void search() { + WsComponents.SearchWsResponse response = wsClient.components().search(new SearchWsRequest() + .setQualifiers(singletonList("FIL"))); + + assertThat(response).isNotNull(); + assertThat(response.getComponents(0).getKey()).isEqualTo(FILE_KEY); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/component/ProjectsWsTest.java b/tests/src/test/java/org/sonarqube/tests/component/ProjectsWsTest.java new file mode 100644 index 00000000000..75d5f5bae16 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/component/ProjectsWsTest.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.component; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.io.IOException; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.WsProjects.BulkUpdateKeyWsResponse; +import org.sonarqube.ws.WsProjects.BulkUpdateKeyWsResponse.Key; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.component.ShowWsRequest; +import org.sonarqube.ws.client.project.BulkUpdateKeyWsRequest; +import org.sonarqube.ws.client.project.UpdateKeyWsRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class ProjectsWsTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + private static final String PROJECT_KEY = "sample"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private WsClient wsClient; + + @Before + public void inspectProject() { + orchestrator.resetData(); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + wsClient = ItUtils.newAdminWsClient(orchestrator); + } + + /** + * SONAR-3105 + */ + @Test + public void projects_web_service() throws IOException { + SonarScanner build = SonarScanner.create(projectDir("shared/xoo-sample")); + orchestrator.executeBuild(build); + + String url = orchestrator.getServer().getUrl() + "/api/projects/index?key=sample&versions=true"; + HttpClient httpclient = new DefaultHttpClient(); + try { + HttpGet get = new HttpGet(url); + HttpResponse response = httpclient.execute(get); + + assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200); + String content = IOUtils.toString(response.getEntity().getContent()); + assertThat(content).doesNotContain("error"); + assertThat(content).contains("sample"); + EntityUtils.consume(response.getEntity()); + + } finally { + httpclient.getConnectionManager().shutdown(); + } + } + + @Test + public void update_key() { + String newProjectKey = "another_project_key"; + WsComponents.Component project = wsClient.components().show(new ShowWsRequest().setKey(PROJECT_KEY)).getComponent(); + assertThat(project.getKey()).isEqualTo(PROJECT_KEY); + + wsClient.projects().updateKey(UpdateKeyWsRequest.builder() + .setKey(PROJECT_KEY) + .setNewKey(newProjectKey) + .build()); + + assertThat(wsClient.components().show(new ShowWsRequest().setId(project.getId())).getComponent().getKey()).isEqualTo(newProjectKey); + } + + @Test + public void bulk_update_key() { + String newProjectKey = "another_project_key"; + WsComponents.Component project = wsClient.components().show(new ShowWsRequest().setKey(PROJECT_KEY)).getComponent(); + assertThat(project.getKey()).isEqualTo(PROJECT_KEY); + + BulkUpdateKeyWsResponse result = wsClient.projects().bulkUpdateKey(BulkUpdateKeyWsRequest.builder() + .setKey(PROJECT_KEY) + .setFrom(PROJECT_KEY) + .setTo(newProjectKey) + .build()); + + assertThat(wsClient.components().show(new ShowWsRequest().setId(project.getId())).getComponent().getKey()).isEqualTo(newProjectKey); + assertThat(result.getKeysCount()).isEqualTo(1); + assertThat(result.getKeys(0)) + .extracting(Key::getKey, Key::getNewKey, Key::getDuplicate) + .containsOnlyOnce(PROJECT_KEY, newProjectKey, false); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/customMeasure/CustomMeasuresTest.java b/tests/src/test/java/org/sonarqube/tests/customMeasure/CustomMeasuresTest.java new file mode 100644 index 00000000000..206f3eb35dd --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/customMeasure/CustomMeasuresTest.java @@ -0,0 +1,117 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.customMeasure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class CustomMeasuresTest { + + private static final String PROJECT_KEY = "sample"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Before + public void deleteProjects() { + orchestrator.resetData(); + } + + @Test + public void custom_measures_should_be_integrated_during_project_analysis() { + analyzeProject(); + setBurnedBudget(1200.3); + setTeamSize(4); + + assertThat(getMeasureAsDouble("team_size")).isNull(); + assertThat(getMeasureAsDouble("burned_budget")).isNull(); + + analyzeProject(); + + assertThat(getMeasureAsDouble("burned_budget")).isEqualTo(1200.3); + assertThat(getMeasureAsDouble("team_size")).isEqualTo(4d); + } + + @Test + public void should_update_value() { + analyzeProject(); + setTeamSize(4); + analyzeProject(); + updateTeamSize(15); + assertThat(getMeasureAsDouble("team_size")).isEqualTo(4d); + analyzeProject();// the value is available when the project is analyzed again + assertThat(getMeasureAsDouble("team_size")).isEqualTo(15d); + } + + @Test + public void should_delete_custom_measure() { + analyzeProject(); + setTeamSize(4); + analyzeProject(); + deleteCustomMeasure("team_size"); + assertThat(getMeasureAsDouble("team_size")).isEqualTo(4d);// the value is still available. It will be removed during next + // analyzed + + analyzeProject(); + assertThat(getMeasureAsDouble("team_size")).isNull(); + } + + private void analyzeProject() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + } + + private void setTeamSize(int i) { + orchestrator.getServer().adminWsClient().post("api/custom_measures/create", "projectKey", PROJECT_KEY, "metricKey", "team_size", "value", String.valueOf(i)); + } + + private void updateTeamSize(int i) { + String response = orchestrator.getServer().adminWsClient().get("api/custom_measures/search", "projectKey", PROJECT_KEY, "metricKey", "team_size"); + Matcher jsonObjectMatcher = Pattern.compile(".*?\"id\"\\s*:\\s*\"(.*?)\".*", Pattern.MULTILINE).matcher(response); + jsonObjectMatcher.find(); + String customMeasureId = jsonObjectMatcher.group(1); + orchestrator.getServer().adminWsClient().post("api/custom_measures/update", "id", customMeasureId, "value", String.valueOf(i)); + } + + private void setBurnedBudget(double d) { + orchestrator.getServer().adminWsClient().post("api/custom_measures/create", "projectKey", PROJECT_KEY, "metricKey", "burned_budget", "value", String.valueOf(d)); + } + + private void deleteCustomMeasure(String metricKey) { + String response = orchestrator.getServer().adminWsClient().get("api/custom_measures/search", "projectKey", PROJECT_KEY, "metricKey", metricKey); + Matcher jsonObjectMatcher = Pattern.compile(".*?\"id\"\\s*:\\s*\"(.*?)\".*", Pattern.MULTILINE).matcher(response); + jsonObjectMatcher.find(); + String customMeasureId = jsonObjectMatcher.group(1); + orchestrator.getServer().adminWsClient().post("api/custom_measures/delete", "id", customMeasureId); + } + + private Double getMeasureAsDouble(String metricKey) { + return ItUtils.getMeasureAsDouble(orchestrator, PROJECT_KEY, metricKey); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/dbCleaner/PurgeTest.java b/tests/src/test/java/org/sonarqube/tests/dbCleaner/PurgeTest.java new file mode 100644 index 00000000000..dc4bb0fcc78 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/dbCleaner/PurgeTest.java @@ -0,0 +1,377 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.dbCleaner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang.time.DateFormatUtils; +import org.apache.commons.lang.time.DateUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryValue; +import org.sonarqube.ws.client.measure.SearchHistoryRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.apache.commons.lang.time.DateUtils.addDays; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static util.ItUtils.formatDate; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +@Ignore +public class PurgeTest { + + private static final String COUNT_FILE_MEASURES = "project_measures pm, projects p where p.uuid = pm.component_uuid and p.scope='FIL'"; + private static final String COUNT_DIR_MEASURES = "project_measures pm, projects p where p.uuid = pm.component_uuid and p.scope='DIR'"; + private static final String PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String PROJECT_SAMPLE_PATH = "dbCleaner/xoo-multi-modules-sample"; + + private static final String ONE_DAY_AGO = DateFormatUtils.ISO_DATE_FORMAT.format(addDays(new Date(), -1)); + private static final String TWO_DAYS_AGO = DateFormatUtils.ISO_DATE_FORMAT.format(addDays(new Date(), -2)); + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public ErrorCollector collector = new ErrorCollector(); + + @Before + public void deleteProjectData() { + orchestrator.resetData(); + + orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_KEY); + + ItUtils.restoreProfile(orchestrator, getClass().getResource("/dbCleaner/one-issue-per-line-profile.xml")); + + setServerProperty(orchestrator, "sonar.dbcleaner.cleanDirectory", null); + setServerProperty(orchestrator, "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay", null); + setServerProperty(orchestrator, "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek", null); + } + + @Test + public void test_evolution_of_number_of_rows_when_scanning_two_times_the_same_project() { + Date today = new Date(); + Date yesterday = DateUtils.addDays(today, -1); + + scan(PROJECT_SAMPLE_PATH, DateFormatUtils.ISO_DATE_FORMAT.format(yesterday)); + + // count components + collector.checkThat("Wrong number of projects", count("projects where qualifier in ('TRK','BRC')"), equalTo(7)); + collector.checkThat("Wrong number of directories", count("projects where qualifier in ('DIR')"), equalTo(4)); + collector.checkThat("Wrong number of files", count("projects where qualifier in ('FIL')"), equalTo(4)); + collector.checkThat("Wrong number of unit test files", count("projects where qualifier in ('UTS')"), equalTo(0)); + + int measuresOnTrk = 45; + int measuresOnBrc = 222; + int measuresOnDir = 141; + int measuresOnFil = 69; + + // count measures + assertMeasuresCountForQualifier("TRK", measuresOnTrk); + assertMeasuresCountForQualifier("BRC", measuresOnBrc); + assertMeasuresCountForQualifier("DIR", measuresOnDir); + assertMeasuresCountForQualifier("FIL", measuresOnFil); + + // No new_* metrics measure should be recorded the first time + collector.checkThat( + "Wrong number of measure of new_ metrics", + count("project_measures, metrics where metrics.id = project_measures.metric_id and metrics.name like 'new_%'"), + equalTo(0)); + + int expectedMeasures = measuresOnTrk + measuresOnBrc + measuresOnDir + measuresOnFil; + collector.checkThat("Wrong number of measures", count("project_measures"), equalTo(expectedMeasures)); + collector.checkThat("Wrong number of measure data", count("project_measures where measure_data is not null"), equalTo(0)); + + // count other tables that are constant between 2 scans + int expectedIssues = 52; + + collector.checkThat("Wrong number of issues", count("issues"), equalTo(expectedIssues)); + + // must be a different date, else a single snapshot is kept per day + scan(PROJECT_SAMPLE_PATH, DateFormatUtils.ISO_DATE_FORMAT.format(today)); + + int newMeasuresOnTrk = 58; + int newMeasuresOnBrc = 304; + int newMeasuresOnDir = 56; + int newMeasuresOnFil = 0; + + assertMeasuresCountForQualifier("TRK", measuresOnTrk + newMeasuresOnTrk); + assertMeasuresCountForQualifier("BRC", measuresOnBrc + newMeasuresOnBrc); + assertMeasuresCountForQualifier("DIR", measuresOnDir + newMeasuresOnDir); + assertMeasuresCountForQualifier("FIL", measuresOnFil + newMeasuresOnFil); + + // Measures on new_* metrics should be recorded + collector.checkThat( + "Wrong number of measure of new_ metrics", + count("project_measures, metrics where metrics.id = project_measures.metric_id and metrics.name like 'new_%'"), + equalTo(154)); + + // added measures relate to project and new_* metrics + expectedMeasures += newMeasuresOnTrk + newMeasuresOnBrc + newMeasuresOnDir + newMeasuresOnFil; + collector.checkThat("Wrong number of measures after second analysis", count("project_measures"), equalTo(expectedMeasures)); + collector.checkThat("Wrong number of measure data", count("project_measures where measure_data is not null"), equalTo(0)); + collector.checkThat("Wrong number of issues", count("issues"), equalTo(expectedIssues)); + } + + /** + * SONAR-3378 + */ + @Test + public void should_keep_all_snapshots_the_first_day() { + // analyse once + scan(PROJECT_SAMPLE_PATH); + // analyse twice + scan(PROJECT_SAMPLE_PATH); + // and check we have 2 snapshots + assertThat(count("snapshots s where s.component_uuid=(select p.uuid from projects p where p.kee='com.sonarsource.it.samples:multi-modules-sample')")).isEqualTo(2); + } + + /** + * SONAR-2807 & SONAR-3378 & SONAR-4710 + */ + @Test + public void should_keep_only_one_snapshot_per_day() { + scan(PROJECT_SAMPLE_PATH); + + int snapshotsCount = count("snapshots"); + int measuresCount = count("project_measures"); + // Using the "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay" property set to '0' is the way + // to keep only 1 snapshot per day + setServerProperty(orchestrator, "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay", "0"); + scan(PROJECT_SAMPLE_PATH); + assertThat(count("snapshots")).as("Different number of snapshots").isEqualTo(snapshotsCount); + + int measureOnNewMetrics = count("project_measures, metrics where metrics.id = project_measures.metric_id and metrics.name like 'new_%'"); + // Number of measures should be the same as previous, with the measures on new metrics + assertThat(count("project_measures")).as("Different number of measures").isEqualTo(measuresCount + measureOnNewMetrics); + } + + /** + * SONAR-7175 + */ + @Test + public void keep_latest_snapshot() { + // Keep all snapshots from last 4 weeks + setServerProperty(orchestrator, "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek", "4"); + + Date oneWeekAgo = addDays(new Date(), -7); + + // Execute an analysis wednesday last week + Calendar lastWednesday = Calendar.getInstance(); + lastWednesday.setTime(oneWeekAgo); + lastWednesday.set(Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY); + String lastWednesdayFormatted = formatDate(lastWednesday.getTime()); + runProjectAnalysis(orchestrator, PROJECT_SAMPLE_PATH, "sonar.projectDate", lastWednesdayFormatted); + + // Execute an analysis thursday last week + Calendar lastThursday = Calendar.getInstance(); + lastThursday.setTime(oneWeekAgo); + lastThursday.set(Calendar.DAY_OF_WEEK, Calendar.THURSDAY); + String lastThursdayFormatted = formatDate(lastThursday.getTime()); + runProjectAnalysis(orchestrator, PROJECT_SAMPLE_PATH, "sonar.projectDate", lastThursdayFormatted); + + // Now only keep 1 snapshot per week + setServerProperty(orchestrator, "sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek", "0"); + + // Execute an analysis today to execute the purge of previous weeks snapshots + runProjectAnalysis(orchestrator, PROJECT_SAMPLE_PATH); + + // Check that only analysis from last thursday is kept (as it's the last one from previous week) + WsMeasures.SearchHistoryResponse response = newAdminWsClient(orchestrator).measures().searchHistory(SearchHistoryRequest.builder() + .setComponent(PROJECT_KEY) + .setMetrics(singletonList("ncloc")) + .build()); + assertThat(response.getMeasuresCount()).isEqualTo(1); + assertThat(response.getMeasuresList().get(0).getHistoryList()).extracting(HistoryValue::getDate).doesNotContain(lastWednesdayFormatted, lastThursdayFormatted); + } + + /** + * SONAR-3120 + */ + @Test + public void should_delete_removed_modules() { + scan("dbCleaner/modules/before"); + assertExists("com.sonarsource.it.samples:multi-modules-sample:module_b"); + assertExists("com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1"); + + // we want the previous snapshot to be purged + setServerProperty(orchestrator, "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay", "0"); + + scan("dbCleaner/modules/after"); + assertDisabled("com.sonarsource.it.samples:multi-modules-sample:module_b"); + assertDisabled("com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1"); + assertExists("com.sonarsource.it.samples:multi-modules-sample:module_c:module_c1"); + } + + /** + * SONAR-3120 + */ + @Test + public void should_delete_removed_files() { + String fileKey = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo"; + scan("dbCleaner/files/before"); + assertExists(fileKey); + + scan("dbCleaner/files/after"); + assertDisabled(fileKey); + assertExists("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/NewHelloA1.xoo"); + } + + /** + * SONAR-2754 + */ + @Test + public void should_delete_historical_data_of_directories_by_default() { + scan(PROJECT_SAMPLE_PATH, TWO_DAYS_AGO); + + int fileMeasures = count(COUNT_FILE_MEASURES); + int dirMeasures = count(COUNT_DIR_MEASURES); + + scan(PROJECT_SAMPLE_PATH, ONE_DAY_AGO); + + // second analysis with new_* metrics + assertThat(count(COUNT_FILE_MEASURES)).isLessThan(2 * fileMeasures); + assertThat(count(COUNT_DIR_MEASURES)).isLessThan(2 * dirMeasures); + } + + /** + * SONAR-2754 + */ + @Test + public void should_not_delete_historical_data_of_directories() { + scan(PROJECT_SAMPLE_PATH, TWO_DAYS_AGO); + + int fileMeasures = count(COUNT_FILE_MEASURES); + int dirMeasures = count(COUNT_DIR_MEASURES); + + setServerProperty(orchestrator, "sonar.dbcleaner.cleanDirectory", "false"); + + scan(PROJECT_SAMPLE_PATH, ONE_DAY_AGO); + + // second analysis as NEW_* metrics + assertThat(count(COUNT_FILE_MEASURES)).isLessThan(2 * fileMeasures); + assertThat(count(COUNT_DIR_MEASURES)).isGreaterThan(2 * dirMeasures); + } + + /** + * SONAR-2061 + */ + @Test + public void should_delete_historical_data_of_flagged_metrics() { + scan(PROJECT_SAMPLE_PATH, TWO_DAYS_AGO); + + // historical data of complexity_in_classes is supposed to be deleted (see CoreMetrics) + String selectNcloc = "project_measures where metric_id in (select id from metrics where name='ncloc')"; + String selectComplexityInClasses = "project_measures where metric_id in (select id from metrics where name='complexity_in_classes')"; + int nclocCount = count(selectNcloc); + int complexitInClassesCount = count(selectComplexityInClasses); + + scan(PROJECT_SAMPLE_PATH, ONE_DAY_AGO); + assertThat(count(selectNcloc)).isGreaterThan(nclocCount); + assertThat(count(selectComplexityInClasses)).isEqualTo(complexitInClassesCount); + } + + private void assertDisabled(String key) { + assertThat(enabledStatusOfComponent(key)).isFalse(); + } + + private void assertExists(String key) { + assertThat(enabledStatusOfComponent(key)).isTrue(); + } + + private Boolean enabledStatusOfComponent(String key) { + return orchestrator.getDatabase().executeSql("select enabled from projects p where p.kee='" + key + "'") + .stream() + .findFirst() + .map(PurgeTest::toBoolean) + .orElse(null); + } + + private static Boolean toBoolean(Map<String, String> s) { + String value = s.get("ENABLED"); + if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("t") || value.equals("1")) { + return true; + } + if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("f") || value.equals("0")) { + return false; + } + throw new IllegalArgumentException("Unsupported value can not be converted to boolean " + value); + } + + private BuildResult scan(String path, String date) { + return scan(path, "sonar.projectDate", date); + } + + private BuildResult scan(String path, String... extraProperties) { + SonarScanner runner = configureRunner(path, extraProperties); + return orchestrator.executeBuild(runner); + } + + private SonarScanner configureRunner(String projectPath, String... props) { + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "one-issue-per-line-profile"); + return SonarScanner.create(ItUtils.projectDir(projectPath)).setProperties(props); + } + + private int count(String condition) { + return orchestrator.getDatabase().countSql("select count(1) from " + condition); + } + + private void assertMeasuresCountForQualifier(String qualifier, int count) { + int result = countMeasures(qualifier); + if (result != count) { + logMeasures("GOT", qualifier); + } + collector.checkThat("Wrong number of measures for qualifier " + qualifier, result, equalTo(count)); + } + + private int countMeasures(String qualifier) { + String sql = "SELECT count(1) FROM project_measures pm, projects p, metrics m where p.uuid=pm.component_uuid and pm.metric_id=m.id and p.qualifier='" + qualifier + "'"; + return orchestrator.getDatabase().countSql(sql); + } + + private void logMeasures(String title, String qualifier) { + String sql = "SELECT m.name as metricName, pm.value as value, pm.text_value as textValue, pm.variation_value_1, pm.variation_value_2, pm.variation_value_3 " + + + "FROM project_measures pm, projects p, metrics m " + + "WHERE pm.component_uuid=p.uuid and pm.metric_id=m.id and p.qualifier='" + + qualifier + "'"; + List<Map<String, String>> rows = orchestrator.getDatabase().executeSql(sql); + + System.out.println("---- " + title + " - measures on qualifier " + qualifier); + for (Map<String, String> row : rows) { + System.out.println(" " + row); + } + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/duplication/CrossModuleDuplicationsTest.java b/tests/src/test/java/org/sonarqube/tests/duplication/CrossModuleDuplicationsTest.java new file mode 100644 index 00000000000..a4588635e37 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/duplication/CrossModuleDuplicationsTest.java @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.duplication; + +import com.google.common.collect.ImmutableMap; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; + +public class CrossModuleDuplicationsTest { + private static final String PROJECT_KEY = "cross-module"; + private static final String PROJECT_DIR = "duplications/" + PROJECT_KEY; + private File projectDir; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @BeforeClass + public static void analyzeProjects() { + + } + + @Before + public void setUpProject() throws IOException { + orchestrator.resetData(); + ItUtils.restoreProfile(orchestrator, getClass().getResource("/duplication/xoo-duplication-profile.xml")); + + FileUtils.copyDirectory(ItUtils.projectDir(PROJECT_DIR), temp.getRoot()); + projectDir = temp.getRoot(); + } + + @Test + public void testDuplications() throws IOException { + analyzeProject(projectDir, PROJECT_KEY, true); + verifyDuplicationMeasures(PROJECT_KEY, 2, 54, 2, 56.3); + + // File1 is the one duplicated in both modules + verifyDuplicationMeasures(PROJECT_KEY + ":module1:src/main/xoo/sample/File1.xoo", 1, 27, 1, 75); + verifyDuplicationMeasures(PROJECT_KEY + ":module2:src/main/xoo/sample/File1.xoo", 1, 27, 1, 75); + } + + @Test + // SONAR-6184 + public void testGhostDuplication() throws IOException { + analyzeProject(projectDir, PROJECT_KEY, true); + + verifyDuplicationMeasures(PROJECT_KEY + ":module1", 1, 27, 1, 45); + verifyDuplicationMeasures(PROJECT_KEY + ":module2", 1, 27, 1, 75); + + // move File2 from module1 to module2 + File src = FileUtils.getFile(projectDir, "module1", "src", "main", "xoo", "sample", "File2.xoo"); + File dst = FileUtils.getFile(projectDir, "module2", "src", "main", "xoo", "sample", "File2.xoo"); + FileUtils.moveFile(src, dst); + + src = new File(src.getParentFile(), "File2.xoo.measures"); + dst = new File(dst.getParentFile(), "File2.xoo.measures"); + FileUtils.moveFile(src, dst); + + // duplication should remain unchanged (except for % of duplication) + analyzeProject(projectDir, PROJECT_KEY, false); + verifyDuplicationMeasures(PROJECT_KEY + ":module1", 1, 27, 1, 75); + verifyDuplicationMeasures(PROJECT_KEY + ":module2", 1, 27, 1, 45); + } + + @Test + // SONAR-6184 + public void testDuplicationFix() throws IOException { + analyzeProject(projectDir, PROJECT_KEY, true); + + verifyDuplicationMeasures(PROJECT_KEY + ":module1", 1, 27, 1, 45); + verifyDuplicationMeasures(PROJECT_KEY + ":module2", 1, 27, 1, 75); + + // remove File1 from module1 + File f1 = FileUtils.getFile(projectDir, "module1", "src", "main", "xoo", "sample", "File1.xoo"); + File f1m = FileUtils.getFile(projectDir, "module2", "src", "main", "xoo", "sample", "File1.xoo.measures"); + f1.delete(); + f1m.delete(); + + // duplication should be 0 + analyzeProject(projectDir, PROJECT_KEY, false); + verifyDuplicationMeasures(PROJECT_KEY + ":module1", 0, 0, 0, 0); + verifyDuplicationMeasures(PROJECT_KEY + ":module2", 0, 0, 0, 0); + } + + private static SonarScanner analyzeProject(File projectDir, String projectKey, boolean create, String... additionalProperties) { + if (create) { + orchestrator.getServer().provisionProject(projectKey, projectKey); + orchestrator.getServer().associateProjectToQualityProfile(projectKey, "xoo", "xoo-duplication-profile"); + } + + SonarScanner sonarRunner = SonarScanner.create(projectDir); + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + + for (int i = 0; i < additionalProperties.length; i += 2) { + builder.put(additionalProperties[i], additionalProperties[i + 1]); + } + SonarScanner scan = sonarRunner.setDebugLogs(true).setProperties(builder.build()); + orchestrator.executeBuild(scan); + return scan; + } + + private static void verifyDuplicationMeasures(String componentKey, int duplicatedBlocks, int duplicatedLines, int duplicatedFiles, double duplicatedLinesDensity) { + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, componentKey, "duplicated_lines", "duplicated_blocks", "duplicated_files", "duplicated_lines_density"); + assertThat(measures.get("duplicated_blocks").intValue()).isEqualTo(duplicatedBlocks); + assertThat(measures.get("duplicated_lines").intValue()).isEqualTo(duplicatedLines); + assertThat(measures.get("duplicated_files").intValue()).isEqualTo(duplicatedFiles); + assertThat(measures.get("duplicated_lines_density")).isEqualTo(duplicatedLinesDensity); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/duplication/CrossProjectDuplicationsOnRemoveFileTest.java b/tests/src/test/java/org/sonarqube/tests/duplication/CrossProjectDuplicationsOnRemoveFileTest.java new file mode 100644 index 00000000000..8813c2cb053 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/duplication/CrossProjectDuplicationsOnRemoveFileTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.duplication; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.apache.commons.io.IOUtils; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import static util.ItUtils.getComponent; +import static util.ItUtils.runProjectAnalysis; +import static util.selenium.Selenese.runSelenese; + +public class CrossProjectDuplicationsOnRemoveFileTest { + + static final String ORIGIN_PROJECT = "origin-project"; + static final String DUPLICATE_PROJECT = "duplicate-project"; + static final String DUPLICATE_FILE = DUPLICATE_PROJECT + ":src/main/xoo/sample/File1.xoo"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @BeforeClass + public static void analyzeProjects() { + orchestrator.resetData(); + ItUtils.restoreProfile(orchestrator, CrossProjectDuplicationsOnRemoveFileTest.class.getResource("/duplication/xoo-duplication-profile.xml")); + + analyzeProject(ORIGIN_PROJECT, "duplications/cross-project/origin"); + analyzeProject(DUPLICATE_PROJECT, "duplications/cross-project/duplicate"); + + // Remove origin project + orchestrator.getServer().adminWsClient().post("api/projects/bulk_delete", "keys", ORIGIN_PROJECT); + assertThat(getComponent(orchestrator, ORIGIN_PROJECT)).isNull(); + } + + @Test + public void duplications_show_ws_does_not_contain_key_of_deleted_file() throws Exception { + String duplication = orchestrator.getServer().adminWsClient().get("api/duplications/show", "key", DUPLICATE_FILE); + + assertEquals(IOUtils.toString(CrossProjectDuplicationsTest.class.getResourceAsStream( + "/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications_on_removed_file-expected.json"), "UTF-8"), + duplication, false); + + // Only one file should be reference, so the reference 2 on origin-project must not exist + assertThat(duplication).doesNotContain("\"2\""); + assertThat(duplication).doesNotContain("origin-project"); + } + + /** + * SONAR-3277 + */ + @Test + public void display_message_in_viewer_when_duplications_with_deleted_files_are_found() throws Exception { + // TODO stas, please replace this IT by a medium test + runSelenese(orchestrator, + "/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications-with-deleted-project.html"); + } + + private static void analyzeProject(String projectKey, String path) { + orchestrator.getServer().provisionProject(projectKey, projectKey); + orchestrator.getServer().associateProjectToQualityProfile(projectKey, "xoo", "xoo-duplication-profile"); + + runProjectAnalysis(orchestrator, path, + "sonar.cpd.cross_project", "true", + "sonar.projectKey", projectKey, + "sonar.projectName", projectKey); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/duplication/CrossProjectDuplicationsTest.java b/tests/src/test/java/org/sonarqube/tests/duplication/CrossProjectDuplicationsTest.java new file mode 100644 index 00000000000..81708b22d2c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/duplication/CrossProjectDuplicationsTest.java @@ -0,0 +1,171 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.duplication; + +import com.google.common.collect.ObjectArrays; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; +import util.issue.IssueRule; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import static util.ItUtils.getMeasureAsDouble; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; +import static util.selenium.Selenese.runSelenese; + +public class CrossProjectDuplicationsTest { + + static final String ORIGIN_PROJECT = "origin-project"; + static final String DUPLICATE_PROJECT = "duplicate-project"; + static final String PROJECT_WITH_EXCLUSION = "project-with-exclusion"; + static final String PROJECT_WITHOUT_ENOUGH_TOKENS = "project_without_enough_tokens"; + + static final String DUPLICATE_FILE = DUPLICATE_PROJECT + ":src/main/xoo/sample/File1.xoo"; + static final String BRANCH = "with-branch"; + + static final String ORIGIN_PATH = "duplications/cross-project/origin"; + static final String DUPLICATE_PATH = "duplications/cross-project/duplicate"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @ClassRule + public static final IssueRule issueRule = IssueRule.from(orchestrator); + + @BeforeClass + public static void analyzeProjects() { + orchestrator.resetData(); + ItUtils.restoreProfile(orchestrator, CrossProjectDuplicationsTest.class.getResource("/duplication/xoo-duplication-profile.xml")); + + analyzeProject(ORIGIN_PROJECT, ORIGIN_PATH); + analyzeProject(DUPLICATE_PROJECT, DUPLICATE_PATH); + analyzeProjectWithBranch(DUPLICATE_PROJECT, DUPLICATE_PATH, BRANCH); + analyzeProject(PROJECT_WITH_EXCLUSION, DUPLICATE_PATH, "sonar.cpd.exclusions", "**/File*"); + + // Set minimum tokens to a big value in order to not get duplications + setServerProperty(orchestrator, "sonar.cpd.xoo.minimumTokens", "1000"); + analyzeProject(PROJECT_WITHOUT_ENOUGH_TOKENS, DUPLICATE_PATH); + } + + @AfterClass + public static void resetServerProperties() throws Exception { + setServerProperty(orchestrator, "sonar.cpd.xoo.minimumTokens", null); + } + + @Test + public void origin_project_has_no_duplication_as_it_has_not_been_analyzed_twice() throws Exception { + assertProjectHasNoDuplication(ORIGIN_PROJECT); + } + + @Test + public void duplicate_project_has_duplication_as_it_has_been_analyzed_twice() throws Exception { + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, DUPLICATE_PROJECT, "duplicated_lines", "duplicated_blocks", "duplicated_files", "duplicated_lines_density"); + assertThat(measures.get("duplicated_lines").intValue()).isEqualTo(27); + assertThat(measures.get("duplicated_blocks").intValue()).isEqualTo(1); + assertThat(measures.get("duplicated_files").intValue()).isEqualTo(1); + assertThat(measures.get("duplicated_lines_density")).isEqualTo(45d); + } + + @Test + public void issue_on_duplicated_blocks_is_generated_on_file() throws Exception { + assertThat(issueRule.search(new SearchWsRequest().setComponentKeys(singletonList(DUPLICATE_FILE)).setRules(singletonList("common-xoo:DuplicatedBlocks"))).getIssuesList()) + .hasSize(1); + } + + @Test + public void verify_sources_lines_ws_duplication_information() throws Exception { + verifyWsResultOnDuplicateFile("api/sources/lines", "sources_lines_duplication-expected.json"); + } + + @Test + public void verify_duplications_show_ws() throws Exception { + verifyWsResultOnDuplicateFile("api/duplications/show", "duplications_show-expected.json"); + } + + @Test + public void project_with_branch_has_no_duplication() throws Exception { + assertProjectHasNoDuplication(DUPLICATE_PROJECT + ":" + BRANCH); + } + + @Test + public void project_with_exclusion_has_no_duplication() throws Exception { + assertProjectHasNoDuplication(PROJECT_WITH_EXCLUSION); + } + + @Test + public void project_without_enough_tokens_has_duplication() throws Exception { + assertProjectHasNoDuplication(PROJECT_WITHOUT_ENOUGH_TOKENS); + } + + @Test + public void verify_viewer() { + runSelenese(orchestrator, "/duplication/CrossProjectDuplicationsTest/cross-project-duplications-viewer.html"); + } + + private static void analyzeProject(String projectKey, String path, String... additionalProperties) { + initProject(projectKey); + executeAnalysis(projectKey, path, additionalProperties); + } + + private static void analyzeProjectWithBranch(String projectKey, String path, String branch) { + initProject(projectKey + ":" + branch); + executeAnalysis(projectKey, path, "sonar.branch", branch); + } + + private static void initProject(String effectiveProjectKey) { + orchestrator.getServer().provisionProject(effectiveProjectKey, effectiveProjectKey); + orchestrator.getServer().associateProjectToQualityProfile(effectiveProjectKey, "xoo", "xoo-duplication-profile"); + } + + private static void executeAnalysis(String projectKey, String path, String... additionalProperties) { + runProjectAnalysis(orchestrator, path, + ObjectArrays.concat( + new String[] { + "sonar.cpd.cross_project", "true", + "sonar.projectKey", projectKey, + "sonar.projectName", projectKey + }, + additionalProperties, String.class)); + } + + private static void assertProjectHasNoDuplication(String projectKey) { + assertThat(getMeasureAsDouble(orchestrator, projectKey, "duplicated_lines")).isZero(); + } + + private static void verifyWsResultOnDuplicateFile(String ws, String expectedFilePath) throws Exception { + String duplication = newAdminWsClient(orchestrator).wsConnector().call(new GetRequest(ws).setParam("key", DUPLICATE_FILE)).content(); + assertEquals(IOUtils.toString(CrossProjectDuplicationsTest.class.getResourceAsStream("/duplication/CrossProjectDuplicationsTest/" + expectedFilePath), "UTF-8"), duplication, + false); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/duplication/DuplicationsTest.java b/tests/src/test/java/org/sonarqube/tests/duplication/DuplicationsTest.java new file mode 100644 index 00000000000..710fd8de0d5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/duplication/DuplicationsTest.java @@ -0,0 +1,189 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.duplication; + +import com.google.common.collect.ObjectArrays; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; +import util.issue.IssueRule; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +public class DuplicationsTest { + + static final String DUPLICATIONS = "file-duplications"; + static final String DUPLICATIONS_WITH_EXCLUSIONS = "file-duplications-with-exclusions"; + static final String WITHOUT_ENOUGH_TOKENS = "project_without_enough_tokens"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @ClassRule + public static final IssueRule issueRule = IssueRule.from(orchestrator); + + private static WsClient adminWsClient; + + @BeforeClass + public static void analyzeProjects() { + orchestrator.resetData(); + + ItUtils.restoreProfile(orchestrator, DuplicationsTest.class.getResource("/duplication/xoo-duplication-profile.xml")); + analyzeProject(DUPLICATIONS); + analyzeProject(DUPLICATIONS_WITH_EXCLUSIONS, "sonar.cpd.exclusions", "**/File*"); + + // Set minimum tokens to a big value in order to not get duplications + setServerProperty(orchestrator, "sonar.cpd.xoo.minimumTokens", "1000"); + analyzeProject(WITHOUT_ENOUGH_TOKENS); + + adminWsClient = newAdminWsClient(orchestrator); + } + + @AfterClass + public static void resetProperties() throws Exception { + setServerProperty(orchestrator, "sonar.cpd.xoo.minimumTokens", null); + } + + private static Map<String, Double> getMeasures(String key) { + return getMeasuresAsDoubleByMetricKey(orchestrator, key, "duplicated_lines", "duplicated_blocks", "duplicated_files", "duplicated_lines_density"); + } + + private static void verifyDuplicationMeasures(String componentKey, int duplicatedBlocks, int duplicatedLines, int duplicatedFiles, double duplicatedLinesDensity) { + Map<String, Double> measures = getMeasures(componentKey); + assertThat(measures.get("duplicated_blocks").intValue()).isEqualTo(duplicatedBlocks); + assertThat(measures.get("duplicated_lines").intValue()).isEqualTo(duplicatedLines); + assertThat(measures.get("duplicated_files").intValue()).isEqualTo(duplicatedFiles); + assertThat(measures.get("duplicated_lines_density")).isEqualTo(duplicatedLinesDensity); + } + + private static void analyzeProject(String projectKey, String... additionalProperties) { + orchestrator.getServer().provisionProject(projectKey, projectKey); + orchestrator.getServer().associateProjectToQualityProfile(projectKey, "xoo", "xoo-duplication-profile"); + + runProjectAnalysis(orchestrator, "duplications/file-duplications", + ObjectArrays.concat( + new String[] { + "sonar.projectKey", projectKey, + "sonar.projectName", projectKey + }, + additionalProperties, String.class)); + } + + private static void verifyWsResultOnDuplicateFile(String fileKey, String ws, String expectedFilePath) throws Exception { + String duplication = orchestrator.getServer().adminWsClient().get(ws, "key", fileKey); + assertEquals(IOUtils.toString(CrossProjectDuplicationsTest.class.getResourceAsStream("/duplication/DuplicationsTest/" + expectedFilePath), "UTF-8"), duplication, + false); + } + + @Test + public void duplicated_lines_within_same_file() { + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo", + 2, + 30 * 2, // 2 blocks with 30 lines + 1, + 84.5); + } + + @Test + public void duplicated_same_lines_within_3_classes() { + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_same_lines_within_3_files/File1.xoo", 1, 33, 1, 78.6); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_same_lines_within_3_files/File2.xoo", 1, 31, 1, 75.6); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_same_lines_within_3_files/File3.xoo", 1, 31, 1, 70.5); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_same_lines_within_3_files", 3, 95, 3, 74.8); + } + + @Test + public void duplicated_lines_within_directory() { + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_dir/DuplicatedLinesInSameDirectory1.xoo", 1, 30, 1, 28.3); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_dir/DuplicatedLinesInSameDirectory2.xoo", 1, 30, 1, 41.7); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_dir", 2, 60, 2, 33.7); + } + + @Test + public void duplicated_lines_with_other_directory() { + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_with_other_dir1/DuplicatedLinesWithOtherDirectory.xoo", 1, 39, 1, 92.9); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_with_other_dir1", 1, 39, 1, 92.9); + + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_with_other_dir2/DuplicatedLinesWithOtherDirectory.xoo", 1, 39, 1, 92.9); + verifyDuplicationMeasures(DUPLICATIONS + ":src/main/xoo/duplicated_lines_with_other_dir2", 1, 39, 1, 92.9); + } + + @Test + public void duplication_measures_on_project() { + verifyDuplicationMeasures(DUPLICATIONS, 9, 293, 8, 63.7); + } + + @Test + public void project_without_enough_tokens_has_duplication() { + verifyDuplicationMeasures(WITHOUT_ENOUGH_TOKENS, 0, 0, 0, 0d); + } + + /** + * SONAR-3108 + */ + @Test + public void use_duplication_exclusions() { + verifyDuplicationMeasures(DUPLICATIONS_WITH_EXCLUSIONS, 6, 198, 5, 43d); + } + + @Test + public void issues_on_duplicated_blocks_are_generated_on_each_file() throws Exception { + assertThat(issueRule.search(new SearchWsRequest().setRules(singletonList("common-xoo:DuplicatedBlocks"))).getIssuesList()).hasSize(13); + } + + @Test + public void verify_sources_lines_ws_duplication_information() throws Exception { + verifyWsResultOnDuplicateFile(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo", + "api/sources/lines", "sources_lines_duplication-expected.json"); + } + + @Test + public void verify_duplications_show_ws() throws Exception { + verifyWsResultOnDuplicateFile(DUPLICATIONS + ":src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo", + "api/duplications/show", "duplications_show-expected.json"); + } + + // SONAR-9441 + @Test + public void fail_properly_when_no_parameter() { + WsResponse result = adminWsClient.wsConnector().call(new GetRequest("api/duplications/show")); + + assertThat(result.code()).isEqualTo(HTTP_BAD_REQUEST); + assertThat(result.content()).contains("Either 'uuid' or 'key' must be provided, not both"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/duplication/NewDuplicationsTest.java b/tests/src/test/java/org/sonarqube/tests/duplication/NewDuplicationsTest.java new file mode 100644 index 00000000000..c16b3441f5d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/duplication/NewDuplicationsTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.duplication; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.util.Map; +import org.assertj.core.data.Offset; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures; + +import static java.lang.Double.parseDouble; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresWithVariationsByMetricKey; +import static util.ItUtils.runProjectAnalysis; + +public class NewDuplicationsTest { + + static final Offset<Double> DEFAULT_OFFSET = Offset.offset(0.1d); + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @BeforeClass + public static void analyzeProjects() { + orchestrator.resetData(); + + runProjectAnalysis(orchestrator, "duplications/new-duplications-v1", + "sonar.projectDate", "2015-02-01", + "sonar.scm.disabled", "false"); + runProjectAnalysis(orchestrator, "duplications/new-duplications-v2", + "sonar.scm.disabled", "false"); + } + + @Test + public void new_duplications_on_project() throws Exception { + Map<String, WsMeasures.Measure> measures = getMeasures("new-duplications"); + assertThat(parseDouble(measures.get("new_lines").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(83d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_lines").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(71d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_lines_density").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(85.5d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_blocks").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(12d, DEFAULT_OFFSET); + } + + @Test + public void new_duplications_on_directory() throws Exception { + Map<String, WsMeasures.Measure> measures = getMeasures("new-duplications:src/main/xoo/duplicated_lines_with_other_dir1"); + assertThat(parseDouble(measures.get("new_lines").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(24d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_lines").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(24d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_lines_density").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(100d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_blocks").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(7d, DEFAULT_OFFSET); + } + + @Test + public void new_duplications_on_file() throws Exception { + Map<String, WsMeasures.Measure> measures = getMeasures("new-duplications:src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo"); + assertThat(parseDouble(measures.get("new_lines").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(41d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_lines").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(29d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_lines_density").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(70.7d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_duplicated_blocks").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(2d, DEFAULT_OFFSET); + } + + private static Map<String, WsMeasures.Measure> getMeasures(String key) { + return getMeasuresWithVariationsByMetricKey(orchestrator, key, "new_lines", "new_duplicated_lines", "new_duplicated_lines_density", "new_duplicated_blocks"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/i18n/I18nTest.java b/tests/src/test/java/org/sonarqube/tests/i18n/I18nTest.java new file mode 100644 index 00000000000..29b3580e0eb --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/i18n/I18nTest.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.i18n; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; + +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class I18nTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + /** + * TODO This test should use a fake widget that display a fake metric with decimals instead of using provided metric + * Ignored because there is not a good idea to force a display language by GET parameter + * The displayed language is based on browser/system locale + */ + @Test + @Ignore + public void test_localization() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + runSelenese(orchestrator, + "/i18n/default-locale-is-english.html", + "/i18n/french-locale.html", + "/i18n/french-pack.html", + "/i18n/locale-with-france-country.html", + "/i18n/locale-with-swiss-country.html"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/AbstractIssueTest.java b/tests/src/test/java/org/sonarqube/tests/issue/AbstractIssueTest.java new file mode 100644 index 00000000000..4f85822c7d6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/AbstractIssueTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category2Suite; +import java.util.List; +import org.junit.ClassRule; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueClient; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.issue.Issues; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class AbstractIssueTest { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Category2Suite.ORCHESTRATOR; + + static IssueClient adminIssueClient() { + return ORCHESTRATOR.getServer().adminWsClient().issueClient(); + } + + static IssueClient issueClient() { + return ORCHESTRATOR.getServer().wsClient().issueClient(); + } + + static Issue searchRandomIssue() { + List<Issue> issues = searchIssues(IssueQuery.create()); + assertThat(issues).isNotEmpty(); + return issues.get(0); + } + + static Issues search(IssueQuery issueQuery) { + issueQuery.urlParams().put("additionalFields", "_all"); + return issueClient().find(issueQuery); + } + + static List<Issue> searchIssues() { + return searchIssues(IssueQuery.create()); + } + + static List<Issue> searchIssues(IssueQuery issueQuery) { + return issueClient().find(issueQuery).list(); + } + + static List<Issue> searchIssuesByProject(String projectKey) { + return search(IssueQuery.create().componentRoots(projectKey)).list(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java b/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java new file mode 100644 index 00000000000..7b6f2caf80a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/AutoAssignTest.java @@ -0,0 +1,171 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.user.CreateRequest; +import org.sonarqube.ws.client.user.SearchRequest; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.setServerProperty; + +public class AutoAssignTest extends AbstractIssueTest { + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + ProjectAnalysis projectAnalysis; + + @Before + public void setup() { + String qualityProfileKey = projectAnalysisRule.registerProfile("/issue/IssueActionTest/xoo-one-issue-per-line-profile.xml"); + String projectKey = projectAnalysisRule.registerProject("issue/AutoAssignTest"); + projectAnalysis = projectAnalysisRule.newProjectAnalysis(projectKey) + .withQualityProfile(qualityProfileKey) + .withProperties("sonar.scm.disabled", "false", "sonar.scm.provider", "xoo"); + } + + @After + public void resetData() throws Exception { + newAdminWsClient(ORCHESTRATOR).wsConnector().call(new PostRequest("api/projects/delete").setParam("project", "AutoAssignTest")); + deleteAllUsers(); + + // Reset default assignee + setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", null); + } + + @Test + public void auto_assign_issues_to_user() throws Exception { + // verify that login matches, case-sensitive + createUser("user1", "User 1", "user1@email.com"); + createUser("USER2", "User 2", "user2@email.com"); + // verify that name is not used to match, whatever the case + createUser("user3", "User 3", "user3@email.com"); + createUser("user4", "USER 4", "user4@email.com"); + // verify that email matches, case-insensitive + createUser("user5", "User 5", "user5@email.com"); + createUser("user6", "User 6", "USER6@email.COM"); + // verify that SCM account matches, case-insensitive + createUser("user7", "User 7", "user7@email.com", "user7ScmAccount"); + createUser("user8", "User 8", "user8@email.com", "user8SCMaccOUNT"); + + projectAnalysis.run(); + + List<Issue> issues = search(IssueQuery.create().components("AutoAssignTest:src/sample.xoo").sort("FILE_LINE")).list(); + // login match, case-sensitive + verifyIssueAssignee(issues, 1, "user1"); + verifyIssueAssignee(issues, 2, null); + // user name is not used to match + verifyIssueAssignee(issues, 3, null); + verifyIssueAssignee(issues, 4, null); + // email match, case-insensitive + verifyIssueAssignee(issues, 5, "user5"); + verifyIssueAssignee(issues, 6, "user6"); + // SCM account match, case-insensitive + verifyIssueAssignee(issues, 7, "user7"); + verifyIssueAssignee(issues, 8, "user8"); + } + + private static void verifyIssueAssignee(List<Issue> issues, int line, @Nullable String expectedAssignee) { + assertThat(issues.get(line - 1).assignee()).isEqualTo(expectedAssignee); + } + + @Test + public void auto_assign_issues_to_default_assignee() throws Exception { + createUser("user1", "User 1", "user1@email.com"); + createUser("user2", "User 2", "user2@email.com"); + setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", "user2"); + projectAnalysis.run(); + + // user1 is assigned to his issues. All other issues are assigned to the default assignee. + assertThat(search(IssueQuery.create().assignees("user1")).list()).hasSize(1); + assertThat(search(IssueQuery.create().assignees("user2")).list()).hasSize(8); + // No unassigned issues + assertThat(search(IssueQuery.create().assigned(false)).list()).isEmpty(); + } + + /** + * SONAR-7098 + * + * Given two versions of same project: + * v1: issue, but no SCM data + * v2: old issue and SCM data + * Expected: all issues should be associated with authors + */ + @Test + public void update_author_and_assignee_when_scm_is_activated() { + createUser("user1", "User 1", "user1@email.com"); + + // Run a first analysis without SCM + projectAnalysis.withProperties("sonar.scm.disabled", "true").run(); + List<Issue> issues = searchIssues(); + assertThat(issues).isNotEmpty(); + + // No author and assignee are set + for (Issue issue : issues) { + assertThat(issue.author()).isEmpty(); + } + assertThat(search(IssueQuery.create().assigned(true)).list()).isEmpty(); + + // Run a second analysis with SCM + projectAnalysis.run(); + issues = searchIssues(); + assertThat(issues).isNotEmpty(); + + // Authors and assignees are set + for (Issue issue : issues) { + assertThat(issue.author()).isNotEmpty(); + } + assertThat(search(IssueQuery.create().assignees("user1")).list()).hasSize(1); + } + + private static void createUser(String login, String name, String email, String... scmAccounts) { + newAdminWsClient(ORCHESTRATOR).users().create( + CreateRequest.builder() + .setLogin(login) + .setName(name) + .setEmail(email) + .setPassword("xxxxxxx") + .setScmAccounts(Arrays.asList(scmAccounts)) + .build()); + } + + private static void deleteAllUsers() { + WsClient wsClient = newAdminWsClient(ORCHESTRATOR); + WsUsers.SearchWsResponse searchResponse = wsClient.users().search(SearchRequest.builder().build()); + searchResponse.getUsersList().forEach(user -> { + wsClient.wsConnector().call(new PostRequest("api/users/deactivate").setParam("login", user.getLogin())); + }); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/CommonRulesTest.java b/tests/src/test/java/org/sonarqube/tests/issue/CommonRulesTest.java new file mode 100644 index 00000000000..69cddb89b7a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/CommonRulesTest.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; + +public class CommonRulesTest extends AbstractIssueTest { + + public static final String FILE_KEY = "common-rules-project:src/Sample.xoo"; + public static final String TEST_FILE_KEY = "common-rules-project:test/SampleTest.xoo"; + + private static WsClient adminWsClient; + + @BeforeClass + public static void setUp() { + ORCHESTRATOR.resetData(); + ItUtils.restoreProfile(ORCHESTRATOR, CommonRulesTest.class.getResource("/issue/CommonRulesTest/xoo-common-rules-profile.xml")); + ORCHESTRATOR.getServer().provisionProject("common-rules-project", "Sample"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("common-rules-project", "xoo", "xoo-common-rules"); + runProjectAnalysis(ORCHESTRATOR, "issue/common-rules", + "sonar.cpd.xoo.minimumTokens", "2", + "sonar.cpd.xoo.minimumLines", "2"); + + adminWsClient = newAdminWsClient(ORCHESTRATOR); + } + + @Test + public void test_rule_on_duplicated_blocks() { + List<Issue> issues = findIssues(FILE_KEY, "common-xoo:DuplicatedBlocks"); + assertThat(issues).hasSize(1); + } + + @Test + public void test_rule_on_comments() { + List<Issue> issues = findIssues(FILE_KEY, "common-xoo:InsufficientCommentDensity"); + assertThat(issues.size()).isEqualTo(1); + } + + @Test + public void test_rule_on_coverage() { + List<Issue> issues = findIssues(FILE_KEY, "common-xoo:InsufficientBranchCoverage"); + assertThat(issues.size()).isEqualTo(1); + + issues = findIssues(FILE_KEY, "common-xoo:InsufficientLineCoverage"); + assertThat(issues.size()).isEqualTo(1); + } + + @Test + public void test_rule_on_skipped_tests() { + List<Issue> issues = findIssues(TEST_FILE_KEY, "common-xoo:SkippedUnitTests"); + assertThat(issues.size()).isEqualTo(1); + } + + @Test + public void test_rule_on_test_errors() { + List<Issue> issues = findIssues(TEST_FILE_KEY, "common-xoo:FailedUnitTests"); + assertThat(issues.size()).isEqualTo(1); + } + + private List<Issue> findIssues(String componentKey, String ruleKey) { + return adminWsClient.issues().search( + new SearchWsRequest() + .setComponents(singletonList(componentKey)) + .setRules(singletonList(ruleKey))) + .getIssuesList(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/CustomRulesTest.java b/tests/src/test/java/org/sonarqube/tests/issue/CustomRulesTest.java new file mode 100644 index 00000000000..cae6130cea6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/CustomRulesTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CustomRulesTest extends AbstractIssueTest { + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + private ProjectAnalysis xooSampleAnalysis; + + @Before + public void setup() { + String profileKey = projectAnalysisRule.registerProfile("/issue/CustomRulesTest/custom.xml"); + String projectKey = projectAnalysisRule.registerProject("shared/xoo-sample"); + this.xooSampleAnalysis = projectAnalysisRule.newProjectAnalysis(projectKey) + .withQualityProfile(profileKey); + } + + @Test + public void analyzeProjectWithCustomRules() throws Exception { + ORCHESTRATOR.getServer().adminWsClient().post("api/rules/create", + "template_key", "xoo:TemplateRule", + "custom_key", "MyCustomRule", + "markdown_description", "My description", + "name", "My custom rule", + "severity", "BLOCKER", + "params", "line=2"); + + xooSampleAnalysis.run(); + + List<Issue> issues = searchIssues(); + assertThat(issues).hasSize(1); + + Issue issue = issues.get(0); + assertThat(issue.ruleKey()).isEqualTo("xoo:MyCustomRule"); + assertThat(issue.line()).isEqualTo(2); + // Overriden in quality profile + assertThat(issue.severity()).isEqualTo("CRITICAL"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueActionTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueActionTest.java new file mode 100644 index 00000000000..6ef4ae9573e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueActionTest.java @@ -0,0 +1,200 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.client.issue.AddCommentRequest; +import org.sonarqube.ws.client.issue.AssignRequest; +import org.sonarqube.ws.client.issue.EditCommentRequest; +import org.sonarqube.ws.client.issue.IssuesService; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import org.sonarqube.ws.client.issue.SetSeverityRequest; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; +import util.issue.IssueRule; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.sonarqube.ws.Common.Severity.BLOCKER; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.toDatetime; + +public class IssueActionTest extends AbstractIssueTest { + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + @ClassRule + public static final IssueRule issueRule = IssueRule.from(ORCHESTRATOR); + + private ProjectAnalysis projectAnalysis; + private IssuesService issuesService; + + private Issue randomIssue; + + @Before + public void setup() { + String qualityProfileKey = projectAnalysisRule.registerProfile("/issue/IssueActionTest/xoo-one-issue-per-line-profile.xml"); + String projectKey = projectAnalysisRule.registerProject("shared/xoo-sample"); + + this.projectAnalysis = projectAnalysisRule.newProjectAnalysis(projectKey).withQualityProfile(qualityProfileKey); + this.projectAnalysis.run(); + this.issuesService = newAdminWsClient(ORCHESTRATOR).issues(); + this.randomIssue = issueRule.getRandomIssue(); + } + + @Test + public void no_comments_by_default() throws Exception { + assertThat(randomIssue.getComments().getCommentsList()).isEmpty(); + } + + @Test + public void add_comment() throws Exception { + Issues.Comment comment = issuesService.addComment(new AddCommentRequest(randomIssue.getKey(), "this is my *comment*")).getIssue().getComments().getComments(0); + assertThat(comment.getKey()).isNotNull(); + assertThat(comment.getHtmlText()).isEqualTo("this is my <strong>comment</strong>"); + assertThat(comment.getLogin()).isEqualTo("admin"); + assertThat(comment.getCreatedAt()).isNotNull(); + + // reload issue + Issue reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.getComments().getCommentsList()).hasSize(1); + assertThat(reloaded.getComments().getComments(0).getKey()).isEqualTo(comment.getKey()); + assertThat(reloaded.getComments().getComments(0).getHtmlText()).isEqualTo("this is my <strong>comment</strong>"); + assertThat(toDatetime(reloaded.getUpdateDate())).isAfter(toDatetime(randomIssue.getUpdateDate())); + } + + /** + * SONAR-4450 + */ + @Test + public void should_reject_blank_comment() throws Exception { + try { + issuesService.addComment(new AddCommentRequest(randomIssue.getKey(), " ")); + fail(); + } catch (org.sonarqube.ws.client.HttpException ex) { + assertThat(ex.code()).isEqualTo(400); + } + + Issue reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.getComments().getCommentsList()).isEmpty(); + } + + @Test + public void edit_comment() throws Exception { + Issues.Comment comment = issuesService.addComment(new AddCommentRequest(randomIssue.getKey(), "this is my *comment*")).getIssue().getComments().getComments(0); + Issues.Comment editedComment = issuesService.editComment(new EditCommentRequest(comment.getKey(), "new *comment*")).getIssue().getComments().getComments(0); + assertThat(editedComment.getHtmlText()).isEqualTo("new <strong>comment</strong>"); + + // reload issue + Issue reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.getComments().getCommentsList()).hasSize(1); + assertThat(reloaded.getComments().getComments(0).getHtmlText()).isEqualTo("new <strong>comment</strong>"); + } + + @Test + public void delete_comment() throws Exception { + Issues.Comment comment = issuesService.addComment(new AddCommentRequest(randomIssue.getKey(), "this is my *comment*")).getIssue().getComments().getComments(0); + Issue issue = issuesService.deleteComment(comment.getKey()).getIssue(); + assertThat(issue.getComments().getCommentsList()).isEmpty(); + + // reload issue + Issue reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.getComments().getCommentsList()).isEmpty(); + } + + /** + * SONAR-4352 + */ + @Test + public void change_severity() { + String componentKey = "sample"; + + // there are no blocker issues + assertThat(searchIssuesBySeverities(componentKey, "BLOCKER")).isEmpty(); + + // increase the severity of an issue + issuesService.setSeverity(new SetSeverityRequest(randomIssue.getKey(), "BLOCKER")); + + assertThat(searchIssuesBySeverities(componentKey, "BLOCKER")).hasSize(1); + + projectAnalysis.run(); + Issue reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.getSeverity()).isEqualTo(BLOCKER); + assertThat(reloaded.getStatus()).isEqualTo("OPEN"); + assertThat(reloaded.hasResolution()).isFalse(); + assertThat(reloaded.getCreationDate()).isEqualTo(randomIssue.getCreationDate()); + assertThat(toDatetime(reloaded.getCreationDate())).isBefore(toDatetime(reloaded.getUpdateDate())); + } + + /** + * SONAR-4287 + */ + @Test + public void assign() { + assertThat(randomIssue.hasAssignee()).isFalse(); + Issues.SearchWsResponse response = issueRule.search(new SearchWsRequest().setIssues(singletonList(randomIssue.getKey()))); + assertThat(response.getUsers().getUsersList()).isEmpty(); + + issuesService.assign(new AssignRequest(randomIssue.getKey(), "admin")); + assertThat(issueRule.search(new SearchWsRequest().setAssignees(singletonList("admin"))).getIssuesList()).hasSize(1); + + projectAnalysis.run(); + Issue reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.getAssignee()).isEqualTo("admin"); + assertThat(reloaded.getCreationDate()).isEqualTo(randomIssue.getCreationDate()); + + response = issueRule.search(new SearchWsRequest().setIssues(singletonList(randomIssue.getKey())).setAdditionalFields(singletonList("users"))); + assertThat(response.getUsers().getUsersList().stream().filter(user -> "admin".equals(user.getLogin())).findFirst()).isPresent(); + assertThat(response.getUsers().getUsersList().stream().filter(user -> "Administrator".equals(user.getName())).findFirst()).isPresent(); + + // unassign + issuesService.assign(new AssignRequest(randomIssue.getKey(), null)); + reloaded = issueRule.getByKey(randomIssue.getKey()); + assertThat(reloaded.hasAssignee()).isFalse(); + assertThat(issueRule.search(new SearchWsRequest().setAssignees(singletonList("admin"))).getIssuesList()).isEmpty(); + } + + /** + * SONAR-4287 + */ + @Test + public void fail_assign_if_assignee_does_not_exist() { + assertThat(randomIssue.hasAssignee()).isFalse(); + try { + issuesService.assign(new AssignRequest(randomIssue.getKey(), "unknown")); + fail(); + } catch (org.sonarqube.ws.client.HttpException ex) { + assertThat(ex.code()).isEqualTo(404); + } + } + + private static List<Issue> searchIssuesBySeverities(String projectKey, String severity) { + return issueRule.search(new SearchWsRequest().setProjectKeys(singletonList(projectKey)).setSeverities(singletonList(severity))).getIssuesList(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueBulkChangeTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueBulkChangeTest.java new file mode 100644 index 00000000000..b62cf49a22c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueBulkChangeTest.java @@ -0,0 +1,272 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import com.google.common.collect.FluentIterable; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.wsclient.base.HttpException; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Issues.BulkChangeWsResponse; +import org.sonarqube.ws.client.issue.BulkChangeRequest; +import org.sonarqube.ws.client.issue.IssuesService; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; +import util.issue.IssueRule; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.Common.Severity.BLOCKER; +import static org.sonarqube.ws.Issues.Issue; +import static util.ItUtils.newAdminWsClient; + +/** + * SONAR-4421 + */ +public class IssueBulkChangeTest extends AbstractIssueTest { + + private static final int BULK_EDITED_ISSUE_COUNT = 3; + private static final String COMMENT_AS_MARKDOWN = "this is my *comment*"; + private static final String COMMENT_AS_HTML = "this is my <strong>comment</strong>"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @ClassRule + public static IssueRule issueRule = IssueRule.from(ORCHESTRATOR); + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + private IssuesService issuesService; + private ProjectAnalysis xooSampleLittleIssuesAnalysis; + + @Before + public void setUp() throws Exception { + String qualityProfileKey = projectAnalysisRule.registerProfile("/issue/IssueBulkChangeTest/one-issue-per-line-profile.xml"); + String projectKey = projectAnalysisRule.registerProject("shared/xoo-sample"); + this.xooSampleLittleIssuesAnalysis = projectAnalysisRule.newProjectAnalysis(projectKey) + .withQualityProfile(qualityProfileKey); + this.issuesService = newAdminWsClient(ORCHESTRATOR).issues(); + } + + @Test + public void should_change_severity() { + xooSampleLittleIssuesAnalysis.run(); + + String newSeverity = "BLOCKER"; + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + BulkChangeWsResponse bulkChange = bulkChangeSeverityOfIssues(issueKeys, newSeverity); + + assertThat(bulkChange.getSuccess()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + assertIssueSeverity(issueKeys, newSeverity); + } + + @Test + public void should_do_transition() { + xooSampleLittleIssuesAnalysis.run(); + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + BulkChangeWsResponse bulkChangeResponse = bulkTransitionStatusOfIssues(issueKeys, "confirm"); + + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + assertIssueStatus(issueKeys, "CONFIRMED"); + } + + @Test + public void should_assign() { + xooSampleLittleIssuesAnalysis.run(); + + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + BulkChangeWsResponse bulkChangeResponse = buldChangeAssigneeOfIssues(issueKeys, "admin"); + + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + for (Issue issue : issueRule.getByKeys(issueKeys)) { + assertThat(issue.getAssignee()).isEqualTo("admin"); + } + } + + @Test + public void should_setSeverity_add_comment_in_single_WS_call() { + xooSampleLittleIssuesAnalysis.run(); + + String newSeverity = "BLOCKER"; + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + + BulkChangeWsResponse bulkChangeResponse = issuesService.bulkChange(BulkChangeRequest.builder() + .setIssues(asList(issueKeys)) + .setSetSeverity(newSeverity) + .setComment(COMMENT_AS_MARKDOWN) + .build()); + + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + for (Issue issue : issueRule.getByKeys(issueKeys)) { + assertThat(issue.getComments().getCommentsList()).hasSize(1); + assertThat(issue.getComments().getComments(0).getHtmlText()).isEqualTo(COMMENT_AS_HTML); + } + } + + @Test + public void should_apply_bulk_change_on_many_actions() { + xooSampleLittleIssuesAnalysis.run(); + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + + BulkChangeWsResponse bulkChangeResponse = issuesService.bulkChange(BulkChangeRequest.builder() + .setIssues(asList(issueKeys)) + .setDoTransition("confirm") + .setAssign("admin") + .setSetSeverity("BLOCKER") + .setComment(COMMENT_AS_MARKDOWN) + .build()); + + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + for (Issue issue : issueRule.getByKeys(issueKeys)) { + assertThat(issue.getStatus()).isEqualTo("CONFIRMED"); + assertThat(issue.getAssignee()).isEqualTo("admin"); + assertThat(issue.getSeverity()).isEqualTo(BLOCKER); + assertThat(issue.getComments().getCommentsList()).hasSize(1); + assertThat(issue.getComments().getComments(0).getHtmlText()).isEqualTo(COMMENT_AS_HTML); + } + } + + @Test + public void should_not_apply_bulk_change_if_not_logged() { + xooSampleLittleIssuesAnalysis.run(); + + String newSeverity = "BLOCKER"; + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + + try { + issuesService.bulkChange(createBulkChangeSeverityOfIssuesQuery(issueKeys, newSeverity)); + } catch (Exception e) { + assertHttpException(e, 401); + } + } + + @Test + public void should_not_apply_bulk_change_if_no_change_to_do() { + xooSampleLittleIssuesAnalysis.run(); + + String newSeverity = "BLOCKER"; + String[] issueKeys = searchIssueKeys(BULK_EDITED_ISSUE_COUNT); + + // Apply the bulk change a first time + BulkChangeWsResponse bulkChangeResponse = bulkChangeSeverityOfIssues(issueKeys, newSeverity); + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + + // Re apply the same bulk change -> no issue should be changed + bulkChangeResponse = bulkChangeSeverityOfIssues(issueKeys, newSeverity); + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(0); + assertThat(bulkChangeResponse.getIgnored()).isEqualTo(BULK_EDITED_ISSUE_COUNT); + } + + @Test + public void should_not_apply_bulk_change_if_no_action() { + xooSampleLittleIssuesAnalysis.run(); + + try { + int limit = BULK_EDITED_ISSUE_COUNT; + String[] issueKeys = searchIssueKeys(limit); + issuesService.bulkChange(BulkChangeRequest.builder().setIssues(asList(issueKeys)).build()); + } catch (Exception e) { + assertHttpException(e, 400); + } + } + + @Test + public void should_add_comment_only_on_issues_that_will_be_changed() { + xooSampleLittleIssuesAnalysis.run(); + int nbIssues = BULK_EDITED_ISSUE_COUNT; + String[] issueKeys = searchIssueKeys(nbIssues); + + // Confirm an issue + adminIssueClient().doTransition(searchIssues().iterator().next().key(), "confirm"); + + // Apply a bulk change on unconfirm transition + BulkChangeWsResponse bulkChangeResponse = issuesService.bulkChange(BulkChangeRequest.builder().setIssues(asList(issueKeys)) + .setDoTransition("unconfirm") + .setComment("this is my comment") + .build()); + assertThat(bulkChangeResponse.getSuccess()).isEqualTo(1); + + int nbIssuesWithComment = 0; + for (Issues.Issue issue : issueRule.getByKeys(issueKeys)) { + if (!issue.getComments().getCommentsList().isEmpty()) { + nbIssuesWithComment++; + } + } + // Only one issue should have the comment + assertThat(nbIssuesWithComment).isEqualTo(1); + } + + private static void assertIssueSeverity(String[] issueKeys, String expectedSeverity) { + for (Issues.Issue issue : issueRule.getByKeys(issueKeys)) { + assertThat(issue.getSeverity().name()).isEqualTo(expectedSeverity); + } + } + + private static void assertIssueStatus(String[] issueKeys, String expectedStatus) { + for (Issues.Issue issue : issueRule.getByKeys(issueKeys)) { + assertThat(issue.getStatus()).isEqualTo(expectedStatus); + } + } + + private static void assertHttpException(Exception e, int expectedCode) { + assertThat(e).isInstanceOf(HttpException.class); + assertThat(((HttpException) e).status()).isEqualTo(expectedCode); + } + + private BulkChangeWsResponse bulkChangeSeverityOfIssues(String[] issueKeys, String newSeverity) { + BulkChangeRequest bulkChangeQuery = createBulkChangeSeverityOfIssuesQuery(issueKeys, newSeverity); + return issuesService.bulkChange(bulkChangeQuery); + } + + private static BulkChangeRequest createBulkChangeSeverityOfIssuesQuery(String[] issueKeys, String newSeverity) { + BulkChangeRequest.Builder request = BulkChangeRequest.builder().setSetSeverity(newSeverity); + if (issueKeys != null && issueKeys.length > 0) { + request.setIssues(asList(issueKeys)); + } + return request.build(); + } + + private BulkChangeWsResponse bulkTransitionStatusOfIssues(String[] issueKeys, String newSeverity) { + return issuesService.bulkChange(BulkChangeRequest.builder().setIssues(asList(issueKeys)).setDoTransition(newSeverity).build()); + } + + private BulkChangeWsResponse buldChangeAssigneeOfIssues(String[] issueKeys, String newAssignee) { + return issuesService.bulkChange(BulkChangeRequest.builder().setIssues(asList(issueKeys)).setAssign(newAssignee).build()); + } + + private static String[] searchIssueKeys(int limit) { + return getIssueKeys(issueRule.search(new SearchWsRequest()).getIssuesList(), limit); + } + + private static String[] getIssueKeys(List<Issues.Issue> issues, int nbIssues) { + return FluentIterable.from(issues) + .limit(nbIssues) + .transform(Issue::getKey) + .toArray(String.class); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueChangelogTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueChangelogTest.java new file mode 100644 index 00000000000..d96536d1bd1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueChangelogTest.java @@ -0,0 +1,124 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Issues.ChangelogWsResponse.Changelog; +import org.sonarqube.ws.client.WsClient; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; + +public class IssueChangelogTest extends AbstractIssueTest { + + private static WsClient adminClient; + + @Before + public void prepareData() { + ORCHESTRATOR.resetData(); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueChangelogTest/one-issue-per-line-profile.xml")); + ORCHESTRATOR.getServer().provisionProject("sample", "sample"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + adminClient = newAdminWsClient(ORCHESTRATOR); + } + + @Test + public void update_changelog_when_assigning_issue_by_user() throws Exception { + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + Issue issue = searchRandomIssue(); + assertIssueHasNoChange(issue.key()); + + adminIssueClient().assign(issue.key(), "admin"); + + List<Changelog> changes = changelog(issue.key()).getChangelogList(); + assertThat(changes).hasSize(1); + Changelog change = changes.get(0); + assertThat(change.getUser()).isEqualTo("admin"); + assertThat(change.getCreationDate()).isNotNull(); + assertThat(change.getDiffsList()) + .extracting(Changelog.Diff::getKey, Changelog.Diff::hasOldValue, Changelog.Diff::getNewValue) + .containsOnly(tuple("assignee", false, "Administrator")); + } + + @Test + public void update_changelog_when_reopening_unresolved_issue_by_scan() throws Exception { + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + Issue issue = searchRandomIssue(); + assertIssueHasNoChange(issue.key()); + + // re analyse the project after resolving an issue in order to reopen it + adminIssueClient().doTransition(issue.key(), "resolve"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + List<Changelog> changes = changelog(issue.key()).getChangelogList(); + assertThat(changes).hasSize(2); + + // Change done by the user (first change is be the oldest one) + Changelog change1 = changes.get(0); + assertThat(change1.getUser()).isEqualTo("admin"); + assertThat(change1.getCreationDate()).isNotNull(); + assertThat(change1.getDiffsList()) + .extracting(Changelog.Diff::getKey, Changelog.Diff::getOldValue, Changelog.Diff::getNewValue) + .containsOnly(tuple("resolution", "", "FIXED"), tuple("status", "OPEN", "RESOLVED")); + + // Change done by scan + Changelog change2 = changes.get(1); + assertThat(change2.hasUser()).isFalse(); + assertThat(change2.getCreationDate()).isNotNull(); + assertThat(change2.getDiffsList()) + .extracting(Changelog.Diff::getKey, Changelog.Diff::getOldValue, Changelog.Diff::getNewValue) + .containsOnly(tuple("resolution", "", ""), tuple("status", "RESOLVED", "REOPENED")); + } + + @Test + public void display_file_name_in_changelog_during_file_move() { + // version 1 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v1"); + + // version 2 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v3"); + + Issue issue = searchRandomIssue(); + List<Changelog> changes = changelog(issue.key()).getChangelogList(); + assertThat(changes).hasSize(1); + Changelog change = changes.get(0); + assertThat(change.hasUser()).isFalse(); + assertThat(change.getCreationDate()).isNotNull(); + assertThat(change.getDiffsList()) + .extracting(Changelog.Diff::getKey, Changelog.Diff::getOldValue, Changelog.Diff::getNewValue) + .containsOnly(tuple("file", "src/main/xoo/sample/Sample.xoo", "src/main/xoo/sample/Sample2.xoo")); + } + + private void assertIssueHasNoChange(String issueKey) { + assertThat(changelog(issueKey).getChangelogList()).isEmpty(); + } + + private static Issues.ChangelogWsResponse changelog(String issueKey) { + return adminClient.issues().changelog(issueKey); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDateTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDateTest.java new file mode 100644 index 00000000000..47ce6e5ac9f --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationDateTest.java @@ -0,0 +1,378 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.container.Server; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.Before; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonarqube.ws.ProjectAnalyses; +import org.sonarqube.ws.client.projectanalysis.SearchRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +/** + * @see <a href="https://jira.sonarsource.com/browse/MMF-567">MMF-567</a> + */ +public class IssueCreationDateTest extends AbstractIssueTest { + + private static final String ISSUE_STATUS_OPEN = "OPEN"; + + private static final String LANGUAGE_XOO = "xoo"; + + private static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; + private static final String DATE_FORMAT = "yyyy-MM-dd"; + + private static final String SAMPLE_PROJECT_KEY = "creation-date-sample"; + private static final String SAMPLE_PROJECT_NAME = "Creation date sample"; + private static final String SAMPLE_QUALITY_PROFILE_NAME = "creation-date-quality-profile"; + private static final String SAMPLE_EXPLICIT_DATE_1 = todayMinusDays(2); + private static final String SAMPLE_EXPLICIT_DATE_2 = todayMinusDays(1); + + private Server server = ORCHESTRATOR.getServer(); + + @Before + public void resetData() { + ORCHESTRATOR.resetData(); + server.provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_NAME); + } + + @Test + public void should_use_scm_date_for_new_issues_if_scm_is_available() { + analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM); + + assertNumberOfIssues(3); + assertIssueCreationDate(Component.OnlyInInitial, IssueCreationDate.OnlyInInitial_R1); + assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1); + assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1); + } + + @Test + public void should_use_analysis_date_for_new_issues_if_scm_is_not_available() { + analysis(QProfile.ONE_RULE, SourceCode.INITIAL); + + assertNumberOfIssues(3); + assertIssueCreationDates(COMPONENTS_OF_SOURCE_INITIAL, IssueCreationDate.FIRST_ANALYSIS); + } + + @Test + public void use_explicit_project_date_if_scm_is_not_available() { + analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.EXPLICIT_DATE_1); + + assertNumberOfIssues(3); + assertIssueCreationDates(COMPONENTS_OF_SOURCE_INITIAL, IssueCreationDate.EXPLICIT_DATE_1); + } + + @Test + public void use_scm_date_even_if_explicit_project_date_is_set() { + analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_1); + + assertNumberOfIssues(3); + assertIssueCreationDate(Component.OnlyInInitial, IssueCreationDate.OnlyInInitial_R1); + assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1); + assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1); + } + + @Test + public void no_rules_no_issues_if_scm_is_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM); + + assertNoIssue(); + } + + @Test + public void no_rules_no_issues_if_scm_is_not_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL); + + assertNoIssue(); + } + + @Test + public void use_scm_date_for_issues_raised_by_new_rules_if_scm_is_newly_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL); + analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM); + + assertNumberOfIssues(3); + assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2); + assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1); + assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1); + } + + @Test + public void use_scm_date_for_issues_raised_by_new_rules_if_scm_is_available_and_ever_has_been_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM); + analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM); + + assertNumberOfIssues(3); + assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2); + assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1); + assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1); + } + + @Test + public void use_analysis_date_for_issues_raised_by_new_rules_if_scm_is_not_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL); + analysis(QProfile.ONE_RULE, SourceCode.CHANGED); + + assertNumberOfIssues(3); + Stream.of(COMPONENTS_OF_SOURCE_CHANGED) + .forEach(component -> { + assertIssueCreationDate(component, IssueCreationDate.LATEST_ANALYSIS); + }); + } + + @Test + public void keep_the_date_of_an_existing_issue_even_if_the_blame_information_changes() { + analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM); + analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM); + + assertNumberOfIssues(3); + assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1); + assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1); + + // this file is new to the second analysis + assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.LATEST_ANALYSIS); + } + + @Test + public void ignore_explicit_date_for_issues_related_to_new_rules_if_scm_is_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_1); + analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_2); + + assertNumberOfIssues(3); + assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2); + assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1); + assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1); + } + + @Test + public void use_explicit_date_for_issues_related_to_new_rules_if_scm_is_not_available() { + analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.EXPLICIT_DATE_1); + analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.EXPLICIT_DATE_2); + + assertNumberOfIssues(3); + assertIssueCreationDates(COMPONENTS_OF_SOURCE_CHANGED, IssueCreationDate.EXPLICIT_DATE_2); + } + + private void analysis(QProfile qProfile, SourceCode sourceCode, ScannerFeature... scm) { + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource(qProfile.path)); + server.associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, LANGUAGE_XOO, SAMPLE_QUALITY_PROFILE_NAME); + + SonarScanner scanner = SonarScanner.create(projectDir(sourceCode.path)); + Arrays.stream(scm).forEach(s -> s.configure(scanner)); + ORCHESTRATOR.executeBuild(scanner); + } + + private static void assertNoIssue() { + assertNumberOfIssues(0); + } + + private static void assertNumberOfIssues(int number) { + assertThat(getIssues(issueQuery())).hasSize(number); + } + + private static void assertIssueCreationDate(Component component, IssueCreationDate expectedDate) { + assertIssueCreationDates(new Component[] {component}, expectedDate); + } + + private static void assertIssueCreationDates(Component[] components, IssueCreationDate expectedDate) { + String[] keys = Arrays.stream(components).map(Component::getKey).toArray(String[]::new); + List<Issue> issues = getIssues(issueQuery().components(keys)); + Date[] dates = Arrays.stream(components).map(x -> expectedDate.getDate()).toArray(Date[]::new); + + assertThat(issues) + .extracting(Issue::creationDate) + .containsExactly(dates); + } + + private static List<Issue> getIssues(IssueQuery query) { + return issueClient().find(query).list(); + } + + private static IssueQuery issueQuery() { + return IssueQuery.create().statuses(ISSUE_STATUS_OPEN); + } + + private static Date dateTimeParse(String expectedDate) { + try { + return new SimpleDateFormat(DATETIME_FORMAT).parse(expectedDate); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + private static Date dateParse(String expectedDate) { + try { + return new SimpleDateFormat(DATE_FORMAT).parse(expectedDate); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + private static String todayMinusDays(int numberOfDays) { + return DateTimeFormatter.ofPattern(DATE_FORMAT).format(LocalDate.now().atStartOfDay().minusDays(numberOfDays)); + } + + private enum SourceCode { + INITIAL("issue/creationDateSampleInitial"), + CHANGED("issue/creationDateSampleChanged"), + ; + + private final String path; + + private SourceCode(String path) { + this.path = path; + } + } + + private enum Component { + OnlyInInitial("creation-date-sample:src/main/xoo/sample/OnlyInInitial.xoo"), + ForeverAndModified("creation-date-sample:src/main/xoo/sample/ForeverAndModified.xoo"), + ForeverAndUnmodified("creation-date-sample:src/main/xoo/sample/ForeverAndUnmodified.xoo"), + OnlyInChanged("creation-date-sample:src/main/xoo/sample/OnlyInChanged.xoo"), + ; + private final String key; + + private Component(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + } + + private static final Component[] COMPONENTS_OF_SOURCE_INITIAL = {Component.OnlyInInitial, Component.ForeverAndModified, Component.ForeverAndUnmodified}; + private static final Component[] COMPONENTS_OF_SOURCE_CHANGED = {Component.ForeverAndModified, Component.ForeverAndUnmodified, Component.OnlyInChanged}; + + private enum QProfile { + ONE_RULE("/issue/IssueCreationDateTest/one-rule.xml"), + NO_RULES("/issue/IssueCreationDateTest/no-rules.xml"), + ; + + private final String path; + + private QProfile(String path) { + this.path = path; + } + } + + private enum ScannerFeature { + SCM { + @Override + void configure(SonarScanner scanner) { + scanner + .setProperty("sonar.scm.provider", "xoo") + .setProperty("sonar.scm.disabled", "false"); + } + }, + EXPLICIT_DATE_1 { + @Override + void configure(SonarScanner scanner) { + scanner + .setProperty("sonar.projectDate", SAMPLE_EXPLICIT_DATE_1); + } + }, + EXPLICIT_DATE_2 { + @Override + void configure(SonarScanner scanner) { + scanner + .setProperty("sonar.projectDate", SAMPLE_EXPLICIT_DATE_2); + } + }, + ; + + void configure(SonarScanner scanner) { + } + } + + private enum IssueCreationDate { + OnlyInInitial_R1(dateTimeParse("2001-01-01T00:00:00+0000")), + ForeverAndUnmodified_R1(dateTimeParse("2002-01-01T00:00:00+0000")), + ForeverAndModified_R1(dateTimeParse("2003-01-01T00:00:00+0000")), + ForeverAndModified_R2(dateTimeParse("2004-01-01T00:00:00+0000")), + OnlyInChanged_R1(dateTimeParse("2005-01-01T00:00:00+0000")), + EXPLICIT_DATE_1(dateParse(SAMPLE_EXPLICIT_DATE_1)), + EXPLICIT_DATE_2(dateParse(SAMPLE_EXPLICIT_DATE_2)), + FIRST_ANALYSIS { + @Override + Date getDate() { + return getAnalysisDate(l -> { + if (l.isEmpty()) { + return Optional.empty(); + } + return Optional.of(l.get(l.size() - 1)); + }); + } + }, + LATEST_ANALYSIS { + @Override + Date getDate() { + return getAnalysisDate(l -> { + if (l.size() > 0) { + return Optional.of(l.get(0)); + } + return Optional.empty(); + }); + } + }, + ; + + private final Date date; + + private IssueCreationDate() { + this.date = null; + } + + private IssueCreationDate(Date date) { + this.date = date; + } + + Date getDate() { + return date; + } + + private static Date getAnalysisDate(Function<List<ProjectAnalyses.Analysis>, Optional<ProjectAnalyses.Analysis>> chooseItem) { + return Optional.of( + ItUtils.newWsClient(ORCHESTRATOR) + .projectAnalysis() + .search(SearchRequest.builder().setProject(SAMPLE_PROJECT_KEY).build()) + .getAnalysesList()) + .flatMap(chooseItem) + .map(ProjectAnalyses.Analysis::getDate) + .map(IssueCreationDateTest::dateTimeParse) + .orElseThrow(() -> new IllegalStateException("There is no analysis")); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationTest.java new file mode 100644 index 00000000000..b6afaf17304 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueCreationTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.issue.Issues; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.runProjectAnalysis; + +public class IssueCreationTest extends AbstractIssueTest { + + private static final String SAMPLE_PROJECT_KEY = "sample"; + + @Before + public void resetData() { + ORCHESTRATOR.resetData(); + } + + /** + * See SONAR-4785 + */ + @Test + public void use_rule_name_if_issue_has_no_message() { + ORCHESTRATOR.getServer().provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_KEY); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueCreationTest/with-custom-message.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "with-custom-message"); + + // First analysis, the issue is generated with a message + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample", "sonar.customMessage.message", "a message"); + Issue issue = issueClient().find(IssueQuery.create()).list().get(0); + assertThat(issue.message()).isEqualTo("a message"); + + // Second analysis, the issue is generated without any message, the name of the rule is used + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + issue = issueClient().find(IssueQuery.create()).list().get(0); + assertThat(issue.message()).isEqualTo("Issue With Custom Message"); + } + + @Test + public void plugin_can_override_profile_severity() throws Exception { + ORCHESTRATOR.getServer().provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_KEY); + + // The rule "OneBlockerIssuePerFile" is enabled with severity "INFO" + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueCreationTest/override-profile-severity.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "override-profile-severity"); + + // But it's hardcoded "blocker" when plugin generates the issue + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + Issues issues = search(IssueQuery.create().rules("xoo:OneBlockerIssuePerFile")); + assertThat(issues.size()).isGreaterThan(0); + for (Issue issue : issues.list()) { + assertThat(issue.severity()).isEqualTo("BLOCKER"); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterExtensionTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterExtensionTest.java new file mode 100644 index 00000000000..7dc46462f63 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterExtensionTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; +import util.issue.IssueRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasureAsDouble; + +/** + * Tests the extension point IssueFilter + */ +public class IssueFilterExtensionTest extends AbstractIssueTest { + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + @ClassRule + public static final IssueRule issueRule = IssueRule.from(ORCHESTRATOR); + + private final String manyRuleProfileKey = projectAnalysisRule.registerProfile("/issue/IssueFilterExtensionTest/xoo-with-many-rules.xml"); + private final String xooMultiModuleProjectKey = projectAnalysisRule.registerProject("shared/xoo-multi-modules-sample"); + private final ProjectAnalysis analysis = projectAnalysisRule.newProjectAnalysis(xooMultiModuleProjectKey) + .withQualityProfile(manyRuleProfileKey); + + @Test + public void should_filter_files() throws Exception { + analysis.withProperties("sonar.exclusions", "**/HelloA1.xoo").run(); + + List<Issue> issues = searchIssues(); + assertThat(issues).isNotEmpty(); + for (Issue issue : issues) { + // verify exclusion to avoid false positive + assertThat(issue.componentKey()).doesNotContain("HelloA1"); + } + + assertThat(getMeasureAsDouble(ORCHESTRATOR, xooMultiModuleProjectKey, "violations").intValue()).isEqualTo(issues.size()); + } + + @Test + public void should_filter_issues() { + // first analysis without issue-filter + analysis.run(); + + // Issue filter removes issues on lines < 5 + // Deprecated violation filter removes issues detected by PMD + List<Issue> unresolvedIssues = searchResolvedIssues(xooMultiModuleProjectKey); + int issuesBeforeLine5 = countIssuesBeforeLine5(unresolvedIssues); + int pmdIssues = countModuleIssues(unresolvedIssues); + assertThat(issuesBeforeLine5).isGreaterThan(0); + assertThat(pmdIssues).isGreaterThan(0); + + // Enable issue filters + analysis.withProperties("enableIssueFilters", "true").run(); + + unresolvedIssues = searchResolvedIssues(xooMultiModuleProjectKey); + List<Issue> resolvedIssues = searchUnresolvedIssues(xooMultiModuleProjectKey); + assertThat(countIssuesBeforeLine5(unresolvedIssues)).isZero(); + assertThat(countModuleIssues(unresolvedIssues)).isZero(); + assertThat(countModuleIssues(resolvedIssues)).isGreaterThan(0); + for (Issue issue : resolvedIssues) { + // SONAR-6364 no line number on closed issues + assertThat(issue.line()).isNull(); + } + } + + private static List<Issue> searchUnresolvedIssues(String projectKey) { + return searchIssues(IssueQuery.create().componentRoots(projectKey).resolved(true)); + } + + private static List<Issue> searchResolvedIssues(String projectKey) { + return searchIssues(IssueQuery.create().componentRoots(projectKey).resolved(false)); + } + + private static int countModuleIssues(List<Issue> issues) { + int count = 0; + for (Issue issue : issues) { + if (issue.ruleKey().equals("xoo:OneIssuePerModule")) { + count++; + } + } + return count; + } + + private static int countIssuesBeforeLine5(List<Issue> issues) { + int count = 0; + for (Issue issue : issues) { + if (issue.line() != null && issue.line() < 5) { + count++; + } + } + return count; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterOnCommonRulesTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterOnCommonRulesTest.java new file mode 100644 index 00000000000..8012aec2ef7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterOnCommonRulesTest.java @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.apache.commons.lang.ArrayUtils; +import org.junit.Before; +import org.junit.Test; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperties; + +public class IssueFilterOnCommonRulesTest extends AbstractIssueTest { + + private static WsClient adminWsClient; + + private static final String PROJECT_KEY = "common-rules-project"; + private static final String PROJECT_DIR = "issue/common-rules"; + + private static final String FILE_KEY = "common-rules-project:src/Sample.xoo"; + + @Before + public void resetData() { + ORCHESTRATOR.resetData(); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueFilterOnCommonRulesTest/xoo-common-rules-profile.xml")); + ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY, "Sample"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "xoo-common-rules"); + + adminWsClient = newAdminWsClient(ORCHESTRATOR); + } + + @Test + public void ignore_all() { + executeAnalysis("sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.ruleKey", "*", + "sonar.issue.ignore.multicriteria.1.resourceKey", "**/*.xoo"); + + assertThat(findAllIssues()).hasSize(0); + } + + @Test + public void ignore_some_rule_and_file() { + executeAnalysis( + "sonar.issue.ignore.multicriteria", "1,2", + "sonar.issue.ignore.multicriteria.1.ruleKey", "common-xoo:DuplicatedBlocks", + "sonar.issue.ignore.multicriteria.1.resourceKey", "**/Sample.xoo", + "sonar.issue.ignore.multicriteria.2.ruleKey", "common-xoo:SkippedUnitTests", + "sonar.issue.ignore.multicriteria.2.resourceKey", "**/SampleTest.xoo"); + + assertThat(findAllIssues()).hasSize(4); + assertThat(findIssuesByRuleKey("common-xoo:DuplicatedBlocks")).isEmpty(); + assertThat(findIssuesByRuleKey("common-xoo:SkippedUnitTests")).isEmpty(); + } + + @Test + public void enforce_one_file() { + executeAnalysis( + "sonar.issue.enforce.multicriteria", "1", + "sonar.issue.enforce.multicriteria.1.ruleKey", "*", + // Only issues on this file will be accepted + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/Sample.xoo"); + + assertThat(findAllIssues()).hasSize(4); + } + + @Test + public void enforce_on_rules() { + executeAnalysis( + "sonar.issue.enforce.multicriteria", "1,2", + "sonar.issue.enforce.multicriteria.1.ruleKey", "common-xoo:DuplicatedBlocks", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/Sample.xoo", + // This rule should only be applied on a file that do not exist => no issue for this rule + "sonar.issue.enforce.multicriteria.2.ruleKey", "common-xoo:InsufficientCommentDensity", + "sonar.issue.enforce.multicriteria.2.resourceKey", "**/OtherFile.xoo"); + + assertThat(findAllIssues()).hasSize(5); + assertThat(findIssuesByRuleKey("common-xoo:DuplicatedBlocks")).hasSize(1); + assertThat(findIssuesByRuleKey("common-xoo:InsufficientCommentDensity")).isEmpty(); + } + + private void executeAnalysis(String... serverProperties) { + String[] cpdProperties = new String[] { + "sonar.cpd.xoo.minimumTokens", "2", + "sonar.cpd.xoo.minimumLines", "2" + }; + setServerProperties(ORCHESTRATOR, PROJECT_KEY, (String[]) ArrayUtils.addAll(serverProperties, cpdProperties)); + runProjectAnalysis(ORCHESTRATOR, PROJECT_DIR); + } + + private List<Issues.Issue> findIssuesByRuleKey(String ruleKey) { + return adminWsClient.issues().search( + new SearchWsRequest() + .setComponents(singletonList(FILE_KEY)) + .setRules(singletonList(ruleKey))) + .getIssuesList(); + } + + private List<Issues.Issue> findAllIssues() { + return adminWsClient.issues().search(new SearchWsRequest()).getIssuesList(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterTest.java new file mode 100644 index 00000000000..bc906d6093d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueFilterTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperties; + +public class IssueFilterTest extends AbstractIssueTest { + + private static final String PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-exclusions"; + private static final String PROJECT_DIR = "exclusions/xoo-multi-modules"; + + @Before + public void resetData() { + ORCHESTRATOR.resetData(); + + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueFilterTest/with-many-rules.xml")); + ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY, "project"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "with-many-rules"); + } + + @Test + public void ignore_all_files() { + executeAnalysis( + "sonar.issue.ignore.multicriteria", "1", + "sonar.issue.ignore.multicriteria.1.resourceKey", "**/*.xoo", + "sonar.issue.ignore.multicriteria.1.ruleKey", "*"); + + checkIssueCountBySeverity(4, 0, 0, 4); + } + + @Test + public void enforce_only_on_one_file() { + executeAnalysis( + "sonar.issue.enforce.multicriteria", "1", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/HelloA1.xoo", + "sonar.issue.enforce.multicriteria.1.ruleKey", "*"); + + checkIssueCountBySeverity( + 1 /* tag */ + 18 /* lines in HelloA1.xoo */ + 1 /* file */, + 0 + 1, + 0, + 0); + } + + @Test + public void enforce_on_two_files_with_same_rule() { + executeAnalysis( + "sonar.issue.enforce.multicriteria", "1,2", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/HelloA1.xoo", + "sonar.issue.enforce.multicriteria.1.ruleKey", "*", + "sonar.issue.enforce.multicriteria.2.resourceKey", "**/HelloA2.xoo", + "sonar.issue.enforce.multicriteria.2.ruleKey", "*"); + + checkIssueCountBySeverity( + 2 /* tags */ + 18 /* lines in HelloA1.xoo */ + 15 /* lines in HelloA2.xoo */ + 2 /* files */, + 0 + 2, + 0, + 0); + } + + @Test + public void enforce_on_two_files_with_different_rules() { + executeAnalysis( + "sonar.issue.enforce.multicriteria", "1,2", + "sonar.issue.enforce.multicriteria.1.resourceKey", "**/HelloA1.xoo", + "sonar.issue.enforce.multicriteria.1.ruleKey", "xoo:OneIssuePerLine", + "sonar.issue.enforce.multicriteria.2.resourceKey", "**/HelloA2.xoo", + "sonar.issue.enforce.multicriteria.2.ruleKey", "xoo:HasTag"); + + checkIssueCountBySeverity( + 1 /* tag in HelloA2 */ + 18 /* lines in HelloA1.xoo */ + 4 /* files */ + 4 /* modules */, + 4, + 0, + 4); + } + + private void executeAnalysis(String... serverProperties) { + setServerProperties(ORCHESTRATOR, PROJECT_KEY, serverProperties); + runProjectAnalysis(ORCHESTRATOR, PROJECT_DIR); + } + + private void checkIssueCountBySeverity(int total, int perFile, int perCommonRule, int perModule) { + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(ORCHESTRATOR, PROJECT_KEY, "violations", "major_violations", "blocker_violations", "critical_violations"); + assertThat(measures.get("violations").intValue()).isEqualTo(total); + assertThat(measures.get("major_violations").intValue()).isEqualTo(perFile); // One per file + assertThat(measures.get("blocker_violations").intValue()).isEqualTo(perCommonRule); // On per common rule + assertThat(measures.get("critical_violations").intValue()).isEqualTo(perModule); // One per module + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java new file mode 100644 index 00000000000..8b91bb76477 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.runProjectAnalysis; + +public class IssueMeasureTest extends AbstractIssueTest { + + private static final String MULTI_MODULE_SAMPLE_PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String SAMPLE_PROJECT_KEY = "sample"; + + @Before + public void resetData() { + ORCHESTRATOR.resetData(); + } + + @Test + public void issues_by_severity_measures() { + ORCHESTRATOR.getServer().provisionProject(MULTI_MODULE_SAMPLE_PROJECT_KEY, MULTI_MODULE_SAMPLE_PROJECT_KEY); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/with-many-rules.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(MULTI_MODULE_SAMPLE_PROJECT_KEY, "xoo", "with-many-rules"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample"); + + assertThat(search(IssueQuery.create().componentRoots(MULTI_MODULE_SAMPLE_PROJECT_KEY)).paging().total()).isEqualTo(136); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(ORCHESTRATOR, MULTI_MODULE_SAMPLE_PROJECT_KEY, "violations", "info_violations", "minor_violations", + "major_violations", + "blocker_violations", "critical_violations"); + assertThat(measures.get("violations")).isEqualTo(136); + assertThat(measures.get("info_violations")).isEqualTo(2); + assertThat(measures.get("minor_violations")).isEqualTo(61); + assertThat(measures.get("major_violations")).isEqualTo(65); + assertThat(measures.get("blocker_violations")).isEqualTo(4); + assertThat(measures.get("critical_violations")).isEqualTo(4); + } + + /** + * SONAR-4330 + * SONAR-7555 + */ + @Test + public void issues_by_resolution_and_status_measures() { + ORCHESTRATOR.getServer().provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_KEY); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-line-profile.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "one-issue-per-line-profile"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + List<Issue> issues = searchIssuesByProject(SAMPLE_PROJECT_KEY); + assertThat(issues).hasSize(17); + + // 1 is a false-positive, 1 is a won't fix, 1 is confirmed, 1 is reopened, and the remaining ones stays open + adminIssueClient().doTransition(issues.get(0).key(), "falsepositive"); + adminIssueClient().doTransition(issues.get(1).key(), "wontfix"); + adminIssueClient().doTransition(issues.get(2).key(), "confirm"); + adminIssueClient().doTransition(issues.get(3).key(), "resolve"); + adminIssueClient().doTransition(issues.get(3).key(), "reopen"); + + // Re analyze the project to compute measures + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(ORCHESTRATOR, SAMPLE_PROJECT_KEY, "false_positive_issues", "wont_fix_issues", "open_issues", "reopened_issues", + "confirmed_issues"); + assertThat(measures.get("false_positive_issues")).isEqualTo(1); + assertThat(measures.get("wont_fix_issues")).isEqualTo(1); + assertThat(measures.get("open_issues")).isEqualTo(13); + assertThat(measures.get("reopened_issues")).isEqualTo(1); + assertThat(measures.get("confirmed_issues")).isEqualTo(1); + } + + @Test + public void no_issue_are_computed_on_empty_profile() { + ORCHESTRATOR.getServer().provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_KEY); + + // no active rules + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "empty"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + assertThat(searchIssuesByProject(SAMPLE_PROJECT_KEY)).isEmpty(); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(ORCHESTRATOR, SAMPLE_PROJECT_KEY, "violations", "blocker_violations"); + assertThat(measures.get("violations")).isEqualTo(0); + assertThat(measures.get("blocker_violations")).isEqualTo(0); + } + + /** + * SONAR-3746 + */ + @Test + public void issues_measures_on_test_files() { + String projectKey = "sample-with-tests"; + String testKey = "sample-with-tests:src/test/xoo/sample/SampleTest.xoo"; + + ORCHESTRATOR.getServer().provisionProject(projectKey, projectKey); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-file-profile.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(projectKey, "xoo", "one-issue-per-file-profile"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample-with-tests"); + + // Store current number of issues + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(ORCHESTRATOR, testKey, "violations"); + assertThat(measures.get("violations")).isEqualTo(1); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueNotificationsTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueNotificationsTest.java new file mode 100644 index 00000000000..49e4cee8afc --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueNotificationsTest.java @@ -0,0 +1,236 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.Iterator; +import javax.mail.internet.MimeMessage; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueClient; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.issue.Issues; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.BulkChangeRequest; +import org.sonarqube.ws.client.issue.IssuesService; +import org.subethamail.wiser.Wiser; +import org.subethamail.wiser.WiserMessage; +import util.ItUtils; +import util.user.UserRule; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.resetEmailSettings; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +public class IssueNotificationsTest extends AbstractIssueTest { + + private final static String PROJECT_KEY = "sample"; + private final static String USER_LOGIN = "tester"; + private final static String USER_PASSWORD = "tester"; + private static final String USER_EMAIL = "tester@example.org"; + + private static Wiser smtpServer; + + private IssueClient issueClient; + + private IssuesService issuesService; + + @ClassRule + public static UserRule userRule = UserRule.from(ORCHESTRATOR); + + @BeforeClass + public static void before() throws Exception { + smtpServer = new Wiser(0); + smtpServer.start(); + System.out.println("SMTP Server port: " + smtpServer.getServer().getPort()); + + // Configure Sonar + resetEmailSettings(ORCHESTRATOR); + setServerProperty(ORCHESTRATOR, "email.smtp_host.secured", "localhost"); + setServerProperty(ORCHESTRATOR, "email.smtp_port.secured", Integer.toString(smtpServer.getServer().getPort())); + + // Send test email to the test user + newAdminWsClient(ORCHESTRATOR).wsConnector().call(new PostRequest("api/emails/send") + .setParam("to", USER_EMAIL) + .setParam("message", "This is a test message from SonarQube")) + .failIfNotSuccessful(); + + // We need to wait until all notifications will be delivered + waitUntilAllNotificationsAreDelivered(1); + + Iterator<WiserMessage> emails = smtpServer.getMessages().iterator(); + + MimeMessage message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<" + USER_EMAIL + ">"); + assertThat((String) message.getContent()).contains("This is a test message from SonarQube"); + + assertThat(emails.hasNext()).isFalse(); + } + + @AfterClass + public static void stop() { + if (smtpServer != null) { + smtpServer.stop(); + } + userRule.deactivateUsers(USER_LOGIN); + setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", null); + resetEmailSettings(ORCHESTRATOR); + } + + @Before + public void prepare() { + ORCHESTRATOR.resetData(); + + // Create test user + userRule.createUser(USER_LOGIN, "Tester", USER_EMAIL, USER_LOGIN); + + smtpServer.getMessages().clear(); + issueClient = ORCHESTRATOR.getServer().adminWsClient().issueClient(); + issuesService = newAdminWsClient(ORCHESTRATOR).issues(); + + setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", null); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-line-profile.xml")); + ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY, "Sample"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "one-issue-per-line-profile"); + + // Add notifications to the test user + WsClient wsClient = newUserWsClient(ORCHESTRATOR, USER_LOGIN, USER_PASSWORD); + wsClient.wsConnector().call(new PostRequest("api/notifications/add") + .setParam("type", "NewIssues") + .setParam("channel", "EmailNotificationChannel")) + .failIfNotSuccessful(); + wsClient.wsConnector().call(new PostRequest("api/notifications/add") + .setParam("type", "ChangesOnMyIssue") + .setParam("channel", "EmailNotificationChannel")) + .failIfNotSuccessful(); + wsClient.wsConnector().call(new PostRequest("api/notifications/add") + .setParam("type", "SQ-MyNewIssues") + .setParam("channel", "EmailNotificationChannel")) + .failIfNotSuccessful(); + + } + + @Test + public void notifications_for_new_issues_and_issue_changes() throws Exception { + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample", "sonar.projectDate", "2015-12-15"); + + // change assignee + Issues issues = issueClient.find(IssueQuery.create().componentRoots(PROJECT_KEY)); + Issue issue = issues.list().get(0); + issueClient.assign(issue.key(), USER_LOGIN); + + waitUntilAllNotificationsAreDelivered(2); + + Iterator<WiserMessage> emails = smtpServer.getMessages().iterator(); + + assertThat(emails.hasNext()).isTrue(); + MimeMessage message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>"); + assertThat((String) message.getContent()).contains("Sample"); + assertThat((String) message.getContent()).contains("17 new issues (new debt: 17min)"); + assertThat((String) message.getContent()).contains("Severity"); + assertThat((String) message.getContent()).contains("One Issue Per Line (xoo): 17"); + assertThat((String) message.getContent()).contains( + "See it in SonarQube: http://localhost:9000/project/issues?id=sample&createdAt=2015-12-15T00%3A00%3A00%2B"); + + assertThat(emails.hasNext()).isTrue(); + message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>"); + assertThat((String) message.getContent()).contains("sample/Sample.xoo"); + assertThat((String) message.getContent()).contains("Assignee changed to Tester"); + assertThat((String) message.getContent()).contains( + "See it in SonarQube: http://localhost:9000/project/issues?id=sample&issues=" + issue.key() + "&open=" + issue.key()); + + assertThat(emails.hasNext()).isFalse(); + } + + @Test + public void notifications_for_personalized_emails() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", USER_LOGIN); + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-with-scm", "sonar.scm.provider", "xoo", "sonar.scm.disabled", "false"); + + waitUntilAllNotificationsAreDelivered(2); + + Iterator<WiserMessage> emails = smtpServer.getMessages().iterator(); + emails.next(); + // the second email sent is the personalized one + MimeMessage message = emails.next().getMimeMessage(); + + assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>"); + assertThat(message.getSubject()).contains("You have 13 new issues"); + } + + /** + * SONAR-4606 + */ + @Test + public void notifications_for_bulk_change_ws() throws Exception { + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample", "sonar.projectDate", "2015-12-15"); + + Issues issues = issueClient.find(IssueQuery.create().componentRoots(PROJECT_KEY)); + Issue issue = issues.list().get(0); + + // bulk change without notification by default + issuesService.bulkChange(BulkChangeRequest.builder() + .setIssues(singletonList(issue.key())) + .setAssign(USER_LOGIN) + .setSetSeverity("MINOR") + .build()); + + // bulk change with notification + issuesService.bulkChange(BulkChangeRequest.builder() + .setIssues(singletonList(issue.key())) + .setSetSeverity("BLOCKER") + .setSendNotifications(true) + .build()); + + waitUntilAllNotificationsAreDelivered(2); + + Iterator<WiserMessage> emails = smtpServer.getMessages().iterator(); + + emails.next(); + MimeMessage message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>"); + assertThat((String) message.getContent()).contains("sample/Sample.xoo"); + assertThat((String) message.getContent()).contains("Severity: BLOCKER (was MINOR)"); + assertThat((String) message.getContent()).contains( + "See it in SonarQube: http://localhost:9000/project/issues?id=sample&issues=" + issue.key() + "&open=" + issue.key()); + + assertThat(emails.hasNext()).isFalse(); + } + + private static void waitUntilAllNotificationsAreDelivered(int expectedNumberOfEmails) throws InterruptedException { + for (int i = 0; i < 10; i++) { + if (smtpServer.getMessages().size() == expectedNumberOfEmails) { + break; + } + Thread.sleep(1_000); + } + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssuePurgeTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssuePurgeTest.java new file mode 100644 index 00000000000..63a033713ac --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssuePurgeTest.java @@ -0,0 +1,187 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.issue.Issues; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssuePurgeTest extends AbstractIssueTest { + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + private ProjectAnalysis xooSampleAnalysis; + private ProjectAnalysis xooMultiModuleAnalysis; + + @Before + public void setUp() throws Exception { + String manyRulesProfile = projectAnalysisRule.registerProfile("/issue/IssuePurgeTest/with-many-rules.xml"); + String xooSampleProjectKey = projectAnalysisRule.registerProject("shared/xoo-sample"); + this.xooSampleAnalysis = projectAnalysisRule.newProjectAnalysis(xooSampleProjectKey) + .withQualityProfile(manyRulesProfile); + String xooMultiModuleProjectKey = projectAnalysisRule.registerProject("shared/xoo-multi-modules-sample"); + this.xooMultiModuleAnalysis = projectAnalysisRule.newProjectAnalysis(xooMultiModuleProjectKey) + .withQualityProfile(manyRulesProfile); + } + + /** + * SONAR-4308 + */ + @Test + public void purge_old_closed_issues() throws Exception { + projectAnalysisRule.setServerProperty("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "5000"); + + // Generate some issues + xooSampleAnalysis.withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.projectDate", "2014-10-01") + .run(); + + // All the issues are open + List<Issue> issuesList = searchIssues(); + for (Issue issue : issuesList) { + assertThat(issue.resolution()).isNull(); + } + + // Second scan with empty profile -> all issues are resolved and closed + // -> Not deleted because less than 5000 days long + xooSampleAnalysis + .withXooEmptyProfile() + .withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.projectDate", "2014-10-15") + .run(); + issuesList = searchIssues(); + assertThat(issuesList).isNotEmpty(); + for (Issue issue : issuesList) { + assertThat(issue.resolution()).isNotNull(); + assertThat(issue.status()).isEqualTo("CLOSED"); + } + + // Third scan -> closed issues are deleted + projectAnalysisRule.setServerProperty("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "1"); + + xooSampleAnalysis.withXooEmptyProfile() + .withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.projectDate", "2014-10-20") + .run(); + Issues issues = issueClient().find(IssueQuery.create()); + assertThat(issues.list()).isEmpty(); + assertThat(issues.paging().total()).isZero(); + } + + /** + * SONAR-7108 + */ + @Test + public void purge_old_closed_issues_when_zero_closed_issues_wanted() throws Exception { + projectAnalysisRule.setServerProperty("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "5000"); + + // Generate some issues + xooSampleAnalysis.withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.projectDate", "2014-10-01") + .run(); + + // All the issues are open + List<Issue> issueList = searchIssues(); + for (Issue issue : issueList) { + assertThat(issue.resolution()).isNull(); + } + + // Second scan with empty profile -> all issues are resolved and closed + // -> Not deleted because less than 5000 days long + xooSampleAnalysis + .withXooEmptyProfile() + .withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.projectDate", "2014-10-15") + .run(); + issueList = searchIssues(); + assertThat(issueList).isNotEmpty(); + for (Issue issue : issueList) { + assertThat(issue.resolution()).isNotNull(); + assertThat(issue.status()).isEqualTo("CLOSED"); + } + + // Third scan -> closed issues are deleted + projectAnalysisRule.setServerProperty("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "0"); + + xooSampleAnalysis.withXooEmptyProfile() + .withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.projectDate", "2014-10-20") + .run(); + + Issues issues = issueClient().find(IssueQuery.create()); + assertThat(issues.list()).isEmpty(); + assertThat(issues.paging().total()).isZero(); + } + + /** + * SONAR-5200 + */ + @Test + public void resolve_issues_when_removing_module() throws Exception { + // Generate some issues + xooMultiModuleAnalysis + .withProperties("sonar.dynamicAnalysis", "false") + .run(); + + // All the issues are open + List<Issue> issues = searchIssues(); + for (Issue issue : issues) { + assertThat(issue.resolution()).isNull(); + } + Issue issue = issues.get(0); + + int issuesOnModuleB = searchIssues(IssueQuery.create().componentRoots("com.sonarsource.it.samples:multi-modules-sample:module_b")).size(); + assertThat(issuesOnModuleB).isEqualTo(28); + + // Second scan without module B -> issues on module B are resolved as removed and closed + xooMultiModuleAnalysis + .withProperties( + "sonar.dynamicAnalysis", "false", + "sonar.modules", "module_a") + .run(); + + // Resolved should should all be mark as REMOVED and affect to module b + List<Issue> reloadedIssues = searchIssues(IssueQuery.create().resolved(true)); + assertThat(reloadedIssues).hasSize(issuesOnModuleB); + for (Issue reloadedIssue : reloadedIssues) { + assertThat(reloadedIssue.resolution()).isEqualTo("FIXED"); + assertThat(reloadedIssue.status()).isEqualTo("CLOSED"); + assertThat(reloadedIssue.componentKey()).contains("com.sonarsource.it.samples:multi-modules-sample:module_b"); + assertThat(reloadedIssue.updateDate().before(issue.updateDate())).isFalse(); + assertThat(reloadedIssue.closeDate()).isNotNull(); + assertThat(reloadedIssue.closeDate().before(reloadedIssue.creationDate())).isFalse(); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java new file mode 100644 index 00000000000..1ad0be32cd9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java @@ -0,0 +1,277 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import org.apache.commons.lang.time.DateUtils; +import org.assertj.core.api.Fail; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.sonar.wsclient.base.HttpException; +import org.sonar.wsclient.base.Paging; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonar.wsclient.issue.Issues; +import org.sonarqube.ws.Common; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.Issues.SearchWsResponse; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; +import static util.ItUtils.toDate; + +public class IssueSearchTest extends AbstractIssueTest { + + private static final String PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String PROJECT_KEY2 = "com.sonarsource.it.samples:multi-modules-sample2"; + + private static int DEFAULT_PAGINATED_RESULTS = 100; + private static int TOTAL_NB_ISSUES = 272; + + @BeforeClass + public static void prepareData() { + ORCHESTRATOR.resetData(); + + ItUtils.restoreProfile(ORCHESTRATOR, IssueSearchTest.class.getResource("/issue/with-many-rules.xml")); + + // Launch 2 analysis to have more than 100 issues in total + ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY, PROJECT_KEY); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "with-many-rules"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample"); + + ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY2, PROJECT_KEY2); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY2, "xoo", "with-many-rules"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample", "sonar.projectKey", PROJECT_KEY2); + + // Assign a issue to test search by assignee + adminIssueClient().assign(searchRandomIssue().key(), "admin"); + + // Resolve a issue to test search by status and by resolution + adminIssueClient().doTransition(searchRandomIssue().key(), "resolve"); + } + + @Before + public void resetProperties() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.forceAuthentication", "false"); + } + + @Test + public void search_all_issues() { + assertThat(search(IssueQuery.create()).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + } + + @Test + public void search_issues_by_component_roots() { + assertThat(search(IssueQuery.create().componentRoots("com.sonarsource.it.samples:multi-modules-sample")).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + assertThat(search(IssueQuery.create().componentRoots("com.sonarsource.it.samples:multi-modules-sample:module_a")).list()).hasSize(82); + assertThat(search(IssueQuery.create().componentRoots("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1")).list()).hasSize(36); + + assertThat(search(IssueQuery.create().componentRoots("unknown")).list()).isEmpty(); + } + + @Test + public void search_issues_by_components() { + assertThat( + search(IssueQuery.create().components("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo")).list()) + .hasSize(34); + assertThat(search(IssueQuery.create().components("unknown")).list()).isEmpty(); + } + + @Test + public void search_issues_by_severities() { + assertThat(search(IssueQuery.create().severities("BLOCKER")).list()).hasSize(8); + assertThat(search(IssueQuery.create().severities("CRITICAL")).list()).hasSize(8); + assertThat(search(IssueQuery.create().severities("MAJOR")).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + assertThat(search(IssueQuery.create().severities("MINOR")).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + assertThat(search(IssueQuery.create().severities("INFO")).list()).hasSize(4); + } + + @Test + public void search_issues_by_statuses() { + assertThat(search(IssueQuery.create().statuses("OPEN")).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + assertThat(search(IssueQuery.create().statuses("RESOLVED")).list()).hasSize(1); + assertThat(search(IssueQuery.create().statuses("CLOSED")).list()).isEmpty(); + } + + @Test + public void search_issues_by_resolutions() { + assertThat(search(IssueQuery.create().resolutions("FIXED")).list()).hasSize(1); + assertThat(search(IssueQuery.create().resolutions("FALSE-POSITIVE")).list()).isEmpty(); + assertThat(search(IssueQuery.create().resolved(true)).list()).hasSize(1); + assertThat(search(IssueQuery.create().resolved(false)).paging().total()).isEqualTo(TOTAL_NB_ISSUES - 1); + } + + @Test + public void search_issues_by_assignees() { + assertThat(search(IssueQuery.create().assignees("admin")).list()).hasSize(1); + assertThat(search(IssueQuery.create().assignees("unknown")).list()).isEmpty(); + assertThat(search(IssueQuery.create().assigned(true)).list()).hasSize(1); + assertThat(search(IssueQuery.create().assigned(false)).paging().total()).isEqualTo(TOTAL_NB_ISSUES - 1); + } + + @Test + public void search_issues_by_rules() { + assertThat(search(IssueQuery.create().rules("xoo:OneIssuePerLine")).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + assertThat(search(IssueQuery.create().rules("xoo:OneIssuePerFile")).list()).hasSize(8); + + try { + search(IssueQuery.create().rules("unknown")); + Assert.fail(); + } catch (org.sonar.wsclient.base.HttpException e) { + assertThat(e.status()).isEqualTo(400); + } + } + + /** + * SONAR-2981 + */ + @Test + public void search_issues_by_dates() { + // issues have been created today + Date today = toDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date())); + Date past = toDate("2013-01-01"); + Date future = toDate("2020-12-31"); + + // createdAfter in the future => bad request + try { + search(IssueQuery.create().createdAfter(future)).list(); + Fail.fail("Expecting 400 from issues search WS"); + } catch (HttpException exception) { + assertThat(exception.getMessage()).contains("Start bound cannot be in the future"); + } + + // after date + assertThat(search(IssueQuery.create().createdAfter(today)).list().size()).isGreaterThan(0); + assertThat(search(IssueQuery.create().createdAfter(past)).list().size()).isGreaterThan(0); + + // before + assertThat(search(IssueQuery.create().createdBefore(future)).list().size()).isGreaterThan(0); + assertThat(search(IssueQuery.create().createdBefore(past)).list()).isEmpty(); + + // before and after + assertThat(search(IssueQuery.create().createdBefore(future).createdAfter(past)).list().size()).isGreaterThan(0); + + // createdAfter > createdBefore => bad request + try { + search(IssueQuery.create().createdBefore(past).createdAfter(today)).list(); + Fail.fail("Expecting 400 from issues search WS"); + } catch (HttpException exception) { + assertThat(exception.getMessage()).contains("Start bound cannot be larger or equal to end bound"); + } + + } + + /** + * SONAR-5132 + */ + @Test + public void search_issues_by_languages() { + assertThat(search(IssueQuery.create().languages("xoo")).list()).hasSize(DEFAULT_PAGINATED_RESULTS); + assertThat(search(IssueQuery.create().languages("foo")).list()).isEmpty(); + } + + @Test + public void paginate_results() { + Issues issues = search(IssueQuery.create().pageSize(20).pageIndex(2)); + + assertThat(issues.list()).hasSize(20); + Paging paging = issues.paging(); + assertThat(paging.pageIndex()).isEqualTo(2); + assertThat(paging.pageSize()).isEqualTo(20); + assertThat(paging.total()).isEqualTo(TOTAL_NB_ISSUES); + + // SONAR-3257 + // return max page size results when using negative page size value + assertThat(search(IssueQuery.create().pageSize(0)).list()).hasSize(TOTAL_NB_ISSUES); + assertThat(search(IssueQuery.create().pageSize(-1)).list()).hasSize(TOTAL_NB_ISSUES); + } + + @Test + public void sort_results() { + List<Issue> issues = search(IssueQuery.create().sort("SEVERITY").asc(false)).list(); + assertThat(issues.get(0).severity()).isEqualTo("BLOCKER"); + assertThat(issues.get(8).severity()).isEqualTo("CRITICAL"); + assertThat(issues.get(17).severity()).isEqualTo("MAJOR"); + } + + /** + * SONAR-4563 + */ + @Test + public void search_by_exact_creation_date() { + final Issue issue = search(IssueQuery.create()).list().get(0); + assertThat(issue.creationDate()).isNotNull(); + + // search the issue key with the same date + assertThat(search(IssueQuery.create().issues().issues(issue.key()).createdAt(issue.creationDate())).list()).hasSize(1); + + // search issue key with 1 second more and less should return nothing + assertThat(search(IssueQuery.create().issues().issues(issue.key()).createdAt(DateUtils.addSeconds(issue.creationDate(), 1))).size()).isEqualTo(0); + assertThat(search(IssueQuery.create().issues().issues(issue.key()).createdAt(DateUtils.addSeconds(issue.creationDate(), -1))).size()).isEqualTo(0); + + // search with future and past dates that do not match any issues + assertThat(search(IssueQuery.create().createdAt(toDate("2020-01-01"))).size()).isEqualTo(0); + assertThat(search(IssueQuery.create().createdAt(toDate("2010-01-01"))).size()).isEqualTo(0); + } + + @Test + public void return_issue_type() throws Exception { + List<org.sonarqube.ws.Issues.Issue> issues = searchByRuleKey("xoo:OneBugIssuePerLine"); + assertThat(issues).isNotEmpty(); + org.sonarqube.ws.Issues.Issue issue = issues.get(0); + assertThat(issue.getType()).isEqualTo(Common.RuleType.BUG); + + issues = searchByRuleKey("xoo:OneVulnerabilityIssuePerModule"); + assertThat(issues).isNotEmpty(); + issue = issues.get(0); + assertThat(issue.getType()).isEqualTo(Common.RuleType.VULNERABILITY); + + issues = searchByRuleKey("xoo:OneIssuePerLine"); + assertThat(issues).isNotEmpty(); + issue = issues.get(0); + assertThat(issue.getType()).isEqualTo(Common.RuleType.CODE_SMELL); + } + + @Test + public void search_issues_by_types() throws IOException { + assertThat(searchIssues(new SearchWsRequest().setTypes(singletonList("CODE_SMELL"))).getPaging().getTotal()).isEqualTo(142); + assertThat(searchIssues(new SearchWsRequest().setTypes(singletonList("BUG"))).getPaging().getTotal()).isEqualTo(122); + assertThat(searchIssues(new SearchWsRequest().setTypes(singletonList("VULNERABILITY"))).getPaging().getTotal()).isEqualTo(8); + } + + private List<org.sonarqube.ws.Issues.Issue> searchByRuleKey(String... ruleKey) throws IOException { + return searchIssues(new SearchWsRequest().setRules(asList(ruleKey))).getIssuesList(); + } + + private SearchWsResponse searchIssues(SearchWsRequest request) throws IOException { + return newAdminWsClient(ORCHESTRATOR).issues().search(request); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java new file mode 100644 index 00000000000..aa1f987c565 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueTagsTest.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category6Suite; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.project.CreateRequest; +import util.ItUtils; + +import static java.util.Arrays.asList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newProjectKey; +import static util.ItUtils.projectDir; +import static util.ItUtils.restoreProfile; + +/** + * Tests WS api/issues/tags + */ +public class IssueTagsTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + private Organization organization; + + @Before + public void setUp() { + organization = tester.organizations().generate(); + } + + @Test + public void getTags() { + restoreProfile(orchestrator, IssueTagsTest.class.getResource("/issue/one-issue-per-line-profile.xml"), organization.getKey()); + String projectKey = newProjectKey(); + tester.wsClient().projects().create( + CreateRequest.builder() + .setKey(projectKey) + .setOrganization(organization.getKey()) + .setName(randomAlphabetic(10)) + .setVisibility("private") + .build()); + analyzeProject(projectKey); + + String issue = tester.wsClient().issues().search(new SearchWsRequest()).getIssues(0).getKey(); + tester.wsClient().issues().setTags(issue, "bla", "blubb"); + + String[] publicTags = {"bad-practice", "convention", "pitfall"}; + String[] privateTags = {"bad-practice", "bla", "blubb", "convention", "pitfall"}; + String defaultOrganization = null; + + // anonymous must not see custom tags of private project + { + String anonymous = null; + assertTags(anonymous, organization.getKey(), publicTags); + assertTags(anonymous, defaultOrganization, publicTags); + } + + // stranger must not see custom tags of private project + { + User stranger = tester.users().generate(); + assertTags(stranger.getLogin(), organization.getKey(), publicTags); + assertTags(stranger.getLogin(), defaultOrganization, publicTags); + } + + // member with user permission must be able to see custom tags of private project, if he provides the organization parameter + { + User member = tester.users().generate(); + addMemberToOrganization(member); + grantUserPermission(projectKey, member); + assertTags(member.getLogin(), organization.getKey(), privateTags); + assertTags(member.getLogin(), defaultOrganization, publicTags); + } + } + + private void addMemberToOrganization(User member) { + tester.organizations().service().addMember(organization.getKey(), member.getLogin()); + } + + private void grantUserPermission(String projectKey, User member) { + tester.wsClient().permissions().addUser( + new AddUserWsRequest() + .setLogin(member.getLogin()) + .setPermission("user") + .setProjectKey(projectKey)); + } + + private void assertTags(@Nullable String userLogin, @Nullable String organization, String... expectedTags) { + assertThat( + (List<String>) ItUtils.jsonToMap( + tester.as(userLogin) + .wsClient() + .issues() + .getTags(organization) + .content()) + .get("tags")).containsExactly( + expectedTags); + } + + private void analyzeProject(String projectKey) { + List<String> keyValueProperties = new ArrayList<>(asList( + "sonar.projectKey", projectKey, + "sonar.organization", organization.getKey(), + "sonar.profile", "one-issue-per-line-profile", + "sonar.login", "admin", "sonar.password", "admin", + "sonar.scm.disabled", "false")); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"), keyValueProperties.toArray(new String[0]))); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueTrackingTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueTrackingTest.java new file mode 100644 index 00000000000..27a9ed0e80f --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueTrackingTest.java @@ -0,0 +1,220 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.Issues.SearchWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.toDate; + +public class IssueTrackingTest extends AbstractIssueTest { + + private static final String SAMPLE_PROJECT_KEY = "sample"; + + private static final String OLD_DATE = "2014-03-01"; + private static final Date NEW_DATE = new Date(); + private static final String NEW_DATE_STR = new SimpleDateFormat("yyyy-MM-dd").format(NEW_DATE); + + private static WsClient adminClient; + + @Before + public void prepareData() { + ORCHESTRATOR.resetData(); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/issue-on-tag-foobar.xml")); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueTrackingTest/one-issue-per-module-profile.xml")); + ORCHESTRATOR.getServer().provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_KEY); + adminClient = newAdminWsClient(ORCHESTRATOR); + } + + @Test + public void close_issues_on_removed_components() throws Exception { + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "issue-on-tag-foobar"); + + // version 1 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v1", + "sonar.projectDate", OLD_DATE); + + List<Issue> issues = searchUnresolvedIssuesByComponent("sample:src/main/xoo/sample/Sample.xoo"); + assertThat(issues).hasSize(1); + + // version 2 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v1", + "sonar.projectDate", NEW_DATE_STR, + "sonar.exclusions", "**/*.xoo"); + + issues = searchIssues(new SearchWsRequest().setProjectKeys(singletonList("sample"))).getIssuesList(); + assertThat(issues).hasSize(1); + assertThat(issues.get(0).getStatus()).isEqualTo("CLOSED"); + assertThat(issues.get(0).getResolution()).isEqualTo("FIXED"); + } + + /** + * SONAR-3072 + */ + @Test + public void track_issues_based_on_blocks_recognition() throws Exception { + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "issue-on-tag-foobar"); + + // version 1 + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "issue-on-tag-foobar"); + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v1", + "sonar.projectDate", OLD_DATE); + + List<Issue> issues = searchUnresolvedIssuesByComponent("sample:src/main/xoo/sample/Sample.xoo"); + assertThat(issues).hasSize(1); + Date issueDate = toDate(issues.iterator().next().getCreationDate()); + + // version 2 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v2", + "sonar.projectDate", NEW_DATE_STR); + + issues = searchUnresolvedIssuesByComponent("sample:src/main/xoo/sample/Sample.xoo"); + assertThat(issues).hasSize(3); + + // issue created during the first scan and moved during the second scan + assertThat(toDate(getIssueOnLine(6, "xoo:HasTag", issues).getCreationDate())).isEqualTo(issueDate); + + // issues created during the second scan + assertThat(toDate(getIssueOnLine(10, "xoo:HasTag", issues).getCreationDate())).isAfter(issueDate); + assertThat(toDate(getIssueOnLine(14, "xoo:HasTag", issues).getCreationDate())).isAfter(issueDate); + } + + /** + * SONAR-4310 + */ + @Test + public void track_existing_unchanged_issues_on_module() throws Exception { + // The custom rule on module is enabled + + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "one-issue-per-module"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + // Only one issue is created + assertThat(searchIssues(new SearchWsRequest()).getIssuesList()).hasSize(1); + Issue issue = getRandomIssue(); + + // Re analysis of the same project + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample"); + + // No new issue should be created + assertThat(searchIssues(new SearchWsRequest()).getIssuesList()).hasSize(1); + + // The issue on module should stay open and be the same from the first analysis + Issue reloadIssue = getIssueByKey(issue.getKey()); + assertThat(reloadIssue.getCreationDate()).isEqualTo(issue.getCreationDate()); + assertThat(reloadIssue.getStatus()).isEqualTo("OPEN"); + assertThat(reloadIssue.hasResolution()).isFalse(); + } + + /** + * SONAR-4310 + */ + @Test + public void track_existing_unchanged_issues_on_multi_modules() throws Exception { + // The custom rule on module is enabled + ORCHESTRATOR.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "com.sonarsource.it.samples:multi-modules-sample"); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "one-issue-per-module"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample"); + + // One issue by module are created + List<Issue> issues = searchIssues(new SearchWsRequest()).getIssuesList(); + assertThat(issues).hasSize(4); + + // Re analysis of the same project + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample"); + + // No new issue should be created + assertThat(searchIssues(new SearchWsRequest()).getIssuesList()).hasSize(issues.size()); + + // Issues on modules should stay open and be the same from the first analysis + for (Issue issue : issues) { + Issue reloadIssue = getIssueByKey(issue.getKey()); + assertThat(reloadIssue.getStatus()).isEqualTo("OPEN"); + assertThat(reloadIssue.hasResolution()).isFalse(); + assertThat(reloadIssue.getCreationDate()).isEqualTo(issue.getCreationDate()); + assertThat(reloadIssue.getUpdateDate()).isEqualTo(issue.getUpdateDate()); + } + } + + @Test + public void track_file_moves_based_on_identical_content() { + ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, "xoo", "issue-on-tag-foobar"); + + // version 1 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v1", + "sonar.projectDate", OLD_DATE); + + List<Issue> issues = searchUnresolvedIssuesByComponent("sample:src/main/xoo/sample/Sample.xoo"); + assertThat(issues).hasSize(1); + Issue issueOnSample = issues.iterator().next(); + + // version 2 + runProjectAnalysis(ORCHESTRATOR, "issue/xoo-tracking-v3", + "sonar.projectDate", NEW_DATE_STR); + + assertThat(searchUnresolvedIssuesByComponent("sample:src/main/xoo/sample/Sample.xoo")).isEmpty(); + + issues = searchUnresolvedIssuesByComponent("sample:src/main/xoo/sample/Sample2.xoo"); + assertThat(issues).hasSize(1); + Issue issueOnSample2 = issues.get(0); + assertThat(issueOnSample2.getKey()).isEqualTo(issueOnSample.getKey()); + assertThat(issueOnSample2.getCreationDate()).isEqualTo(issueOnSample.getCreationDate()); + assertThat(issueOnSample2.getUpdateDate()).isNotEqualTo(issueOnSample.getUpdateDate()); + assertThat(issueOnSample2.getStatus()).isEqualTo("OPEN"); + } + + private Issue getIssueOnLine(int line, String rule, List<Issue> issues) { + return issues.stream() + .filter(issue -> issue.getRule().equals(rule)) + .filter(issue -> issue.getLine() == line) + .findFirst().orElseThrow(IllegalArgumentException::new); + } + + private List<Issue> searchUnresolvedIssuesByComponent(String componentKey) { + return searchIssues(new SearchWsRequest().setComponentKeys(singletonList(componentKey)).setResolved(false)).getIssuesList(); + } + + private static Issue getRandomIssue() { + return searchIssues(new SearchWsRequest()).getIssues(0); + } + + private static Issue getIssueByKey(String issueKey) { + SearchWsResponse search = searchIssues(new SearchWsRequest().setIssues(singletonList(issueKey))); + assertThat(search.getTotal()).isEqualTo(1); + return search.getIssues(0); + } + + private static SearchWsResponse searchIssues(SearchWsRequest request) { + return adminClient.issues().search(request); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueWorkflowTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueWorkflowTest.java new file mode 100644 index 00000000000..bfcd4afca84 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueWorkflowTest.java @@ -0,0 +1,314 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.client.issue.DoTransitionRequest; +import org.sonarqube.ws.client.issue.IssuesService; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ProjectAnalysis; +import util.ProjectAnalysisRule; +import util.issue.IssueRule; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.toDatetime; + +public class IssueWorkflowTest extends AbstractIssueTest { + + @Rule + public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR); + + @ClassRule + public static final IssueRule issueRule = IssueRule.from(ORCHESTRATOR); + + private ProjectAnalysis analysisWithIssues; + private ProjectAnalysis analysisWithoutIssues; + private IssuesService issuesService; + + private Issue issue; + + @Before + public void before() { + issuesService = newAdminWsClient(ORCHESTRATOR).issues(); + String oneIssuePerFileProfileKey = projectAnalysisRule.registerProfile("/issue/IssueWorkflowTest/xoo-one-issue-per-line-profile.xml"); + String analyzedProjectKey = projectAnalysisRule.registerProject("issue/workflow"); + analysisWithIssues = projectAnalysisRule.newProjectAnalysis(analyzedProjectKey).withQualityProfile(oneIssuePerFileProfileKey); + analysisWithoutIssues = analysisWithIssues.withXooEmptyProfile(); + analysisWithIssues.run(); + + issue = issueRule.getRandomIssue(); + } + + /** + * Issue on a disabled rule (uninstalled plugin or rule deactivated from quality profile) must + * be CLOSED with resolution REMOVED + */ + @Test + public void issue_is_closed_as_removed_when_rule_is_disabled() throws Exception { + SearchWsRequest ruleSearchRequest = new SearchWsRequest().setRules(singletonList("xoo:OneIssuePerLine")); + List<Issue> issues = issueRule.search(ruleSearchRequest).getIssuesList(); + assertThat(issues).isNotEmpty(); + + // re-analyze with profile "empty". The rule is disabled so the issues must be closed + analysisWithoutIssues.run(); + issues = issueRule.search(ruleSearchRequest).getIssuesList(); + assertThat(issues).isNotEmpty(); + for (Issue issue : issues) { + assertThat(issue.getStatus()).isEqualTo("CLOSED"); + assertThat(issue.getResolution()).isEqualTo("REMOVED"); + } + } + + /** + * SONAR-4329 + */ + @Test + public void user_should_confirm_issue() { + // mark as confirmed + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "confirm")); + + Issue confirmed = issueRule.getByKey(issue.getKey()); + assertThat(confirmed.getStatus()).isEqualTo("CONFIRMED"); + assertThat(confirmed.hasResolution()).isFalse(); + assertThat(confirmed.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // user unconfirm the issue + assertThat(transitions(confirmed.getKey())).contains("unconfirm"); + issuesService.doTransition(new DoTransitionRequest(confirmed.getKey(), "unconfirm")); + + Issue unconfirmed = issueRule.getByKey(issue.getKey()); + assertThat(unconfirmed.getStatus()).isEqualTo("REOPENED"); + assertThat(unconfirmed.hasResolution()).isFalse(); + assertThat(unconfirmed.getCreationDate()).isEqualTo(confirmed.getCreationDate()); + } + + /** + * SONAR-4329 + */ + @Test + public void user_should_mark_as_false_positive_confirmed_issue() { + // mark as confirmed + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "confirm")); + + Issue confirmed = issueRule.getByKey(issue.getKey()); + assertThat(confirmed.getStatus()).isEqualTo("CONFIRMED"); + assertThat(confirmed.hasResolution()).isFalse(); + assertThat(confirmed.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // user mark the issue as false-positive + assertThat(transitions(confirmed.getKey())).contains("falsepositive"); + issuesService.doTransition(new DoTransitionRequest(confirmed.getKey(), "falsepositive")); + + Issue falsePositive = issueRule.getByKey(issue.getKey()); + assertThat(falsePositive.getStatus()).isEqualTo("RESOLVED"); + assertThat(falsePositive.getResolution()).isEqualTo("FALSE-POSITIVE"); + assertThat(falsePositive.getCreationDate()).isEqualTo(confirmed.getCreationDate()); + } + + /** + * SONAR-4329 + */ + @Test + public void scan_should_close_no_more_existing_confirmed() { + // mark as confirmed + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "confirm")); + Issue falsePositive = issueRule.getByKey(issue.getKey()); + assertThat(falsePositive.getStatus()).isEqualTo("CONFIRMED"); + assertThat(falsePositive.hasResolution()).isFalse(); + assertThat(falsePositive.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // scan without any rules -> confirmed is closed + analysisWithoutIssues.run(); + Issue closed = issueRule.getByKey(issue.getKey()); + assertThat(closed.getStatus()).isEqualTo("CLOSED"); + assertThat(closed.getResolution()).isEqualTo("REMOVED"); + assertThat(closed.getCreationDate()).isEqualTo(issue.getCreationDate()); + } + + /** + * SONAR-4288 + */ + @Test + public void scan_should_reopen_unresolved_issue_but_marked_as_resolved() { + // mark as resolved + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "resolve")); + Issue resolvedIssue = issueRule.getByKey(issue.getKey()); + assertThat(resolvedIssue.getStatus()).isEqualTo("RESOLVED"); + assertThat(resolvedIssue.getResolution()).isEqualTo("FIXED"); + assertThat(resolvedIssue.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // re-execute scan, with the same Q profile -> the issue has not been fixed + analysisWithIssues.run(); + + // reload issue + Issue reopenedIssue = issueRule.getByKey(issue.getKey()); + + // the issue has been reopened + assertThat(reopenedIssue.getStatus()).isEqualTo("REOPENED"); + assertThat(reopenedIssue.hasResolution()).isFalse(); + assertThat(reopenedIssue.getCreationDate()).isEqualTo(issue.getCreationDate()); + assertThat(toDatetime(reopenedIssue.getUpdateDate())).isAfter(toDatetime(issue.getUpdateDate())); + } + + /** + * SONAR-4288 + */ + @Test + public void scan_should_close_resolved_issue() { + // mark as resolved + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "resolve")); + Issue resolvedIssue = issueRule.getByKey(issue.getKey()); + assertThat(resolvedIssue.getStatus()).isEqualTo("RESOLVED"); + assertThat(resolvedIssue.getResolution()).isEqualTo("FIXED"); + assertThat(resolvedIssue.getCreationDate()).isEqualTo(issue.getCreationDate()); + assertThat(resolvedIssue.hasCloseDate()).isFalse(); + + // re-execute scan without rules -> the issue is removed with resolution "REMOVED" + analysisWithoutIssues.run(); + + // reload issue + Issue closedIssue = issueRule.getByKey(issue.getKey()); + assertThat(closedIssue.getStatus()).isEqualTo("CLOSED"); + assertThat(closedIssue.getResolution()).isEqualTo("REMOVED"); + assertThat(closedIssue.getCreationDate()).isEqualTo(issue.getCreationDate()); + assertThat(toDatetime(closedIssue.getUpdateDate())).isAfter(toDatetime(resolvedIssue.getUpdateDate())); + assertThat(closedIssue.hasCloseDate()).isTrue(); + assertThat(toDatetime(closedIssue.getCloseDate())).isAfter(toDatetime(closedIssue.getCreationDate())); + } + + /** + * SONAR-4288 + */ + @Test + public void user_should_reopen_issue_marked_as_resolved() { + // user marks issue as resolved + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "resolve")); + Issue resolved = issueRule.getByKey(issue.getKey()); + assertThat(resolved.getStatus()).isEqualTo("RESOLVED"); + assertThat(resolved.getResolution()).isEqualTo("FIXED"); + assertThat(resolved.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // user reopens the issue + assertThat(transitions(resolved.getKey())).contains("reopen"); + adminIssueClient().doTransition(resolved.getKey(), "reopen"); + + Issue reopened = issueRule.getByKey(resolved.getKey()); + assertThat(reopened.getStatus()).isEqualTo("REOPENED"); + assertThat(reopened.hasResolution()).isFalse(); + assertThat(reopened.getCreationDate()).isEqualTo(resolved.getCreationDate()); + } + + /** + * SONAR-4286 + */ + @Test + public void scan_should_not_reopen_or_close_false_positives() { + // user marks issue as false-positive + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "falsepositive")); + + Issue falsePositive = issueRule.getByKey(issue.getKey()); + assertThat(falsePositive.getStatus()).isEqualTo("RESOLVED"); + assertThat(falsePositive.getResolution()).isEqualTo("FALSE-POSITIVE"); + assertThat(falsePositive.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // re-execute the same scan + analysisWithIssues.run(); + + // refresh + Issue reloaded = issueRule.getByKey(falsePositive.getKey()); + assertThat(reloaded.getStatus()).isEqualTo("RESOLVED"); + assertThat(reloaded.getResolution()).isEqualTo("FALSE-POSITIVE"); + assertThat(reloaded.getCreationDate()).isEqualTo(issue.getCreationDate()); + assertThat(toDatetime(reloaded.getUpdateDate())).isEqualTo(toDatetime(falsePositive.getUpdateDate())); + } + + /** + * SONAR-4286 + */ + @Test + public void scan_should_close_no_more_existing_false_positive() { + // user marks as false-positive + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "falsepositive")); + Issue falsePositive = issueRule.getByKey(issue.getKey()); + assertThat(falsePositive.getStatus()).isEqualTo("RESOLVED"); + assertThat(falsePositive.getResolution()).isEqualTo("FALSE-POSITIVE"); + assertThat(falsePositive.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // scan without any rules -> false-positive is closed + analysisWithoutIssues.run(); + Issue closed = issueRule.getByKey(issue.getKey()); + assertThat(closed.getStatus()).isEqualTo("CLOSED"); + assertThat(closed.getResolution()).isEqualTo("REMOVED"); + assertThat(closed.getCreationDate()).isEqualTo(issue.getCreationDate()); + } + + /** + * SONAR-4286 + */ + @Test + public void user_should_reopen_false_positive() { + // user marks as false-positive + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "falsepositive")); + + Issue falsePositive = issueRule.getByKey(issue.getKey()); + assertThat(falsePositive.getStatus()).isEqualTo("RESOLVED"); + assertThat(falsePositive.getResolution()).isEqualTo("FALSE-POSITIVE"); + assertThat(falsePositive.getCreationDate()).isEqualTo(issue.getCreationDate()); + + // user reopens the issue + assertThat(transitions(falsePositive.getKey())).contains("reopen"); + adminIssueClient().doTransition(falsePositive.getKey(), "reopen"); + + Issue reopened = issueRule.getByKey(issue.getKey()); + assertThat(reopened.getStatus()).isEqualTo("REOPENED"); + assertThat(reopened.hasResolution()).isFalse(); + assertThat(reopened.getCreationDate()).isEqualTo(falsePositive.getCreationDate()); + } + + @Test + public void user_should_not_reopen_closed_issue() { + issuesService.doTransition(new DoTransitionRequest(issue.getKey(), "resolve")); + + // re-execute scan without rules -> the issue is closed + analysisWithoutIssues.run(); + + // user try to reopen the issue + assertThat(transitions(issue.getKey())).isEmpty(); + } + + private List<String> transitions(String issueKey) { + Issues.SearchWsResponse response = searchIssues(new SearchWsRequest().setIssues(singletonList(issueKey)).setAdditionalFields(singletonList("transitions"))); + assertThat(response.getTotal()).isEqualTo(1); + return response.getIssues(0).getTransitions().getTransitionsList(); + } + + private Issues.SearchWsResponse searchIssues(SearchWsRequest request) { + return newAdminWsClient(ORCHESTRATOR).issues().search(request); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssuesPageTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssuesPageTest.java new file mode 100644 index 00000000000..cc3829b78e2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/IssuesPageTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category2Suite; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.issues.Issue; +import org.sonarqube.pageobjects.issues.IssuesPage; +import util.ItUtils; +import util.user.UserRule; + +import static util.ItUtils.runProjectAnalysis; + +public class IssuesPageTest { + private static final String PROJECT_KEY = "sample"; + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category2Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(ORCHESTRATOR); + + public Navigation nav = Navigation.create(ORCHESTRATOR); + + private String adminUser; + + @BeforeClass + public static void prepareData() { + ORCHESTRATOR.resetData(); + + ItUtils.restoreProfile(ORCHESTRATOR, IssuesPageTest.class.getResource("/issue/with-many-rules.xml")); + + ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY, PROJECT_KEY); + ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "with-many-rules"); + runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample"); + } + + @Before + public void before() { + adminUser = userRule.createAdminUser(); + } + + @Test + public void should_display_actions() { + IssuesPage page = nav.logIn().submitCredentials(adminUser).openIssues(); + Issue issue = page.getFirstIssue(); + issue.shouldAllowAssign().shouldAllowChangeType(); + } + + @Test + public void should_not_display_actions() { + Navigation nav = Navigation.create(ORCHESTRATOR); + IssuesPage page = nav.openIssues(); + Issue issue = page.getFirstIssue(); + issue.shouldNotAllowAssign().shouldNotAllowChangeType(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/NewIssuesMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/issue/NewIssuesMeasureTest.java new file mode 100644 index 00000000000..40a5de1ab18 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/NewIssuesMeasureTest.java @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issue; + +import com.sonar.orchestrator.build.SonarScanner; +import java.util.Map; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.WsMeasures.Measure; +import util.ItUtils; + +import static java.lang.Integer.parseInt; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getLeakPeriodValue; +import static util.ItUtils.getMeasuresWithVariationsByMetricKey; +import static util.ItUtils.projectDir; +import static util.ItUtils.setServerProperty; + +/** + * SONAR-4564 + */ +public class NewIssuesMeasureTest extends AbstractIssueTest { + + @AfterClass + public static void resetPeriod() { + ItUtils.resetPeriod(ORCHESTRATOR); + } + + @Before + public void cleanUpAnalysisData() { + ORCHESTRATOR.resetData(); + } + + @Test + public void new_issues_measures() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.leak.period", "previous_analysis"); + ORCHESTRATOR.getServer().provisionProject("sample", "Sample"); + + // Execute an analysis in the past with no issue to have a past snapshot + ORCHESTRATOR.getServer().associateProjectToQualityProfile("sample", "xoo", "empty"); + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperty("sonar.projectDate", "2013-01-01")); + + // Execute a analysis now with some issues + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-line-profile.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line-profile"); + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + assertThat(ORCHESTRATOR.getServer().wsClient().issueClient().find(IssueQuery.create()).list()).isNotEmpty(); + assertThat(getLeakPeriodValue(ORCHESTRATOR, "sample:src/main/xoo/sample/Sample.xoo", "new_violations")).isEqualTo(17); + + // second analysis, with exactly the same profile -> no new issues + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + assertThat(ORCHESTRATOR.getServer().wsClient().issueClient().find(IssueQuery.create()).list()).isNotEmpty(); + assertThat(getLeakPeriodValue(ORCHESTRATOR, "sample:src/main/xoo/sample/Sample.xoo", "new_violations")).isZero(); + } + + @Test + public void new_issues_measures_should_be_zero_on_project_when_no_new_issues_since_x_days() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.leak.period", "30"); + ORCHESTRATOR.getServer().provisionProject("sample", "Sample"); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-line-profile.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line-profile"); + + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")) + // Analyse a project in the past, with a date older than 30 last days (second period) + .setProperty("sonar.projectDate", "2013-01-01")); + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + // new issues measures should be to 0 on project on 2 periods as new issues has been created + assertThat(getLeakPeriodValue(ORCHESTRATOR, "sample", "new_violations")).isZero(); + } + + /** + * SONAR-3647 + */ + @Test + public void new_issues_measures_consistent_with_variations() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.leak.period", "previous_analysis"); + ORCHESTRATOR.getServer().provisionProject("sample", "Sample"); + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-line-profile.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line-profile"); + + // Execute an analysis in the past to have a past snapshot + // version 1 + ORCHESTRATOR.executeBuilds(SonarScanner.create(projectDir("shared/xoo-history-v1"))); + + // version 2 with 2 new violations and 3 more ncloc + ORCHESTRATOR.executeBuilds(SonarScanner.create(projectDir("shared/xoo-history-v2"))); + + assertThat(ORCHESTRATOR.getServer().wsClient().issueClient().find(IssueQuery.create()).list()).isNotEmpty(); + + Map<String, Measure> measures = getMeasuresWithVariationsByMetricKey(ORCHESTRATOR, "sample", "new_violations", "violations", "ncloc"); + assertThat(measures.get("new_violations").getPeriods().getPeriodsValueList()).extracting(WsMeasures.PeriodValue::getValue).containsOnly("17"); + + Measure violations = measures.get("violations"); + assertThat(parseInt(violations.getValue())).isEqualTo(43); + assertThat(violations.getPeriods().getPeriodsValueList()).extracting(periodValue -> parseInt(periodValue.getValue())).containsOnly(17); + + Measure ncloc = measures.get("ncloc"); + assertThat(parseInt(ncloc.getValue())).isEqualTo(40); + assertThat(ncloc.getPeriods().getPeriodsValueList()).extracting(periodValue -> parseInt(periodValue.getValue())).containsOnly(16); + } + + @Test + public void new_issues_measures_should_be_correctly_calculated_when_adding_a_new_module() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.leak.period", "previous_analysis"); + ORCHESTRATOR.getServer().provisionProject("com.sonarsource.it.samples:multi-modules-sample", "com.sonarsource.it.samples:multi-modules-sample"); + + // First analysis without module b + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/NewIssuesMeasureTest/profile1.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "profile1"); + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")) + .setProperties("sonar.modules", "module_a")); + + // Second analysis with module b and with a new rule activated to have new issues on module a since last analysis + ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/NewIssuesMeasureTest/profile2.xml")); + ORCHESTRATOR.getServer().associateProjectToQualityProfile("com.sonarsource.it.samples:multi-modules-sample", "xoo", "profile2"); + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getLeakPeriodValue(ORCHESTRATOR, "com.sonarsource.it.samples:multi-modules-sample", "new_violations")).isEqualTo(65); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssueAssignTest.java b/tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssueAssignTest.java new file mode 100644 index 00000000000..436e466454d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issue/OrganizationIssueAssignTest.java @@ -0,0 +1,219 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.issue; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.issue.AssignRequest; +import org.sonarqube.ws.client.issue.BulkChangeRequest; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import org.sonarqube.ws.client.project.CreateRequest; +import org.sonarqube.ws.client.qualityprofile.AddProjectRequest; +import org.sonarqube.pageobjects.issues.IssuesPage; +import util.issue.IssueRule; + +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.expectHttpError; +import static util.ItUtils.restoreProfile; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +public class OrganizationIssueAssignTest { + + private final static String SAMPLE_PROJECT_KEY = "sample"; + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + @Rule + public Tester tester = new Tester(orchestrator); + + @Rule + public IssueRule issueRule = IssueRule.from(orchestrator); + + private Organizations.Organization org1; + private Organizations.Organization org2; + private User user; + + @Before + public void setUp() throws Exception { + org1 = tester.organizations().generate(); + org2 = tester.organizations().generate(); + user = tester.users().generate(); + restoreProfile(orchestrator, getClass().getResource("/organization/IssueAssignTest/one-issue-per-file-profile.xml"), org1.getKey()); + } + + @Test + public void auto_assign_issues_to_user_if_default_assignee_is_member_of_project_organization() { + tester.organizations().addMember(org1, user); + + provisionProject(SAMPLE_PROJECT_KEY, org1.getKey()); + setServerProperty(orchestrator, "sample", "sonar.issues.defaultAssigneeLogin", user.getLogin()); + + analyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + + assertThat(issueRule.getRandomIssue().getAssignee()).isEqualTo(user.getLogin()); + } + + @Test + public void does_not_auto_assign_issues_to_user_if_default_assignee_is_not_member_of_project_organization() { + tester.organizations().addMember(org2, user); + provisionProject(SAMPLE_PROJECT_KEY, org1.getKey()); + setServerProperty(orchestrator, "sample", "sonar.issues.defaultAssigneeLogin", user.getLogin()); + + analyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + + assertThat(issueRule.getRandomIssue().hasAssignee()).isFalse(); + } + + @Test + public void assign_issue_to_user_being_member_of_same_organization_as_project_issue_organization() { + tester.organizations().addMember(org1, user); + provisionAndAnalyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + Issue issue = issueRule.getRandomIssue(); + + assignIssueTo(issue, user); + + assertThat(issueRule.getByKey(issue.getKey()).getAssignee()).isEqualTo(user.getLogin()); + } + + @Test + public void fail_to_assign_issue_to_user_not_being_member_of_same_organization_as_project_issue_organization() { + tester.organizations().addMember(org2, user); + provisionAndAnalyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + Issue issue = issueRule.getRandomIssue(); + + expectHttpError(400, + format("User '%s' is not member of organization '%s'", user.getLogin(), org1.getKey()), + () -> assignIssueTo(issue, user)); + } + + @Test + public void bulk_assign_issues_to_user_being_only_member_of_same_organization_as_project_issue_organization() { + restoreProfile(orchestrator, getClass().getResource("/organization/IssueAssignTest/one-issue-per-file-profile.xml"), org2.getKey()); + // User is only member of org1, not of org2 + tester.organizations().addMember(org1, user); + provisionAndAnalyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + provisionAndAnalyseProject("sample2", org2.getKey()); + List<String> issues = issueRule.search(new org.sonarqube.ws.client.issue.SearchWsRequest()).getIssuesList().stream().map(Issue::getKey).collect(Collectors.toList()); + + Issues.BulkChangeWsResponse response = tester.wsClient().issues() + .bulkChange(BulkChangeRequest.builder().setIssues(issues).setAssign(user.getLogin()).build()); + + assertThat(response.getIgnored()).isGreaterThan(0); + assertThat(issueRule.search(new SearchWsRequest().setProjectKeys(singletonList("sample"))).getIssuesList()).extracting(Issue::getAssignee) + .containsOnly(user.getLogin()); + assertThat(issueRule.search(new SearchWsRequest().setProjectKeys(singletonList("sample2"))).getIssuesList()).extracting(Issue::hasAssignee) + .containsOnly(false); + } + + @Test + public void single_assign_search_show_only_members_in_global_issues() { + tester.organizations().addMember(org1, user); + User otherUser = tester.users().generate(); + provisionAndAnalyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + IssuesPage page = tester.openBrowser().logIn().submitCredentials(user.getLogin()).openIssues(); + page.getFirstIssue() + .shouldAllowAssign() + .assigneeSearchResultCount(otherUser.getLogin(), 0) + .assigneeSearchResultCount(user.getLogin(), 1); + } + + @Test + public void bulk_assign_search_only_members_of_organization_in_project_issues() { + tester.organizations().addMember(org1, user); + User otherUser = tester.users().generate(); + + provisionAndAnalyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + IssuesPage page = tester.openBrowser() + .logIn().submitCredentials(user.getLogin()) + .openComponentIssues(SAMPLE_PROJECT_KEY); + page + .bulkChangeOpen() + .bulkChangeAssigneeSearchCount(user.getLogin(), 1) + .bulkChangeAssigneeSearchCount(otherUser.getLogin(), 0); + } + + @Test + public void bulk_assign_search_all_users_in_global_issues() { + tester.organizations().addMember(org1, user); + User otherUser = tester.users().generate(); + provisionAndAnalyseProject(SAMPLE_PROJECT_KEY, org1.getKey()); + IssuesPage page = tester.openBrowser() + .logIn().submitCredentials(user.getLogin()) + .openIssues(); + page + .bulkChangeOpen() + .bulkChangeAssigneeSearchCount(user.getLogin(), 1) + .bulkChangeAssigneeSearchCount(otherUser.getLogin(), 1); + } + + private void provisionAndAnalyseProject(String projectKey, String organization) { + provisionProject(projectKey, organization); + analyseProject(projectKey, organization); + } + + private void provisionProject(String projectKey, String organization) { + tester.wsClient().projects().create( + CreateRequest.builder() + .setKey(projectKey) + .setName(projectKey) + .setOrganization(organization) + .build()); + } + + private void analyseProject(String projectKey, String organization) { + addQualityProfileToProject(organization, projectKey); + runProjectAnalysis(orchestrator, "issue/xoo-with-scm", + "sonar.projectKey", projectKey, + "sonar.organization", organization, + "sonar.login", "admin", + "sonar.password", "admin", + "sonar.scm.disabled", "false", + "sonar.scm.provider", "xoo"); + } + + private void addQualityProfileToProject(String organization, String projectKey) { + tester.wsClient().qualityProfiles().addProject( + AddProjectRequest.builder() + .setProjectKey(projectKey) + .setOrganization(organization) + .setLanguage("xoo") + .setProfileName("one-issue-per-file-profile") + .build()); + } + + private Issues.Operation assignIssueTo(Issue issue, User u) { + return tester.wsClient().issues().assign(new AssignRequest(issue.getKey(), u.getLogin())); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/issueFilter/ToDoTest.java b/tests/src/test/java/org/sonarqube/tests/issueFilter/ToDoTest.java new file mode 100644 index 00000000000..9b49441ec18 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/issueFilter/ToDoTest.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.issueFilter; + +public class ToDoTest { +} diff --git a/tests/src/test/java/org/sonarqube/tests/lite/LiteSuite.java b/tests/src/test/java/org/sonarqube/tests/lite/LiteSuite.java new file mode 100644 index 00000000000..b362cd9dfb3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/lite/LiteSuite.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.lite; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + LiteTest.class +}) +public class LiteSuite { + +} diff --git a/tests/src/test/java/org/sonarqube/tests/lite/LiteTest.java b/tests/src/test/java/org/sonarqube/tests/lite/LiteTest.java new file mode 100644 index 00000000000..e60adfdbc2b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/lite/LiteTest.java @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.lite; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.client.component.TreeWsRequest; +import org.sonarqube.ws.client.issue.IssuesService; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import org.sonarqube.ws.client.measure.ComponentTreeWsRequest; +import org.sonarqube.ws.client.measure.ComponentWsRequest; +import org.sonarqube.ws.client.measure.MeasuresService; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.xooPlugin; + +public class LiteTest { + + private static final String PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-sample"; + + private static Orchestrator orchestrator = Orchestrator.builderEnv() + .setOrchestratorProperty("sonar.web.context", "/sonarqube") + .addPlugin(xooPlugin()) + .build(); + + private static Tester tester = new Tester(orchestrator); + + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(orchestrator) + .around(tester); + + @BeforeClass + public static void setUp() { + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample"); + } + + @Test + public void call_issues_ws() { + // all issues + IssuesService issuesService = tester.wsClient().issues(); + Issues.SearchWsResponse response = issuesService.search(new SearchWsRequest()); + assertThat(response.getIssuesCount()).isGreaterThan(0); + + // project issues + response = issuesService.search(new SearchWsRequest().setProjectKeys(singletonList(PROJECT_KEY))); + assertThat(response.getIssuesCount()).isGreaterThan(0); + } + + @Test + public void call_components_ws() { + // files in project + WsComponents.TreeWsResponse tree = tester.wsClient().components().tree(new TreeWsRequest() + .setBaseComponentKey(PROJECT_KEY) + .setQualifiers(singletonList("FIL"))); + assertThat(tree.getComponentsCount()).isEqualTo(4); + tree.getComponentsList().forEach(c -> { + assertThat(c.getQualifier()).isEqualTo("FIL"); + assertThat(c.getName()).endsWith(".xoo"); + }); + } + + @Test + public void call_measures_ws() { + // project measures + MeasuresService measuresService = tester.wsClient().measures(); + WsMeasures.ComponentWsResponse component = measuresService.component(new ComponentWsRequest() + .setComponentKey(PROJECT_KEY) + .setMetricKeys(asList("lines", "ncloc", "files"))); + assertThat(component.getComponent().getMeasuresCount()).isEqualTo(3); + + // file measures + WsMeasures.ComponentTreeWsResponse tree = measuresService.componentTree(new ComponentTreeWsRequest() + .setBaseComponentKey(PROJECT_KEY) + .setQualifiers(singletonList("FIL")) + .setMetricKeys(asList("lines", "ncloc"))); + assertThat(tree.getComponentsCount()).isEqualTo(4); + tree.getComponentsList().forEach(c -> { + assertThat(c.getMeasuresList()).extracting(m -> m.getMetric()).containsOnly("lines", "ncloc"); + }); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/DecimalScaleMetricTest.java b/tests/src/test/java/org/sonarqube/tests/measure/DecimalScaleMetricTest.java new file mode 100644 index 00000000000..448c9554402 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/DecimalScaleMetricTest.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category3Suite; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasureAsDouble; + +/** + * SONAR-6939 + */ +public class DecimalScaleMetricTest { + + /** + * Requires the plugin "batch-plugin" + */ + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @Test + public void override_decimal_scale_of_numeric_metric() { + String projectKey = "DecimalScaleMetricTest.override_decimal_scale_of_numeric_metric"; + // see DecimalScaleMetric + String metricKey = "decimal_scale"; + ItUtils.runProjectAnalysis(orchestrator, "shared/xoo-sample", + "sonar.projectKey", projectKey, + "sonar.scanner.feedDecimalScaleMetric", String.valueOf(true)); + + assertThat(getMeasureAsDouble(orchestrator, projectKey, metricKey)).isEqualTo(0.0001); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/DifferentialPeriodsTest.java b/tests/src/test/java/org/sonarqube/tests/measure/DifferentialPeriodsTest.java new file mode 100644 index 00000000000..0597e0e8e97 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/DifferentialPeriodsTest.java @@ -0,0 +1,177 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import java.util.Date; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.pageobjects.Navigation; +import util.ItUtils; +import util.user.UserRule; + +import static org.apache.commons.lang.time.DateUtils.addDays; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.formatDate; +import static util.ItUtils.getLeakPeriodValue; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.resetPeriod; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +public class DifferentialPeriodsTest { + + static final String PROJECT_KEY = "sample"; + static final String MULTI_MODULE_PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-sample"; + + static WsClient CLIENT; + + @ClassRule + public static final Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private String adminUser; + + @BeforeClass + public static void createWsClient() throws Exception { + CLIENT = newAdminWsClient(orchestrator); + } + + @Before + public void cleanUpAnalysisData() { + orchestrator.resetData(); + adminUser = userRule.createAdminUser(); + } + + @After + public void reset() throws Exception { + resetPeriod(orchestrator); + } + + /** + * SONAR-7093 + */ + @Test + public void ensure_leak_period_defined_at_project_level_is_taken_into_account() throws Exception { + orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_KEY); + + // Set a global property and a project property to ensure project property is used + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + setServerProperty(orchestrator, PROJECT_KEY, "sonar.leak.period", "30"); + + // Execute an analysis in the past to have a past snapshot without any issues + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "empty"); + runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.projectDate", formatDate(addDays(new Date(), -15))); + + // Second analysis -> issues will be created + ItUtils.restoreProfile(orchestrator, getClass().getResource("/measure/one-issue-per-line-profile.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "one-issue-per-line"); + runProjectAnalysis(orchestrator, "shared/xoo-sample"); + + // Third analysis -> There's no new issue from previous analysis + runProjectAnalysis(orchestrator, "shared/xoo-sample"); + + // Project should have 17 new issues for leak period + assertThat(getLeakPeriodValue(orchestrator, PROJECT_KEY, "violations")).isEqualTo(17); + + // Check on ui that it's possible to define leak period on project + Navigation.create(orchestrator).openHome().logIn().submitCredentials(adminUser).openSettings("sample") + .assertSettingDisplayed("sonar.leak.period"); + } + + /** + * SONAR-7237 + */ + @Test + public void ensure_differential_measures_are_computed_when_adding_new_component_after_period() throws Exception { + orchestrator.getServer().provisionProject(MULTI_MODULE_PROJECT_KEY, MULTI_MODULE_PROJECT_KEY); + setServerProperty(orchestrator, MULTI_MODULE_PROJECT_KEY, "sonar.leak.period", "30"); + + // Execute an analysis 60 days ago without module b + orchestrator.getServer().associateProjectToQualityProfile(MULTI_MODULE_PROJECT_KEY, "xoo", "empty"); + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample", + "sonar.projectDate", formatDate(addDays(new Date(), -60)), + "sonar.modules", "module_a"); + + // Second analysis, 20 days ago, issues will be created + ItUtils.restoreProfile(orchestrator, getClass().getResource("/measure/one-issue-per-line-profile.xml")); + orchestrator.getServer().associateProjectToQualityProfile(MULTI_MODULE_PROJECT_KEY, "xoo", "one-issue-per-line"); + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample", + "sonar.projectDate", formatDate(addDays(new Date(), -20)), + "sonar.modules", "module_a,module_b"); + + // Variation on module b should exist + assertThat(getLeakPeriodValue(orchestrator, MULTI_MODULE_PROJECT_KEY + ":module_b", "ncloc")).isEqualTo(24); + } + + @Test + public void compute_no_new_lines_measures_when_changes_but_no_scm() throws Exception { + orchestrator.getServer().provisionProject(MULTI_MODULE_PROJECT_KEY, MULTI_MODULE_PROJECT_KEY); + setServerProperty(orchestrator, MULTI_MODULE_PROJECT_KEY, "sonar.leak.period", "previous_analysis"); + + // Execute an analysis 60 days ago without module b + orchestrator.getServer().associateProjectToQualityProfile(MULTI_MODULE_PROJECT_KEY, "xoo", "empty"); + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample", + "sonar.projectDate", formatDate(addDays(new Date(), -60)), + "sonar.modules", "module_a"); + + // Second analysis, 20 days ago + ItUtils.restoreProfile(orchestrator, getClass().getResource("/measure/one-issue-per-line-profile.xml")); + orchestrator.getServer().associateProjectToQualityProfile(MULTI_MODULE_PROJECT_KEY, "xoo", "one-issue-per-line"); + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample", + "sonar.projectDate", formatDate(addDays(new Date(), -20)), + "sonar.modules", "module_a,module_b"); + + // No new lines measure + assertNoMeasures(MULTI_MODULE_PROJECT_KEY, "new_lines", "new_lines_to_cover"); + } + + @Test + public void compute_zero_new_lines_measures_when_no_changes_and_scm_available() throws Exception { + String projectKey = "sample-scm"; + orchestrator.getServer().provisionProject(projectKey, projectKey); + setServerProperty(orchestrator, projectKey, "sonar.leak.period", "previous_analysis"); + + // Execute an analysis 60 days ago + runProjectAnalysis(orchestrator, "scm/xoo-sample-with-scm", "sonar.projectDate", formatDate(addDays(new Date(), -60)), + "sonar.scm.provider", "xoo", "sonar.scm.disabled", "false"); + + // Second analysis, 20 days ago + runProjectAnalysis(orchestrator, "scm/xoo-sample-with-scm", "sonar.projectDate", formatDate(addDays(new Date(), -20)), + "sonar.scm.provider", "xoo", "sonar.scm.disabled", "false"); + + // New lines measures is zero + assertThat(getLeakPeriodValue(orchestrator, projectKey, "new_lines")).isEqualTo(0); + assertThat(getLeakPeriodValue(orchestrator, projectKey, "new_lines_to_cover")).isEqualTo(0); + } + + private void assertNoMeasures(String projectKey, String... metrics) { + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, projectKey, metrics)).isEmpty(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/MeasuresWsTest.java b/tests/src/test/java/org/sonarqube/tests/measure/MeasuresWsTest.java new file mode 100644 index 00000000000..53e20f39bc0 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/MeasuresWsTest.java @@ -0,0 +1,174 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.util.List; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.WsMeasures.ComponentTreeWsResponse; +import org.sonarqube.ws.WsMeasures.ComponentWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.measure.ComponentTreeWsRequest; +import org.sonarqube.ws.client.measure.ComponentWsRequest; +import util.ItUtils; + +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; +import static util.ItUtils.setServerProperty; + +public class MeasuresWsTest { + @ClassRule + public static final Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + private static final String FILE_KEY = "sample:src/main/xoo/sample/Sample.xoo"; + private static final String DIR_KEY = "sample:src/main/xoo/sample"; + WsClient wsClient; + + @BeforeClass + public static void initPeriod() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + } + + @AfterClass + public static void resetPeriod() throws Exception { + ItUtils.resetPeriod(orchestrator); + } + + @Before + public void inspectProject() { + orchestrator.resetData(); + + wsClient = ItUtils.newAdminWsClient(orchestrator); + } + + @Test + public void component_tree() { + scanXooSample(); + + ComponentTreeWsResponse response = wsClient.measures().componentTree(new ComponentTreeWsRequest() + .setBaseComponentKey("sample") + .setMetricKeys(singletonList("ncloc")) + .setAdditionalFields(newArrayList("metrics", "periods"))); + + assertThat(response).isNotNull(); + assertThat(response.getBaseComponent().getKey()).isEqualTo("sample"); + assertThat(response.getMetrics().getMetricsList()).extracting("key").containsOnly("ncloc"); + List<WsMeasures.Component> components = response.getComponentsList(); + assertThat(components).hasSize(2).extracting("key").containsOnly(DIR_KEY, FILE_KEY); + assertThat(components.get(0).getMeasuresList().get(0).getValue()).isEqualTo("13"); + } + + /** + * @see SONAR-7958 + */ + @Test + public void component_tree_supports_module_move_down() { + String projectKey = "sample"; + String newModuleKey = "sample:new_module"; + String moduleAKey = "module_a"; + String dirKey = "module_a:src/main/xoo/sample"; + String fileKey = "module_a:src/main/xoo/sample/Sample.xoo"; + + scanXooSampleModuleMoveV1(); + + verifyComponentTreeWithChildren(projectKey, moduleAKey); + verifyComponentTreeWithChildren(moduleAKey, dirKey); + verifyComponentTreeWithChildren(dirKey, fileKey); + + scanXooSampleModuleMoveV2(); + + verifyComponentTreeWithChildren(projectKey, newModuleKey); + verifyComponentTreeWithChildren(newModuleKey, moduleAKey); + verifyComponentTreeWithChildren(moduleAKey, dirKey); + verifyComponentTreeWithChildren(dirKey, fileKey); + } + + /** + * @see SONAR-7958 + */ + @Test + public void component_tree_supports_module_move_up() { + String projectKey = "sample"; + String newModuleKey = "sample:new_module"; + String moduleAKey = "module_a"; + String dirKey = "module_a:src/main/xoo/sample"; + String fileKey = "module_a:src/main/xoo/sample/Sample.xoo"; + + scanXooSampleModuleMoveV2(); + + verifyComponentTreeWithChildren(projectKey, newModuleKey); + verifyComponentTreeWithChildren(newModuleKey, moduleAKey); + verifyComponentTreeWithChildren(moduleAKey, dirKey); + verifyComponentTreeWithChildren(dirKey, fileKey); + + scanXooSampleModuleMoveV1(); + + verifyComponentTreeWithChildren(projectKey, moduleAKey); + verifyComponentTreeWithChildren(moduleAKey, dirKey); + verifyComponentTreeWithChildren(dirKey, fileKey); + } + + private void verifyComponentTreeWithChildren(String baseComponentKey, String... childKeys) { + ComponentTreeWsResponse response = wsClient.measures().componentTree(new ComponentTreeWsRequest() + .setBaseComponentKey(baseComponentKey) + .setMetricKeys(singletonList("ncloc")) + .setStrategy("children")); + + assertThat(response.getBaseComponent().getKey()).isEqualTo(baseComponentKey); + assertThat(response.getComponentsList()) + .extracting("key").containsOnly(childKeys); + } + + @Test + public void component() { + scanXooSample(); + + ComponentWsResponse response = wsClient.measures().component(new ComponentWsRequest() + .setComponentKey("sample") + .setMetricKeys(singletonList("ncloc")) + .setAdditionalFields(newArrayList("metrics", "periods"))); + + WsMeasures.Component component = response.getComponent(); + assertThat(component.getKey()).isEqualTo("sample"); + assertThat(component.getMeasuresList()).isNotEmpty(); + assertThat(response.getMetrics().getMetricsList()).extracting("key").containsOnly("ncloc"); + } + + private void scanXooSample() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + } + + private void scanXooSampleModuleMoveV1() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample-module-move-v1"))); + } + + private void scanXooSampleModuleMoveV2() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample-module-move-v2"))); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/ProjectDashboardTest.java b/tests/src/test/java/org/sonarqube/tests/measure/ProjectDashboardTest.java new file mode 100644 index 00000000000..584938877da --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/ProjectDashboardTest.java @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.openqa.selenium.Keys; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectDashboardPage; +import util.user.UserRule; + +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Condition.text; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class ProjectDashboardTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private Navigation nav = Navigation.create(orchestrator); + + private static WsClient wsClient; + private String adminUser; + + @Before + public void setUp() throws Exception { + wsClient = newAdminWsClient(orchestrator); + orchestrator.resetData(); + adminUser = userRule.createAdminUser(); + } + + @Test + public void after_first_analysis() throws Exception { + executeBuild("shared/xoo-sample", "project-for-overview", "Project For Overview"); + + runSelenese(orchestrator, "/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html"); + } + + @Test + public void display_size() { + executeBuild("shared/xoo-sample", "sample", "Sample"); + + ProjectDashboardPage page = Navigation.create(orchestrator).openProjectDashboard("sample"); + + page.getLinesOfCode().should(hasText("13")); + page.getLanguageDistribution().should(hasText("Xoo"), hasText("13")); + } + + @Test + public void display_tags_without_edit() { + executeBuild("shared/xoo-sample", "sample", "Sample"); + + // Add some tags to the project + wsClient.wsConnector().call( + new PostRequest("api/project_tags/set") + .setParam("project", "sample") + .setParam("tags", "foo,bar,baz")); + + ProjectDashboardPage page = Navigation.create(orchestrator).openProjectDashboard("sample"); + page + .shouldHaveTags("foo", "bar", "baz") + .shouldNotBeEditable(); + } + + @Test + public void display_tags_with_edit() { + executeBuild("shared/xoo-sample", "sample-with-tags", "Sample with tags"); + // Add some tags to another project to have them in the list + wsClient.wsConnector().call( + new PostRequest("api/project_tags/set") + .setParam("project", "sample-with-tags") + .setParam("tags", "foo,bar,baz")); + + executeBuild("shared/xoo-sample", "sample", "Sample"); + ProjectDashboardPage page = nav.logIn().submitCredentials(adminUser).openProjectDashboard("sample"); + page + .shouldHaveTags("No tags") + .shouldBeEditable() + .openTagEditor() + .getTagAtIdx(2).click(); + page + .shouldHaveTags("foo") + .sendKeysToTagsInput("test") + .getTagAtIdx(0).should(hasText("+ test")).click(); + page + .shouldHaveTags("foo", "test") + .getTagAtIdx(1).should(hasText("test")); + page + .sendKeysToTagsInput(Keys.ENTER) + .shouldHaveTags("test"); + } + + @Test + @Ignore("there is no more place to show the error") + public void display_a_nice_error_when_requesting_unknown_project() { + Navigation nav = Navigation.create(orchestrator); + nav.open("/dashboard/index?id=unknown"); + nav.getErrorMessage().should(text("The requested project does not exist. Either it has never been analyzed successfully or it has been deleted.")); + // TODO verify that on global homepage + } + + private void executeBuild(String projectLocation, String projectKey, String projectName) { + orchestrator.executeBuild( + SonarScanner.create(projectDir(projectLocation)) + .setProjectKey(projectKey) + .setProjectName(projectName)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/ProjectMeasuresPageTest.java b/tests/src/test/java/org/sonarqube/tests/measure/ProjectMeasuresPageTest.java new file mode 100644 index 00000000000..46ee9aafd11 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/ProjectMeasuresPageTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.pageobjects.Navigation; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class ProjectMeasuresPageTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @BeforeClass + public static void inspectProject() { + orchestrator.executeBuild( + SonarScanner + .create(projectDir("shared/xoo-sample")) + .setProperty("sonar.projectKey", "project-measures-page-test-project") + .setProperty("sonar.projectName", "ProjectMeasuresPageTest Project")); + + // one more time + orchestrator.executeBuild( + SonarScanner + .create(projectDir("shared/xoo-sample")) + .setProperty("sonar.projectKey", "project-measures-page-test-project") + .setProperty("sonar.projectName", "ProjectMeasuresPageTest Project")); + } + + @Test + public void should_display_measures_page() { + runSelenese(orchestrator, "/measure/ProjectMeasuresPageTest/should_display_measures_page.html"); + } + + @Test + public void should_drilldown_on_list_view() { + runSelenese(orchestrator, "/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html"); + } + + @Test + public void should_drilldown_on_tree_view() { + runSelenese(orchestrator, "/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html"); + } + + @Test + public void should_show_history() { + Navigation nav = Navigation.create(orchestrator); + nav.open("/component_measures/metric/reliability_rating/history?id=project-measures-page-test-project"); + $(".line-chart").shouldBe(visible); + $$(".line-chart-tick-x").shouldHaveSize(5); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/SincePreviousVersionHistoryTest.java b/tests/src/test/java/org/sonarqube/tests/measure/SincePreviousVersionHistoryTest.java new file mode 100644 index 00000000000..5dd7e15ca23 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/SincePreviousVersionHistoryTest.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.annotation.Nullable; +import org.apache.commons.lang.time.DateUtils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static java.lang.Integer.parseInt; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.WsMeasures.Measure; +import static org.sonarqube.ws.WsMeasures.PeriodValue; +import static util.ItUtils.getLeakPeriodValue; +import static util.ItUtils.getMeasureWithVariation; +import static util.ItUtils.projectDir; +import static util.ItUtils.setServerProperty; + +public class SincePreviousVersionHistoryTest { + + private static final String PROJECT = "com.sonarsource.it.samples:multi-modules-sample"; + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @BeforeClass + public static void initPeriod() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_version"); + } + + @AfterClass + public static void resetPeriod() throws Exception { + ItUtils.resetPeriod(orchestrator); + } + + private static void analyzeProject(String version) { + analyzeProject(version, null, null); + } + + private static void analyzeProjectWithExclusions(String version, String exclusions) { + analyzeProject(version, exclusions, null); + } + + private static void analyzeProjectWithDate(String version, String date) { + analyzeProject(version, null, date); + } + + private static void analyzeProject(String version, @Nullable String exclusions, @Nullable String date) { + SonarScanner build = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")) + .setProperties("sonar.projectVersion", version); + if (exclusions != null) { + build.setProperties("sonar.exclusions", exclusions); + } + if (date != null) { + build.setProperty("sonar.projectDate", date); + } + orchestrator.executeBuild(build); + } + + public static String toStringDate(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(date); + } + + @Before + public void resetData() throws Exception { + orchestrator.resetData(); + } + + /** + * SONAR-2496 + */ + @Test + public void test_since_previous_version_period() { + analyzeProjectWithExclusions("0.9", "**/*2.xoo"); + analyzeProject("1.0-SNAPSHOT"); + analyzeProject("1.0-SNAPSHOT"); + + Measure measure = getMeasureWithVariation(orchestrator, PROJECT, "files"); + + // There are 4 files + assertThat(parseInt(measure.getValue())).isEqualTo(4); + // 2 files were added since the first analysis which was version 0.9 + assertThat(measure.getPeriods().getPeriodsValueList()).extracting(PeriodValue::getValue).contains("2"); + } + + /** + * SONAR-6356 + */ + @Test + public void since_previous_version_should_use_first_analysis_when_no_version_found() { + Date now = new Date(); + + // Analyze project by excluding some files + analyzeProject("1.0-SNAPSHOT", "**/*2.xoo", toStringDate(DateUtils.addDays(now, -2))); + // No difference measure after first analysis + assertThat(getLeakPeriodValue(orchestrator, PROJECT, "files")).isNull(); + + analyzeProjectWithDate("1.0-SNAPSHOT", toStringDate(DateUtils.addDays(now, -1))); + // No new version, first analysis is used -> 2 new files + assertThat(getLeakPeriodValue(orchestrator, PROJECT, "files")).isEqualTo(2); + + analyzeProjectWithDate("1.0-SNAPSHOT", toStringDate(now)); + // Still no new version, first analysis is used -> 2 new files + assertThat(getLeakPeriodValue(orchestrator, PROJECT, "files")).isEqualTo(2); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/SinceXDaysHistoryTest.java b/tests/src/test/java/org/sonarqube/tests/measure/SinceXDaysHistoryTest.java new file mode 100644 index 00000000000..b37942b817e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/SinceXDaysHistoryTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.annotation.Nullable; +import org.apache.commons.lang.time.DateUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures; +import util.ItUtils; + +import static java.lang.Integer.parseInt; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasureWithVariation; +import static util.ItUtils.projectDir; +import static util.ItUtils.setServerProperty; + +public class SinceXDaysHistoryTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + private static final String PROJECT = "multi-files-sample"; + + @BeforeClass + public static void analyseProjectWithHistory() { + initPeriod(); + + orchestrator.resetData(); + ItUtils.restoreProfile(orchestrator, SinceXDaysHistoryTest.class.getResource("/measure/one-issue-per-line-profile.xml")); + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "one-issue-per-line"); + + // Execute a analysis in the past before since 30 days period -> 0 issue, 0 file + analyzeProject("2013-01-01", "**/File1*,**/File2*,**/File3*,**/File4*"); + + // Execute a analysis 20 days ago, after since 30 days period -> 16 issues, 1 file + analyzeProject(getPastDate(20), "**/File2*,**/File3*,**/File4*"); + + // Execute a analysis 10 days ago, after since 30 days period -> 28 issues, 2 files + analyzeProject(getPastDate(10), "**/File3*,**/File4*"); + + // Execute a analysis in the present with all modules -> 52 issues, 4 files + analyzeProject(); + } + + private static void initPeriod() { + setServerProperty(orchestrator, "sonar.leak.period", "30"); + } + + @AfterClass + public static void resetPeriods() throws Exception { + ItUtils.resetPeriod(orchestrator); + } + + @Test + public void check_files_variation() throws Exception { + checkMeasure("files", 3); + } + + @Test + public void check_issues_variation() throws Exception { + checkMeasure("violations", 45); + } + + @Test + public void check_new_issues_measures() throws Exception { + checkMeasure("new_violations", 45); + } + + private void checkMeasure(String metric, int variation) { + WsMeasures.Measure measure = getMeasureWithVariation(orchestrator, PROJECT, metric); + assertThat(measure.getPeriods().getPeriodsValueList()).extracting(periodValue -> parseInt(periodValue.getValue())).containsOnly(variation); + } + + private static void analyzeProject() { + analyzeProject(null, null); + } + + private static void analyzeProject(@Nullable String date, @Nullable String exclusions) { + SonarScanner runner = SonarScanner.create(projectDir("measureHistory/xoo-multi-files-sample")); + if (date != null) { + runner.setProperty("sonar.projectDate", date); + } + if (exclusions != null) { + runner.setProperties("sonar.exclusions", exclusions); + } + orchestrator.executeBuild(runner); + } + + private static String getPastDate(int nbPastDays) { + return new SimpleDateFormat("yyyy-MM-dd").format(DateUtils.addDays(new Date(), nbPastDays * -1)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/measure/TimeMachineTest.java b/tests/src/test/java/org/sonarqube/tests/measure/TimeMachineTest.java new file mode 100644 index 00000000000..90dbd6f9009 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/measure/TimeMachineTest.java @@ -0,0 +1,163 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.measure; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures.Measure; +import org.sonarqube.ws.WsMeasures.SearchHistoryResponse; +import org.sonarqube.ws.WsMeasures.SearchHistoryResponse.HistoryValue; +import org.sonarqube.ws.client.measure.MeasuresService; +import org.sonarqube.ws.client.measure.SearchHistoryRequest; +import util.ItUtils; +import util.ItUtils.ComponentNavigation; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.formatDate; +import static util.ItUtils.getComponentNavigation; +import static util.ItUtils.getMeasuresByMetricKey; +import static util.ItUtils.getMeasuresWithVariationsByMetricKey; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; +import static util.ItUtils.setServerProperty; + +public class TimeMachineTest { + + private static final String PROJECT = "sample"; + private static final String FIRST_ANALYSIS_DATE = "2014-10-19"; + private static final String SECOND_ANALYSIS_DATE = "2014-11-13"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + private static MeasuresService wsMeasures; + + @BeforeClass + public static void initialize() { + orchestrator.resetData(); + initPeriod(); + ItUtils.restoreProfile(orchestrator, TimeMachineTest.class.getResource("/measure/one-issue-per-line-profile.xml")); + orchestrator.getServer().provisionProject("sample", "Sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + analyzeProject("measure/xoo-history-v1", FIRST_ANALYSIS_DATE); + analyzeProject("measure/xoo-history-v2", SECOND_ANALYSIS_DATE); + + wsMeasures = newAdminWsClient(orchestrator).measures(); + } + + private static void initPeriod() { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + } + + @AfterClass + public static void resetPeriod() throws Exception { + ItUtils.resetPeriod(orchestrator); + } + + private static BuildResult analyzeProject(String path, String date) { + return orchestrator.executeBuild(SonarScanner.create(projectDir(path), "sonar.projectDate", date)); + } + + @Test + public void projectIsAnalyzed() { + ComponentNavigation component = getComponentNavigation(orchestrator, PROJECT); + assertThat(component.getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(component.getDate().getMonth()).isEqualTo(10); // November + } + + @Test + public void testHistoryOfIssues() { + SearchHistoryResponse response = searchHistory("blocker_violations", "critical_violations", "info_violations", "major_violations", "minor_violations"); + assertThat(response.getPaging().getTotal()).isEqualTo(2); + + assertHistory(response, "blocker_violations", "0", "0"); + assertHistory(response, "critical_violations", "0", "0"); + assertHistory(response, "info_violations", "0", "0"); + assertHistory(response, "major_violations", "0", "0"); + assertHistory(response, "minor_violations", "26", "43"); + } + + @Test + public void testHistoryOfMeasures() { + SearchHistoryResponse response = searchHistory("lines", "ncloc"); + + assertThat(response.getPaging().getTotal()).isEqualTo(2); + assertHistory(response, "lines", "26", "43"); + assertHistory(response, "ncloc", "24", "40"); + } + + @Test + public void noDataForInterval() { + Date now = new Date(); + + SearchHistoryResponse response = wsMeasures.searchHistory(SearchHistoryRequest.builder() + .setComponent(PROJECT) + .setMetrics(singletonList("lines")) + .setFrom(formatDate(now)) + .setTo(formatDate(now)) + .build()); + + assertThat(response.getPaging().getTotal()).isEqualTo(0); + assertThat(response.getMeasures(0).getHistoryList()).isEmpty(); + } + + /** + * SONAR-4962 + */ + @Test + public void measure_variations_are_only_meaningful_when_additional_fields_contains_periods() { + Map<String, Measure> measures = getMeasuresWithVariationsByMetricKey(orchestrator, PROJECT, "violations", "new_violations"); + assertThat(measures.get("violations")).isNotNull(); + assertThat(measures.get("new_violations")).isNotNull(); + SearchHistoryResponse response = searchHistory("new_violations"); + assertThat(response.getMeasures(0).getHistoryCount()).isGreaterThan(0); + + measures = getMeasuresByMetricKey(orchestrator, PROJECT, "violations", "new_violations"); + assertThat(measures.get("violations")).isNotNull(); + assertThat(measures.get("new_violations")).isNull(); + } + + private static SearchHistoryResponse searchHistory(String... metrics) { + return wsMeasures.searchHistory(SearchHistoryRequest.builder() + .setComponent(PROJECT) + .setMetrics(Arrays.asList(metrics)) + .build()); + } + + private static void assertHistory(SearchHistoryResponse response, String metric, String... expectedMeasures) { + for (SearchHistoryResponse.HistoryMeasure measures : response.getMeasuresList()) { + if (metric.equals(measures.getMetric())) { + assertThat(measures.getHistoryList()).extracting(HistoryValue::getValue).containsExactly(expectedMeasures); + return; + } + } + + throw new IllegalArgumentException("Metric not found"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/BillingTest.java b/tests/src/test/java/org/sonarqube/tests/organization/BillingTest.java new file mode 100644 index 00000000000..22d209954e3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/BillingTest.java @@ -0,0 +1,238 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category6Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.organization.UpdateProjectVisibilityWsRequest; +import org.sonarqube.ws.client.project.CreateRequest; +import org.sonarqube.ws.client.project.UpdateVisibilityRequest; +import org.sonarqube.pageobjects.Navigation; +import util.ItUtils; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.WsCe.TaskResponse; +import static util.ItUtils.expectHttpError; +import static util.ItUtils.newProjectKey; +import static util.ItUtils.projectDir; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class BillingTest { + + private static final String PROPERTY_PREVENT_ANALYSIS = "sonar.billing.preventProjectAnalysis"; + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + private Organizations.Organization organization; + private User orgAdministrator; + + @Before + @After + public void reset() { + resetSettings(orchestrator, null, PROPERTY_PREVENT_ANALYSIS, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate"); + } + + @Before + public void setUp() { + organization = tester.organizations().generate(); + orgAdministrator = tester.users().generateAdministrator(organization); + } + + @Test + public void execute_successfully_ce_analysis_on_organization() { + setServerProperty(orchestrator, PROPERTY_PREVENT_ANALYSIS, "false"); + + String taskUuid = executeAnalysis(newProjectKey()); + + TaskResponse taskResponse = tester.wsClient().ce().task(taskUuid); + assertThat(taskResponse.getTask().hasErrorMessage()).isFalse(); + } + + @Test + public void fail_to_execute_ce_analysis_on_organization() { + setServerProperty(orchestrator, PROPERTY_PREVENT_ANALYSIS, "true"); + + String taskUuid = executeAnalysis(newProjectKey()); + + TaskResponse taskResponse = tester.wsClient().ce().task(taskUuid); + assertThat(taskResponse.getTask().hasErrorMessage()).isTrue(); + assertThat(taskResponse.getTask().getErrorMessage()).contains(format("Organization %s cannot perform analysis", organization.getKey())); + } + + @Test + public void api_navigation_organization_returns_canUpdateProjectsVisibilityToPrivate() { + User user = tester.users().generate(); + tester.organizations().addMember(organization, user); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + assertWsResponseAsAdmin(new GetRequest("api/navigation/organization").setParam("organization", organization.getKey()), + "\"canUpdateProjectsVisibilityToPrivate\":true"); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + assertWsResponseAsAdmin(new GetRequest("api/navigation/organization").setParam("organization", organization.getKey()), + "\"canUpdateProjectsVisibilityToPrivate\":false"); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + assertWsResponseAsUser(new GetRequest("api/navigation/organization").setParam("organization", organization.getKey()), + "\"canUpdateProjectsVisibilityToPrivate\":false", user); + } + + @Test + public void api_navigation_component_returns_canUpdateProjectVisibilityToPrivate() { + String projectKey = createPublicProject(); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + assertWsResponseAsAdmin(new GetRequest("api/navigation/component").setParam("componentKey", projectKey), + "\"canUpdateProjectVisibilityToPrivate\":true"); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + assertWsResponseAsAdmin(new GetRequest("api/navigation/component").setParam("componentKey", projectKey), + "\"canUpdateProjectVisibilityToPrivate\":false"); + } + + @Test + public void does_not_fail_to_update_default_projects_visibility_to_private() { + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + + tester.wsClient().organizations().updateProjectVisibility(UpdateProjectVisibilityWsRequest.builder() + .setOrganization(organization.getKey()) + .setProjectVisibility("private") + .build()); + + assertWsResponseAsAdmin(new GetRequest("api/navigation/organization").setParam("organization", organization.getKey()), + "\"projectVisibility\":\"private\""); + } + + @Test + public void fail_to_update_organization_default_visibility_to_private() { + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + + expectHttpError(400, + format("Organization %s cannot use private project", organization.getKey()), + () -> tester.wsClient().organizations() + .updateProjectVisibility(UpdateProjectVisibilityWsRequest.builder().setOrganization(organization.getKey()).setProjectVisibility("private").build())); + } + + @Test + public void does_not_fail_to_update_project_visibility_to_private() { + String projectKey = createPublicProject(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + + tester.wsClient().projects().updateVisibility(UpdateVisibilityRequest.builder().setProject(projectKey).setVisibility("private").build()); + + assertWsResponseAsAdmin(new GetRequest("api/navigation/component").setParam("componentKey", projectKey), "\"visibility\":\"private\""); + } + + @Test + public void fail_to_update_project_visibility_to_private() { + String projectKey = createPublicProject(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + + expectHttpError(400, + format("Organization %s cannot use private project", organization.getKey()), + () -> tester.wsClient().projects().updateVisibility(UpdateVisibilityRequest.builder().setProject(projectKey).setVisibility("private").build())); + } + + @Test + public void does_not_fail_to_create_private_project() { + String projectKey = newProjectKey(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + + tester.wsClient().projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organization.getKey()).setVisibility("public").build()); + + assertWsResponseAsAdmin(new GetRequest("api/navigation/component").setParam("componentKey", projectKey), "\"visibility\":\"public\""); + } + + @Test + public void fail_to_create_private_project() { + String projectKey = newProjectKey(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + + expectHttpError(400, + format("Organization %s cannot use private project", organization.getKey()), + () -> tester.wsClient().projects() + .create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organization.getKey()).setVisibility("private").build())); + } + + @Test + public void ui_does_not_allow_to_turn_project_to_private() { + String projectKey = createPublicProject(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + + Navigation.create(orchestrator) + .logIn().submitCredentials(orgAdministrator.getLogin()) + .openProjectPermissions(projectKey) + .shouldBePublic() + .shouldNotAllowPrivate(); + } + + @Test + public void ui_allows_to_turn_project_to_private() { + String projectKey = createPublicProject(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + + tester.openBrowser() + .logIn().submitCredentials(orgAdministrator.getLogin()) + .openProjectPermissions(projectKey) + .shouldBePublic() + .turnToPrivate(); + } + + private String createPublicProject() { + return tester.projects().generate(organization).getKey(); + } + + private String executeAnalysis(String projectKey) { + BuildResult buildResult = orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"), + "sonar.organization", organization.getKey(), + "sonar.projectKey", projectKey, + "sonar.login", orgAdministrator.getLogin(), + "sonar.password", orgAdministrator.getLogin())); + return ItUtils.extractCeTaskId(buildResult); + } + + private void assertWsResponseAsAdmin(GetRequest request, String expectedContent) { + WsResponse response = tester.wsClient().wsConnector().call(request).failIfNotSuccessful(); + assertThat(response.content()).contains(expectedContent); + } + + private void assertWsResponseAsUser(GetRequest request, String expectedContent, User user) { + WsResponse response = tester.as(user.getLogin()).wsClient().wsConnector().call(request).failIfNotSuccessful(); + assertThat(response.content()).contains(expectedContent); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/OrganizationMembershipTest.java b/tests/src/test/java/org/sonarqube/tests/organization/OrganizationMembershipTest.java new file mode 100644 index 00000000000..256b30b7271 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/OrganizationMembershipTest.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.permission.AddUserWsRequest; + +import static util.ItUtils.setServerProperty; + +public class OrganizationMembershipTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @BeforeClass + public static void setUp() { + setServerProperty(orchestrator, "sonar.organizations.anyoneCanCreate", "true"); + } + + @AfterClass + public static void tearDown() { + setServerProperty(orchestrator, "sonar.organizations.anyoneCanCreate", null); + } + + @Test + public void new_user_should_not_become_member_of_default_organization() { + User user = tester.users().generate(); + tester.organizations().assertThatNotMemberOf(null, user); + } + + @Test + public void add_and_remove_member() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + + addMembership(organization, user); + tester.organizations().assertThatMemberOf(organization, user); + + removeMembership(organization, user); + tester.organizations().assertThatNotMemberOf(organization, user); + } + + @Test + public void remove_organization_admin_member() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + addMembership(organization, user); + + tester.wsClient().permissions().addUser(new AddUserWsRequest().setLogin(user.getLogin()).setPermission("admin").setOrganization(organization.getKey())); + tester.organizations().assertThatMemberOf(organization, user); + + removeMembership(organization, user); + tester.organizations().assertThatNotMemberOf(organization, user); + } + + @Test + public void fail_to_remove_organization_admin_member_when_last_admin() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + addMembership(organization, user); + + tester.wsClient().permissions().addUser(new AddUserWsRequest().setLogin(user.getLogin()).setPermission("admin").setOrganization(organization.getKey())); + tester.organizations().assertThatMemberOf(organization, user); + // Admin is the creator of the organization so he was granted with admin permission + tester.wsClient().organizations().removeMember(organization.getKey(), "admin"); + + expectedException.expect(HttpException.class); + expectedException.expectMessage("The last administrator member cannot be removed"); + removeMembership(organization, user); + } + + @Test + public void remove_user_remove_its_membership() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + addMembership(organization, user); + + tester.users().service().deactivate(user.getLogin()); + tester.organizations().assertThatNotMemberOf(organization, user); + } + + @Test + public void user_creating_an_organization_becomes_member_of_this_organization() { + User user = tester.users().generate(); + + Organization organization = tester.as(user.getLogin()).organizations().generate(); + + tester.organizations().assertThatMemberOf(organization, user); + } + + private void addMembership(Organization organization, User user) { + tester.organizations().addMember(organization, user); + } + + private void removeMembership(Organization organization, User user) { + tester.wsClient().organizations().removeMember(organization.getKey(), user.getLogin()); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/OrganizationMembershipUiTest.java b/tests/src/test/java/org/sonarqube/tests/organization/OrganizationMembershipUiTest.java new file mode 100644 index 00000000000..eb6b6bb639e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/OrganizationMembershipUiTest.java @@ -0,0 +1,179 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.OrganizationTester; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.pageobjects.organization.MembersPage; + +import static util.ItUtils.setServerProperty; + +public class OrganizationMembershipUiTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + private User root; + + @Before + public void setUp() { + setServerProperty(orchestrator, "sonar.organizations.anyoneCanCreate", "true"); + root = tester.users().generate(); + tester.wsClient().roots().setRoot(root.getLogin()); + } + + @After + public void tearDown() { + setServerProperty(orchestrator, "sonar.organizations.anyoneCanCreate", null); + } + + @Test + public void should_display_members_page() { + Organization organization = tester.organizations().generate(); + User member1 = tester.users().generate(p -> p.setName("foo")); + addMember(organization, member1); + User member2 = tester.users().generate(p -> p.setName("bar")); + addMember(organization, member2); + User nonMember = tester.users().generate(); + + MembersPage page = tester.openBrowser().openOrganizationMembers(organization.getKey()); + page + .canNotAddMember() + .shouldHaveTotal(3); + page.getMembersByIdx(0).shouldBeNamed("admin", "Administrator"); + page.getMembersByIdx(1) + .shouldBeNamed(member2.getLogin(), member2.getName()); + page.getMembersByIdx(2) + .shouldBeNamed(member1.getLogin(), member1.getName()) + .shouldNotHaveActions(); + } + + @Test + public void search_for_members() { + Organization organization = tester.organizations().generate(); + User member1 = tester.users().generate(p -> p.setName("foo")); + addMember(organization, member1); + User member2 = tester.users().generate(p -> p.setName("sameprefixuser1")); + addMember(organization, member2); + // Created to verify that only the user part of the org is returned + User userWithSameNamePrefix = tester.users().generate(p -> p.setName(member2.getName() + "sameprefixuser2")); + + MembersPage page = tester.openBrowser().openOrganizationMembers(organization.getKey()); + page + .searchForMember("sameprefixuser") + .shouldHaveTotal(1); + page.getMembersByIdx(0).shouldBeNamed(member2.getLogin(), member2.getName()); + page + .searchForMember(member1.getLogin()) + .shouldHaveTotal(1); + page.getMembersByIdx(0).shouldBeNamed(member1.getLogin(), member1.getName()); + } + + @Test + public void admin_can_add_members() { + Organization organization = tester.organizations().generate(); + User user1 = tester.users().generate(u -> u.setLogin("foo")); + User user2 = tester.users().generate(); + + MembersPage page = tester.openBrowser() + .logIn().submitCredentials(root.getLogin()) + .openOrganizationMembers(organization.getKey()); + page + .shouldHaveTotal(1) + .addMember(user1.getLogin()) + .shouldHaveTotal(2); + page.getMembersByIdx(0).shouldBeNamed("admin", "Administrator").shouldHaveGroups(2); + page.getMembersByIdx(1).shouldBeNamed(user1.getLogin(), user1.getName()).shouldHaveGroups(1); + } + + @Test + public void admin_can_remove_members() { + Organization organization = tester.organizations().generate(); + User user1 = tester.users().generate(); + addMember(organization, user1); + User user2 = tester.users().generate(); + addMember(organization, user2); + + MembersPage page = tester.openBrowser() + .logIn().submitCredentials(root.getLogin()) + .openOrganizationMembers(organization.getKey()); + page.shouldHaveTotal(3) + .getMembersByIdx(1).removeMembership(); + page.shouldHaveTotal(2); + } + + @Test + public void admin_can_manage_groups() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(u -> u.setLogin("foo")); + addMember(organization, user); + + MembersPage page = tester.openBrowser() + .logIn().submitCredentials(root.getLogin()) + .openOrganizationMembers(organization.getKey()); + // foo user + page.getMembersByIdx(1) + .manageGroupsOpen() + .manageGroupsSelect("owners") + .manageGroupsSave() + .shouldHaveGroups(2); + // admin user + page.getMembersByIdx(0) + .manageGroupsOpen() + .manageGroupsSelect("owners") + .manageGroupsSave() + .shouldHaveGroups(1); + } + + @Test + public void groups_count_should_be_updated_when_a_member_was_just_added() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + + MembersPage page = tester.openBrowser() + .logIn().submitCredentials(root.getLogin()) + .openOrganizationMembers(organization.getKey()); + page + .addMember(user.getLogin()) + .getMembersByIdx(1) + .shouldHaveGroups(1) + .manageGroupsOpen() + .manageGroupsSelect("owners") + .manageGroupsSave() + .shouldHaveGroups(2); + } + + private OrganizationTester addMember(Organization organization, User member1) { + return tester.organizations().addMember(organization, member1); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/OrganizationTest.java b/tests/src/test/java/org/sonarqube/tests/organization/OrganizationTest.java new file mode 100644 index 00000000000..7ea671d5928 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/OrganizationTest.java @@ -0,0 +1,370 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildFailureException; +import org.sonarqube.tests.Category6Suite; +import java.util.List; +import java.util.Locale; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.tests.OrganizationTester; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.QualityProfiles; +import org.sonarqube.ws.Rules; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.WsUserGroups.Group; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.component.ComponentsService; +import org.sonarqube.ws.client.organization.CreateWsRequest; +import org.sonarqube.ws.client.organization.OrganizationService; +import org.sonarqube.ws.client.organization.SearchWsRequest; +import org.sonarqube.ws.client.organization.UpdateWsRequest; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.permission.PermissionsService; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.expectBadRequestError; +import static util.ItUtils.expectForbiddenError; +import static util.ItUtils.expectNotFoundError; +import static util.ItUtils.expectUnauthorizedError; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +public class OrganizationTest { + + private static final String SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS = "sonar.organizations.anyoneCanCreate"; + private static final String DEFAULT_ORGANIZATION_KEY = "default-organization"; + private static final String NAME = "Foo Company"; + // private static final String KEY = "foo-company"; + private static final String DESCRIPTION = "the description of Foo company"; + private static final String URL = "https://www.foo.fr"; + private static final String AVATAR_URL = "https://www.foo.fr/corporate_logo.png"; + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + @Rule + public Tester tester = new Tester(orchestrator); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @After + public void tearDown() { + setServerProperty(orchestrator, SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS, null); + } + + @Test + public void default_organization_should_exist() { + Organization defaultOrg = tester.organizations().service().search(SearchWsRequest.builder().build()) + .getOrganizationsList() + .stream() + .filter(Organization::getGuarded) + .findFirst() + .orElseThrow(IllegalStateException::new); + assertThat(defaultOrg.getKey()).isEqualTo(DEFAULT_ORGANIZATION_KEY); + assertThat(defaultOrg.getName()).isEqualTo("Default Organization"); + } + + @Test + public void default_organization_can_not_be_deleted() { + expectBadRequestError(() -> tester.organizations().service().delete(DEFAULT_ORGANIZATION_KEY)); + } + + @Test + public void create_update_and_delete_organizations() { + OrganizationService service = tester.organizations().service(); + + Organization org = tester.organizations().generate(o -> o + .setName(NAME) + .setDescription(DESCRIPTION) + .setUrl(URL) + .setAvatar(AVATAR_URL) + .build()); + assertThat(org.getName()).isEqualTo(NAME); + assertThat(org.getDescription()).isEqualTo(DESCRIPTION); + assertThat(org.getUrl()).isEqualTo(URL); + assertThat(org.getAvatar()).isEqualTo(AVATAR_URL); + + verifyOrganization(org, NAME, DESCRIPTION, URL, AVATAR_URL); + assertThatBuiltInQualityProfilesExist(org); + + // update by key + service.update(new UpdateWsRequest.Builder() + .setKey(org.getKey()) + .setName("new name") + .setDescription("new description") + .setUrl("new url") + .setAvatar("new avatar url") + .build()); + verifyOrganization(org, "new name", "new description", "new url", "new avatar url"); + + // remove optional fields + service.update(new UpdateWsRequest.Builder() + .setKey(org.getKey()) + .setName("new name 2") + .setDescription("") + .setUrl("") + .setAvatar("") + .build()); + verifyOrganization(org, "new name 2", null, null, null); + + // delete organization + service.delete(org.getKey()); + assertThatOrganizationDoesNotExit(org); + assertThatQualityProfilesDoNotExist(org); + + // create again + service.create(new CreateWsRequest.Builder() + .setName(NAME) + .setKey(org.getKey()) + .build()) + .getOrganization(); + verifyOrganization(org, NAME, null, null, null); + } + + @Test + public void create_generates_key_from_name() { + // create organization without key + String name = "Foo Company to keyize"; + String expectedKey = "foo-company-to-keyize"; + Organization createdOrganization = tester.organizations().service().create(new CreateWsRequest.Builder() + .setName(name) + .build()) + .getOrganization(); + assertThat(createdOrganization.getKey()).isEqualTo(expectedKey); + verifyOrganization(createdOrganization, name, null, null, null); + } + + @Test + public void anonymous_user_cannot_administrate_organization() { + Organization org = tester.organizations().generate(); + OrganizationTester anonymousTester = tester.asAnonymous().organizations(); + + expectForbiddenError(() -> anonymousTester.generate()); + expectUnauthorizedError(() -> anonymousTester.service().update(new UpdateWsRequest.Builder().setKey(org.getKey()).setName("new name").build())); + expectUnauthorizedError(() -> anonymousTester.service().delete(org.getKey())); + } + + @Test + public void logged_in_user_cannot_administrate_organization() { + Organization org = tester.organizations().generate(); + User user = tester.users().generate(); + OrganizationTester userTester = tester.as(user.getLogin()).organizations(); + + expectForbiddenError(() -> userTester.generate()); + expectForbiddenError(() -> userTester.service().update(new UpdateWsRequest.Builder().setKey(org.getKey()).setName("new name").build())); + expectForbiddenError(() -> userTester.service().delete(org.getKey())); + } + + @Test + public void logged_in_user_can_administrate_organization_if_root() { + User user = tester.users().generate(); + OrganizationTester asUser = tester.as(user.getLogin()).organizations(); + + tester.wsClient().roots().setRoot(user.getLogin()); + Organization org = asUser.generate(); + + // delete org, attempt recreate when no root anymore and ensure it can't anymore + asUser.service().delete(org.getKey()); + + tester.wsClient().roots().unsetRoot(user.getLogin()); + expectForbiddenError(() -> asUser.generate()); + } + + @Test + public void an_organization_member_can_analyze_project() { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + Group group = tester.groups().generate(organization); + // users.removeGroups("sonar-users"); + tester.organizations().service().addMember(organization.getKey(), user.getLogin()); + addPermissionsToUser(organization.getKey(), user.getLogin(), "provisioning", "scan"); + + runProjectAnalysis(orchestrator, "shared/xoo-sample", + "sonar.organization", organization.getKey(), + "sonar.login", user.getLogin(), + "sonar.password", user.getLogin()); + ComponentsService componentsService = tester.as(user.getLogin()).wsClient().components(); + assertThat(searchSampleProject(organization.getKey(), componentsService).getComponentsList()).hasSize(1); + } + + @Test + public void by_default_anonymous_cannot_analyse_project_on_organization() { + Organization organization = tester.organizations().generate(); + + try { + runProjectAnalysis(orchestrator, "shared/xoo-sample", + "sonar.organization", organization.getKey()); + fail(); + } catch (BuildFailureException e) { + assertThat(e.getResult().getLogs()).contains("Insufficient privileges"); + } + + ComponentsService componentsService = tester.wsClient().components(); + assertThat(searchSampleProject(organization.getKey(), componentsService).getComponentsCount()).isEqualTo(0); + } + + @Test + public void by_default_anonymous_can_browse_project_on_organization() { + Organization organization = tester.organizations().generate(); + + runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.organization", organization.getKey(), "sonar.login", "admin", "sonar.password", "admin"); + + ComponentsService componentsService = tester.asAnonymous().wsClient().components(); + assertThat(searchSampleProject(organization.getKey(), componentsService).getComponentsList()).hasSize(1); + } + + private void addPermissionsToUser(String orgKeyAndName, String login, String permission, String... otherPermissions) { + PermissionsService permissionsService = tester.wsClient().permissions(); + permissionsService.addUser(new AddUserWsRequest().setLogin(login).setOrganization(orgKeyAndName).setPermission(permission)); + for (String otherPermission : otherPermissions) { + permissionsService.addUser(new AddUserWsRequest().setLogin(login).setOrganization(orgKeyAndName).setPermission(otherPermission)); + } + } + + @Test + public void deleting_an_organization_deletes_its_projects() { + Organization organization = tester.organizations().generate(); + + runProjectAnalysis(orchestrator, "shared/xoo-sample", + "sonar.organization", organization.getKey(), + "sonar.login", "admin", + "sonar.password", "admin"); + ComponentsService componentsService = tester.wsClient().components(); + assertThat(searchSampleProject(organization.getKey(), componentsService).getComponentsList()).hasSize(1); + + tester.organizations().service().delete(organization.getKey()); + + expectNotFoundError(() -> searchSampleProject(organization.getKey(), componentsService)); + assertThatOrganizationDoesNotExit(organization); + } + + @Test + public void return_groups_belonging_to_a_user_on_an_organization() throws Exception { + Organization organization = tester.organizations().generate(); + User user = tester.users().generate(); + tester.organizations().service().addMember(organization.getKey(), user.getLogin()); + + Group group = tester.groups().generate(organization); + tester.groups().addMemberToGroups(organization, user.getLogin(), group.getName()); + + List<WsUsers.GroupsWsResponse.Group> memberOfGroups = tester.groups().getGroupsOfUser(organization, user.getLogin()); + + assertThat(memberOfGroups).extracting(WsUsers.GroupsWsResponse.Group::getName) + .containsExactlyInAnyOrder(group.getName(), "Members"); + } + + @Test + public void anonymous_cannot_create_organizations_even_if_anyone_is_allowed_to() { + setServerProperty(orchestrator, SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS, "true"); + + expectUnauthorizedError(() -> tester.asAnonymous().organizations().generate()); + } + + @Test + public void logged_in_user_can_create_organizations_if_anyone_is_allowed_to() { + setServerProperty(orchestrator, SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS, "true"); + User user = tester.users().generate(); + + Organization organization = tester.as(user.getLogin()).organizations().generate(); + + assertThat(organization.getName()).isNotEmpty(); + assertThat(organization.getKey()).isNotEmpty(); + assertThat(organization.getGuarded()).isFalse(); + + List<Organization> reloadedOrgs = tester.organizations().service().search(SearchWsRequest.builder().build()).getOrganizationsList(); + assertThat(reloadedOrgs) + .filteredOn(o -> o.getKey().equals(organization.getKey())) + .hasSize(1); + } + + private WsComponents.SearchWsResponse searchSampleProject(String organizationKey, ComponentsService componentsService) { + return componentsService + .search(new org.sonarqube.ws.client.component.SearchWsRequest() + .setOrganization(organizationKey) + .setQualifiers(singletonList("TRK")) + .setQuery("sample")); + } + + private void assertThatOrganizationDoesNotExit(Organization org) { + SearchWsRequest request = new SearchWsRequest.Builder().setOrganizations(org.getKey()).build(); + assertThat(tester.organizations().service().search(request).getOrganizationsList()).isEmpty(); + } + + private void verifyOrganization(Organization createdOrganization, String name, String description, String url, + String avatarUrl) { + SearchWsRequest request = new SearchWsRequest.Builder().setOrganizations(createdOrganization.getKey()).build(); + List<Organization> result = tester.organizations().service().search(request).getOrganizationsList(); + assertThat(result).hasSize(1); + Organization searchedOrganization = result.get(0); + assertThat(searchedOrganization.getKey()).isEqualTo(createdOrganization.getKey()); + assertThat(searchedOrganization.getName()).isEqualTo(name); + if (description == null) { + assertThat(searchedOrganization.hasDescription()).isFalse(); + } else { + assertThat(searchedOrganization.getDescription()).isEqualTo(description); + } + if (url == null) { + assertThat(searchedOrganization.hasUrl()).isFalse(); + } else { + assertThat(searchedOrganization.getUrl()).isEqualTo(url); + } + if (avatarUrl == null) { + assertThat(searchedOrganization.hasAvatar()).isFalse(); + } else { + assertThat(searchedOrganization.getAvatar()).isEqualTo(avatarUrl); + } + } + + private void assertThatBuiltInQualityProfilesExist(Organization org) { + org.sonarqube.ws.client.qualityprofile.SearchWsRequest profilesRequest = new org.sonarqube.ws.client.qualityprofile.SearchWsRequest() + .setOrganizationKey(org.getKey()); + QualityProfiles.SearchWsResponse response = tester.wsClient().qualityProfiles().search(profilesRequest); + assertThat(response.getProfilesCount()).isGreaterThan(0); + + response.getProfilesList().forEach(p -> { + assertThat(p.getIsInherited()).isFalse(); + assertThat(p.getProjectCount()).isEqualTo(0); + assertThat(p.getIsBuiltIn()).isTrue(); + if (p.getName().toLowerCase(Locale.ENGLISH).contains("empty")) { + assertThat(p.getActiveRuleCount()).isEqualTo(0); + } else { + assertThat(p.getActiveRuleCount()).isGreaterThan(0); + // that allows to check the Elasticsearch index of active rules + Rules.SearchResponse activeRulesResponse = tester.wsClient().rules().search(new org.sonarqube.ws.client.rule.SearchWsRequest().setActivation(true).setQProfile(p.getKey())); + assertThat(activeRulesResponse.getTotal()).as("profile " + p.getName()).isEqualTo(p.getActiveRuleCount()); + assertThat(activeRulesResponse.getRulesCount()).isEqualTo((int) p.getActiveRuleCount()); + } + }); + } + + private void assertThatQualityProfilesDoNotExist(Organization org) { + expectNotFoundError(() -> tester.wsClient().qualityProfiles().search( + new org.sonarqube.ws.client.qualityprofile.SearchWsRequest().setOrganizationKey(org.getKey()))); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/PersonalOrganizationTest.java b/tests/src/test/java/org/sonarqube/tests/organization/PersonalOrganizationTest.java new file mode 100644 index 00000000000..5f6320c6397 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/PersonalOrganizationTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.organization.SearchWsRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.setServerProperty; + +public class PersonalOrganizationTest { + + private static final String SETTING_CREATE_PERSONAL_ORG = "sonar.organizations.createPersonalOrg"; + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Before + public void setUp() { + setServerProperty(orchestrator, SETTING_CREATE_PERSONAL_ORG, "true"); + } + + @After + public void tearDown() { + setServerProperty(orchestrator, SETTING_CREATE_PERSONAL_ORG, null); + } + + @Test + public void personal_organizations_are_created_for_new_users() { + WsUsers.CreateWsResponse.User user = tester.users().generate(); + + List<Organizations.Organization> existing = tester.wsClient().organizations().search(SearchWsRequest.builder().build()).getOrganizationsList(); + assertThat(existing) + .filteredOn(o -> o.getGuarded()) + .filteredOn(o -> o.getKey().equals(user.getLogin())) + .hasSize(1) + .matches(l -> l.get(0).getName().equals(user.getName())); + + tester.organizations().assertThatMemberOf(existing.get(0), user); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/RootUserOnOrganizationTest.java b/tests/src/test/java/org/sonarqube/tests/organization/RootUserOnOrganizationTest.java new file mode 100644 index 00000000000..e462fb709c5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/RootUserOnOrganizationTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import java.sql.SQLException; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Session; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsRoot; +import org.sonarqube.ws.WsUsers; +import util.user.UserRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.expectBadRequestError; +import static util.ItUtils.expectForbiddenError; + +public class RootUserOnOrganizationTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Test + public void system_administrator_is_flagged_as_root_when_he_enables_organization_support() { + assertThat(tester.wsClient().roots().search().getRootsList()) + .extracting(WsRoot.Root::getLogin) + .containsExactly(UserRule.ADMIN_LOGIN); + } + + @Test + public void a_root_can_flag_other_user_as_root() { + WsUsers.CreateWsResponse.User user = tester.users().generate(); + tester.wsClient().roots().setRoot(user.getLogin()); + + assertThat(tester.wsClient().roots().search().getRootsList()) + .extracting(WsRoot.Root::getLogin) + .containsExactlyInAnyOrder(UserRule.ADMIN_LOGIN, user.getLogin()); + } + + @Test + public void last_root_can_not_be_unset_root() throws SQLException { + expectBadRequestError(() -> tester.wsClient().roots().unsetRoot(UserRule.ADMIN_LOGIN)); + } + + @Test + public void root_can_be_set_and_unset_via_web_services() { + WsUsers.CreateWsResponse.User user1 = tester.users().generate(); + WsUsers.CreateWsResponse.User user2 = tester.users().generate(); + Session user1Session = tester.as(user1.getLogin()); + Session user2Session = tester.as(user2.getLogin()); + + // non root can not set or unset root another user not itself + expectForbiddenError(() -> user1Session.wsClient().roots().setRoot(user2.getLogin())); + expectForbiddenError(() -> user1Session.wsClient().roots().setRoot(user1.getLogin())); + expectForbiddenError(() -> user1Session.wsClient().roots().unsetRoot(user1.getLogin())); + expectForbiddenError(() -> user2Session.wsClient().roots().unsetRoot(user1.getLogin())); + expectForbiddenError(() -> user2Session.wsClient().roots().unsetRoot(user2.getLogin())); + // admin (the first root) sets root1 as root + tester.wsClient().roots().setRoot(user1.getLogin()); + // root1 can set root root2 + user1Session.wsClient().roots().setRoot(user2.getLogin()); + // root2 can unset root root1 + user2Session.wsClient().roots().unsetRoot(user1.getLogin()); + // root2 can unset root itself as it's not the last root + user2Session.wsClient().roots().unsetRoot(user2.getLogin()); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/organization/RootUserTest.java b/tests/src/test/java/org/sonarqube/tests/organization/RootUserTest.java new file mode 100644 index 00000000000..517fc94d0f1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/organization/RootUserTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.organization; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; + +import static util.ItUtils.expectForbiddenError; + +public class RootUserTest { + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void nobody_is_root_by_default_when_organizations_are_disabled() { + // anonymous + expectForbiddenError(() -> tester.wsClient().roots().search()); + + // admin + expectForbiddenError(() -> tester.wsClient().roots().search()); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/AbstractPerfTest.java b/tests/src/test/java/org/sonarqube/tests/performance/AbstractPerfTest.java new file mode 100644 index 00000000000..2cb2519fdc4 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/AbstractPerfTest.java @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance; + +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.locator.FileLocation; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.hamcrest.CustomMatcher; +import org.junit.Rule; +import org.junit.rules.ErrorCollector; +import org.junit.rules.TestName; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class AbstractPerfTest { + static final double ACCEPTED_DURATION_VARIATION_IN_PERCENTS = 8.0; + + @Rule + public TestName testName = new TestName(); + + protected void assertDurationAround(long duration, long expectedDuration) { + double variation = 100.0 * (0.0 + duration - expectedDuration) / expectedDuration; + System.out.printf("Test %s : executed in %d ms (%.2f %% from target)\n", testName.getMethodName(), duration, variation); + assertThat(Math.abs(variation)).as(String.format("Expected %d ms, got %d ms", expectedDuration, duration)).isLessThan(ACCEPTED_DURATION_VARIATION_IN_PERCENTS); + } + + protected void assertDurationAround(ErrorCollector collector, long duration, long expectedDuration) { + double variation = 100.0 * (0.0 + duration - expectedDuration) / expectedDuration; + System.out.printf("Test %s : executed in %d ms (%.2f %% from target)\n", testName.getMethodName(), duration, variation); + collector.checkThat(String.format("Expected %d ms, got %d ms", expectedDuration, duration), Math.abs(variation), new CustomMatcher<Double>("a value less than " + + ACCEPTED_DURATION_VARIATION_IN_PERCENTS) { + @Override + public boolean matches(Object item) { + return ((item instanceof Double) && ((Double) item).compareTo(ACCEPTED_DURATION_VARIATION_IN_PERCENTS) < 0); + } + }); + } + + protected void assertDurationLessThan(long duration, long maxDuration) { + System.out.printf("Test %s : %d ms (max allowed is %d)\n", testName.getMethodName(), duration, maxDuration); + assertThat(duration).as(String.format("Expected less than %d ms, got %d ms", maxDuration, duration)).isLessThanOrEqualTo(maxDuration); + } + + protected void assertDurationLessThan(ErrorCollector collector, long duration, final long maxDuration) { + System.out.printf("Test %s : %d ms (max allowed is %d)\n", testName.getMethodName(), duration, maxDuration); + collector.checkThat(String.format("Expected less than %d ms, got %d ms", maxDuration, duration), duration, new CustomMatcher<Long>("a value less than " + + maxDuration) { + @Override + public boolean matches(Object item) { + return ((item instanceof Long) && ((Long) item).compareTo(maxDuration) < 0); + } + }); + } + + protected Properties readProfiling(File baseDir, String moduleKey) throws IOException { + File profilingFile = new File(baseDir, ".sonar/profiling/" + moduleKey + "-profiler.properties"); + Properties props = new Properties(); + FileInputStream in = FileUtils.openInputStream(profilingFile); + try { + props.load(in); + } finally { + IOUtils.closeQuietly(in); + } + return props; + } + + /** + * New batch analysis with most features disabled by default (empty QP, no CPD, no SCM, ...) + */ + public static SonarScanner newScanner(String sonarRunnerOpts, String... props) { + SonarScanner scanner = SonarScanner.create() + .setProperties( + "sonar.scm.disabled", "true", + "sonar.cpd.exclusions", "**") + .setProperties(props); + scanner + .setEnvironmentVariable("SONAR_RUNNER_OPTS", sonarRunnerOpts) + .setEnvironmentVariable("SONAR_SCANNER_OPTS", sonarRunnerOpts) + .setProjectDir(FileLocation.of("projects/performance/xoo-sample").getFile()); + return scanner; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/MavenLogs.java b/tests/src/test/java/org/sonarqube/tests/performance/MavenLogs.java new file mode 100644 index 00000000000..ee9cc7a5c80 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/MavenLogs.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang.StringUtils; + +public class MavenLogs { + + /** + * Total time: 6.015s + * Total time: 3:14.025s + */ + public static Long extractTotalTime(String logs) { + Pattern pattern = Pattern.compile("^.*Total time: (\\d*:)?(\\d+).(\\d+)s.*$", Pattern.DOTALL); + Matcher matcher = pattern.matcher(logs); + if (matcher.matches()) { + String minutes = StringUtils.defaultIfBlank(StringUtils.removeEnd(matcher.group(1), ":"), "0"); + String seconds = StringUtils.defaultIfBlank(matcher.group(2), "0"); + String millis = StringUtils.defaultIfBlank(matcher.group(3), "0"); + + return (Long.parseLong(minutes) * 60000) + (Long.parseLong(seconds) * 1000) + Long.parseLong(millis); + } + throw new IllegalStateException("Maven logs do not contain \"Total time\""); + } + + /** + * Final Memory: 68M/190M + */ + public static Long extractEndMemory(String logs) { + return extractLong(logs, ".*Final Memory: (\\d+)M/[\\d]+M.*"); + } + + public static Long extractMaxMemory(String logs) { + return extractLong(logs, ".*Final Memory: [\\d]+M/(\\d+)M.*"); + } + + private static Long extractLong(String logs, String format) { + Pattern pattern = Pattern.compile(format); + Matcher matcher = pattern.matcher(logs); + if (matcher.matches()) { + String s = matcher.group(1); + return Long.parseLong(s); + } + return null; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/MavenLogsTest.java b/tests/src/test/java/org/sonarqube/tests/performance/MavenLogsTest.java new file mode 100644 index 00000000000..b9df51bbebc --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/MavenLogsTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MavenLogsTest { + @Test + public void testExtractTotalTime() throws Exception { + assertThat(MavenLogs.extractTotalTime(" \n Total time: 6.015s \n ")).isEqualTo(6015); + assertThat(MavenLogs.extractTotalTime(" \n Total time: 3:14.025s\n ")).isEqualTo(194025); + } + + @Test + public void testMaxMemory() throws Exception { + assertThat(MavenLogs.extractMaxMemory(" Final Memory: 68M/190M ")).isEqualTo(190); + } + + @Test + public void testEndMemory() throws Exception { + assertThat(MavenLogs.extractEndMemory(" Final Memory: 68M/190M ")).isEqualTo(68); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/PerfRule.java b/tests/src/test/java/org/sonarqube/tests/performance/PerfRule.java new file mode 100644 index 00000000000..7b12d79bd38 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/PerfRule.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance; + +import com.google.common.base.Joiner; +import org.hamcrest.CustomMatcher; +import org.junit.rules.ErrorCollector; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public abstract class PerfRule extends ErrorCollector { + + private final int runCount; + private final List<List<Long>> recordedResults = new ArrayList<List<Long>>(); + + private int currentRun; + private String testName; + + public PerfRule(int runCount) { + this.runCount = runCount; + } + + @Override + public Statement apply(final Statement base, Description description) { + this.testName = description.getMethodName(); + return new Statement() { + @Override + public void evaluate() throws Throwable { + for (currentRun = 1; currentRun <= runCount; currentRun++) { + recordedResults.add(new ArrayList<Long>()); + beforeEachRun(); + base.evaluate(); + } + verify(); + } + + }; + } + + protected abstract void beforeEachRun(); + + public void assertDurationAround(long duration, long expectedDuration) { + currentResults().add(duration); + if (isLastRun()) { + long meanDuration = computeAverageDurationOfCurrentStep(); + double variation = 100.0 * (0.0 + meanDuration - expectedDuration) / expectedDuration; + checkThat(String.format("Expected %d ms in average, got %d ms [%s]", expectedDuration, meanDuration, Joiner.on(",").join(getAllResultsOfCurrentStep())), Math.abs(variation), + new CustomMatcher<Double>( + "a value less than " + + AbstractPerfTest.ACCEPTED_DURATION_VARIATION_IN_PERCENTS) { + @Override + public boolean matches(Object item) { + return ((item instanceof Double) && ((Double) item).compareTo(AbstractPerfTest.ACCEPTED_DURATION_VARIATION_IN_PERCENTS) < 0); + } + }); + } + } + + private Long[] getAllResultsOfCurrentStep() { + Long[] result = new Long[runCount]; + for (int i = 0; i < runCount; i++) { + result[i] = recordedResults.get(i).get(currentResults().size() - 1); + } + return result; + } + + private long computeAverageDurationOfCurrentStep() { + Long[] result = getAllResultsOfCurrentStep(); + // Compute a truncated mean by ignoring greater value + Arrays.sort(result); + long meanDuration = 0; + for (int i = 0; i < (runCount - 1); i++) { + meanDuration += result[i]; + } + meanDuration /= (runCount - 1); + return meanDuration; + } + + private List<Long> currentResults() { + return recordedResults.get(currentRun - 1); + } + + public void assertDurationLessThan(long duration, final long maxDuration) { + currentResults().add(duration); + if (isLastRun()) { + long meanDuration = computeAverageDurationOfCurrentStep(); + checkThat(String.format("Expected less than %d ms in average, got %d ms [%s]", maxDuration, meanDuration, Joiner.on(",").join(getAllResultsOfCurrentStep())), meanDuration, + new CustomMatcher<Long>("a value less than " + + maxDuration) { + @Override + public boolean matches(Object item) { + return ((item instanceof Long) && ((Long) item).compareTo(maxDuration) < 0); + } + }); + } + } + + private boolean isLastRun() { + return currentRun == runCount; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/ServerLogs.java b/tests/src/test/java/org/sonarqube/tests/performance/ServerLogs.java new file mode 100644 index 00000000000..ece4f4a26a7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/ServerLogs.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.container.Server; +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.FileUtils; + +public class ServerLogs { + + public static Date extractDate(String line) { + String pattern = "yyyy.MM.dd HH:mm:ss"; + SimpleDateFormat format = new SimpleDateFormat(pattern); + if (line.length() > 19) { + try { + return format.parse(line.substring(0, 19)); + } catch (Exception e) { + // ignore + } + } + return null; + } + + public static Date extractFirstDate(List<String> lines) { + for (String line : lines) { + Date d = ServerLogs.extractDate(line); + if (d != null) { + return d; + } + } + return null; + } + + public static void clear(Orchestrator orch) throws IOException { + Server server = orch.getServer(); + if (server != null) { + for (File file : new File[]{server.getAppLogs(), server.getWebLogs(), server.getCeLogs(), server.getEsLogs()}) { + if (file != null) { + FileUtils.write(file, "", false); + } + } + } + } + + /** + * 2015.09.29 16:57:45 INFO ce[o.s.s.c.q.CeWorkerRunnableImpl] Executed task | project=com.github.kevinsawicki:http-request-parent | id=AVAZm9oHIXrp54OmOeQe | time=2283ms + */ + public static Long extractComputationTotalTime(Orchestrator orchestrator) throws IOException { + File report = orchestrator.getServer().getCeLogs(); + List<String> logsLines = FileUtils.readLines(report, Charsets.UTF_8); + return extractComputationTotalTime(logsLines); + } + + static Long extractComputationTotalTime(List<String> logs) { + Pattern pattern = Pattern.compile(".*INFO.*Executed task.* \\| time=(\\d+)ms.*"); + for (int i = logs.size() - 1; i >= 0; i--) { + String line = logs.get(i); + Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + String duration = matcher.group(1); + return Long.parseLong(duration); + } + } + + return null; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/ServerLogsTest.java b/tests/src/test/java/org/sonarqube/tests/performance/ServerLogsTest.java new file mode 100644 index 00000000000..7ace69e4a52 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/ServerLogsTest.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance; + +import com.google.common.collect.Lists; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ServerLogsTest { + @Test + public void logs_with_different_computations_take_the_last_one() throws Exception { + assertThat(ServerLogs.extractComputationTotalTime(Lists.newArrayList( + "2015.09.29 16:57:45 INFO web[][o.s.s.c.q.CeWorkerRunnableImpl] Executed task | project=com.github.kevinsawicki:http-request-parent | id=AVAZm9oHIXrp54OmOeQe | time=2283ms", + "2015.09.29 16:57:45 INFO web[][o.s.s.c.q.CeWorkerRunnableImpl] Executed task | project=com.github.kevinsawicki:http-request-parent | id=AVAZm9oHIXrp54OmOeQe | time=1234ms"))) + .isEqualTo(1234L); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/BootstrappingTest.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/BootstrappingTest.java new file mode 100644 index 00000000000..a8e3c2376a8 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/BootstrappingTest.java @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.MavenLogs; +import org.sonarqube.tests.performance.PerfRule; + +@Ignore("Timeout on computation side") +public class BootstrappingTest extends AbstractPerfTest { + + @Rule + public PerfRule perfRule = new PerfRule(4) { + @Override + protected void beforeEachRun() { + orchestrator.resetData(); + } + }; + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + @ClassRule + public static Orchestrator orchestrator = ScannerPerformanceSuite.ORCHESTRATOR; + + private static File manyFlatModulesBaseDir; + private static File manyNestedModulesBaseDir; + + @BeforeClass + public static void setUp() throws Exception { + // Execute a first analysis to prevent any side effects with cache of plugin JAR files + orchestrator.executeBuild(newScanner("-Xmx512m -server", "sonar.profile", "one-xoo-issue-per-line")); + + manyFlatModulesBaseDir = prepareProjectWithManyFlatModules(100); + manyNestedModulesBaseDir = prepareProjectWithManyNestedModules(50); + } + + @Test + public void analyzeProjectWith100FlatModules() throws IOException { + + SonarScanner scanner = SonarScanner.create() + .setProperties( + "sonar.projectKey", "many-flat-modules", + "sonar.projectName", "Many Flat Modules", + "sonar.projectVersion", "1.0", + "sonar.sources", "", + "sonar.showProfiling", "true"); + scanner + .setEnvironmentVariable("SONAR_RUNNER_OPTS", "-Xmx512m -server") + .setProjectDir(manyFlatModulesBaseDir); + + BuildResult result = orchestrator.executeBuild(scanner); + // First analysis + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 22800L); + + result = orchestrator.executeBuild(scanner); + // Second analysis is longer since we load project referential + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 27200L); + } + + private static File prepareProjectWithManyFlatModules(int SIZE) throws IOException { + File baseDir = temp.newFolder(); + File projectProps = new File(baseDir, "sonar-project.properties"); + + StringBuilder moduleListBuilder = new StringBuilder(SIZE * ("module".length() + 2)); + + for (int i = 1; i <= SIZE; i++) { + moduleListBuilder.append("module").append(i); + File moduleDir = new File(baseDir, "module" + i); + moduleDir.mkdir(); + if (i != SIZE) { + moduleListBuilder.append(","); + } + } + + FileUtils.write(projectProps, "sonar.modules=", true); + FileUtils.write(projectProps, moduleListBuilder.toString(), true); + FileUtils.write(projectProps, "\n", true); + return baseDir; + } + + @Test + public void analyzeProjectWith50NestedModules() throws IOException { + SonarScanner scanner = SonarScanner.create() + .setProperties( + "sonar.projectKey", "many-nested-modules", + "sonar.projectName", "Many Nested Modules", + "sonar.projectVersion", "1.0", + "sonar.sources", "", + "sonar.showProfiling", "true"); + scanner.setEnvironmentVariable("SONAR_RUNNER_OPTS", "-Xmx512m -server"); + scanner.setProjectDir(manyNestedModulesBaseDir); + + BuildResult result = orchestrator.executeBuild(scanner); + // First analysis + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 8900L); + + result = orchestrator.executeBuild(scanner); + // Second analysis + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 9300L); + } + + private static File prepareProjectWithManyNestedModules(int SIZE) throws IOException { + File baseDir = temp.newFolder(); + File currentDir = baseDir; + + for (int i = 1; i <= SIZE; i++) { + File projectProps = new File(currentDir, "sonar-project.properties"); + FileUtils.write(projectProps, "sonar.modules=module" + i + "\n", true); + if (i >= 1) { + FileUtils.write(projectProps, "sonar.moduleKey=module" + (i - 1), true); + } + File moduleDir = new File(currentDir, "module" + i); + moduleDir.mkdir(); + currentDir = moduleDir; + } + FileUtils.write(new File(currentDir, "sonar-project.properties"), "sonar.moduleKey=module" + SIZE, true); + return baseDir; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/DuplicationTest.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/DuplicationTest.java new file mode 100644 index 00000000000..4729fb331d8 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/DuplicationTest.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.MavenBuild; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsClientFactories; +import org.sonarqube.ws.client.measure.ComponentWsRequest; + +import static java.lang.Double.parseDouble; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class DuplicationTest extends AbstractPerfTest { + + @Rule + public ErrorCollector collector = new ErrorCollector(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @ClassRule + public static final Orchestrator orchestrator = ScannerPerformanceSuite.ORCHESTRATOR; + + @BeforeClass + public static void setUp() throws IOException { + // Execute a first analysis to prevent any side effects with cache of plugin JAR files + orchestrator.executeBuild(newScanner("-Xmx512m -server", "sonar.profile", "one-xoo-issue-per-line")); + } + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + /** + * SONAR-3060 + */ + @Test + public void hugeJavaFile() { + MavenBuild build = MavenBuild.create(new File("projects/performance/huge-file/pom.xml")) + .setEnvironmentVariable("MAVEN_OPTS", "-Xmx1024m") + .setProperty("sonar.sourceEncoding", "UTF-8") + .setCleanSonarGoals(); + orchestrator.executeBuild(build); + Map<String, Double> measure = getMeasures("com.sonarsource.it.samples:huge-file:src/main/java/huge/HugeFile.java"); + assertThat(measure.get("duplicated_lines")).isGreaterThan(50000.0); + } + + private Map<String, Double> getMeasures(String key) { + return newWsClient().measures().component(new ComponentWsRequest() + .setComponentKey(key) + .setMetricKeys(asList("duplicated_lines", "duplicated_blocks", "duplicated_files", "duplicated_lines_density"))) + .getComponent().getMeasuresList() + .stream() + .collect(Collectors.toMap(WsMeasures.Measure::getMetric, measure -> parseDouble(measure.getValue()))); + } + + private WsClient newWsClient() { + return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() + .url(orchestrator.getServer().getUrl()) + .build()); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/FileSystemTest.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/FileSystemTest.java new file mode 100644 index 00000000000..fca5799f3ea --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/FileSystemTest.java @@ -0,0 +1,106 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarRunner; +import java.io.File; +import java.io.IOException; +import java.util.Properties; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.PerfRule; + +public class FileSystemTest extends AbstractPerfTest { + + @Rule + public PerfRule perfRule = new PerfRule(4) { + @Override + protected void beforeEachRun() { + orchestrator.resetData(); + } + }; + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + @ClassRule + public static Orchestrator orchestrator = ScannerPerformanceSuite.ORCHESTRATOR; + + private static File baseDir; + + @BeforeClass + public static void setUp() throws IOException { + // Execute a first analysis to prevent any side effects with cache of plugin JAR files + orchestrator.executeBuild(newScanner("-Xmx512m -server", "sonar.profile", "one-xoo-issue-per-line")); + baseDir = prepareProject(); + } + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + @Test + public void indexProjectWith1000BigFilesXmx128() throws IOException { + run(128, 30000L); + } + + private void run(int xmx, long expectedDuration) throws IOException { + SonarRunner runner = SonarRunner.create() + .setProperties( + "sonar.projectKey", "filesystemXmx" + xmx, + "sonar.projectName", "filesystem xmx" + xmx, + "sonar.projectVersion", "1.0", + "sonar.sources", "src", + "sonar.analysis.mode", "issues", + "sonar.preloadFileMetadata", "true", + "sonar.showProfiling", "true") + .setEnvironmentVariable("SONAR_RUNNER_OPTS", "-Xmx" + xmx + "m -server") + .setProjectDir(baseDir); + + orchestrator.executeBuild(runner); + + Properties prof = readProfiling(baseDir, "filesystemXmx" + xmx); + perfRule.assertDurationAround(Long.valueOf(prof.getProperty("Index filesystem")), expectedDuration); + } + + private static File prepareProject() throws IOException { + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + int nbFiles = 1000; + int lines = 10000; + for (int nb = 1; nb <= nbFiles; nb++) { + File xooFile = new File(srcDir, "sample" + nb + ".xoo"); + FileUtils.write(xooFile, StringUtils.repeat(StringUtils.repeat("a", 100) + "\n", lines)); + } + return baseDir; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/HighlightingTest.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/HighlightingTest.java new file mode 100644 index 00000000000..f995244763b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/HighlightingTest.java @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import java.io.File; +import java.io.IOException; +import java.util.Properties; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.MavenLogs; +import org.sonarqube.tests.performance.PerfRule; + +public class HighlightingTest extends AbstractPerfTest { + + @Rule + public PerfRule perfRule = new PerfRule(4) { + @Override + protected void beforeEachRun() { + orchestrator.resetData(); + } + }; + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + @ClassRule + public static Orchestrator orchestrator = ScannerPerformanceSuite.ORCHESTRATOR; + + @BeforeClass + public static void setUp() throws IOException { + // Execute a first analysis to prevent any side effects with cache of plugin JAR files + orchestrator.executeBuild(newScanner("-Xmx512m -server", "sonar.profile", "one-xoo-issue-per-line")); + } + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + @Test + public void computeSyntaxHighlightingOnBigFiles() throws IOException { + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + int nbFiles = 100; + int ruleCount = 100000; + int nblines = 1000; + int linesize = ruleCount / nblines; + for (int nb = 1; nb <= nbFiles; nb++) { + File xooFile = new File(srcDir, "sample" + nb + ".xoo"); + File xoohighlightingFile = new File(srcDir, "sample" + nb + ".xoo.highlighting"); + FileUtils.write(xooFile, StringUtils.repeat(StringUtils.repeat("a", linesize) + "\n", nblines)); + StringBuilder sb = new StringBuilder(16 * ruleCount); + for (int i = 0; i < ruleCount; i++) { + sb.append(i).append(":").append(i + 1).append(":s\n"); + } + FileUtils.write(xoohighlightingFile, sb.toString()); + } + + SonarScanner scanner = SonarScanner.create() + .setScannerVersion("2.4") + .setProperties( + "sonar.projectKey", "highlighting", + "sonar.projectName", "highlighting", + "sonar.projectVersion", "1.0", + "sonar.sources", "src", + "sonar.showProfiling", "true"); + scanner.setEnvironmentVariable("SONAR_RUNNER_OPTS", "-Xmx512m -server") + .setProjectDir(baseDir); + + BuildResult result = orchestrator.executeBuild(scanner); + System.out.println("Total time: " + MavenLogs.extractTotalTime(result.getLogs())); + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 25700L); + + Properties prof = readProfiling(baseDir, "highlighting"); + perfRule.assertDurationAround(Long.valueOf(prof.getProperty("Xoo Highlighting Sensor")), 9700L); + + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/IssuesModeTest.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/IssuesModeTest.java new file mode 100644 index 00000000000..03f811529e1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/IssuesModeTest.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import java.io.File; +import java.io.IOException; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.PerfRule; + +public class IssuesModeTest extends AbstractPerfTest { + + @ClassRule + public static Orchestrator orchestrator = ScannerPerformanceSuite.ORCHESTRATOR; + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public PerfRule perfRule = new PerfRule(4) { + @Override + protected void beforeEachRun() { + orchestrator.resetData(); + } + }; + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + @Test + public void issues_mode_scan_xoo_project() throws IOException { + File userHome = temp.newFolder(); + orchestrator.getServer().provisionProject("sample", "xoo-sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-xoo-issue-per-line"); + SonarScanner runner = newScanner( + "-Xmx512m -server", + "sonar.analysis.mode", "issues", + "sonar.userHome", userHome.getAbsolutePath(), + "sonar.showProfiling", "true") + .setScannerVersion("2.8"); + long start = System.currentTimeMillis(); + orchestrator.executeBuild(runner, false); + long duration = System.currentTimeMillis() - start; + System.out.println("Issues analysis: " + duration + "ms"); + + perfRule.assertDurationAround(duration, 4300L); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/MemoryTest.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/MemoryTest.java new file mode 100644 index 00000000000..2b9b162d72e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/MemoryTest.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.google.common.base.Strings; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.http.HttpMethod; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.MavenLogs; +import org.sonarqube.tests.performance.PerfRule; + +public class MemoryTest extends AbstractPerfTest { + + @Rule + public PerfRule perfRule = new PerfRule(4) { + @Override + protected void beforeEachRun() { + orchestrator.resetData(); + } + }; + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + @ClassRule + public static Orchestrator orchestrator = ScannerPerformanceSuite.ORCHESTRATOR; + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + int DEPTH = 4; + + @Test + public void should_not_fail_with_limited_xmx_memory_and_no_coverage_per_test() { + orchestrator.executeBuild( + newScanner("-Xmx80m -server -XX:-HeapDumpOnOutOfMemoryError")); + } + + // Property on root module is duplicated in each module so it may be big + @Test + public void analyzeProjectWithManyModulesAndBigProperties() throws IOException { + + File baseDir = temp.newFolder(); + + prepareModule(baseDir, "moduleA", 1); + prepareModule(baseDir, "moduleB", 1); + prepareModule(baseDir, "moduleC", 1); + + FileUtils.write(new File(baseDir, "sonar-project.properties"), "sonar.modules=moduleA,moduleB,moduleC\n", true); + FileUtils.write(new File(baseDir, "sonar-project.properties"), "sonar.myBigProp=" + Strings.repeat("A", 10000), true); + + SonarScanner scanner = SonarScanner.create() + .setScannerVersion("2.8") + .setProperties( + "sonar.projectKey", "big-module-tree", + "sonar.projectName", "Big Module Tree", + "sonar.projectVersion", "1.0", + "sonar.sources", "", + "sonar.showProfiling", "true"); + scanner.setEnvironmentVariable("SONAR_SCANNER_OPTS", "-Xmx512m -server") + .setProjectDir(baseDir); + + BuildResult result = orchestrator.executeBuild(scanner); + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 6190L); + + // Second execution with a property on server side + orchestrator.getServer().newHttpCall("/api/settings/set") + .setMethod(HttpMethod.POST) + .setAdminCredentials() + .setParam("key", "sonar.anotherBigProp") + .setParam("value", Strings.repeat("B", 1000)) + .setParam("component", "big-module-tree") + .execute(); + result = orchestrator.executeBuild(scanner); + perfRule.assertDurationAround(MavenLogs.extractTotalTime(result.getLogs()), 6120L); + } + + private void prepareModule(File parentDir, String moduleName, int depth) throws IOException { + File moduleDir = new File(parentDir, moduleName); + moduleDir.mkdir(); + File projectProps = new File(moduleDir, "sonar-project.properties"); + FileUtils.write(projectProps, "sonar.moduleKey=" + moduleName + "\n", true); + if (depth < DEPTH) { + FileUtils.write(projectProps, "sonar.modules=" + moduleName + "A," + moduleName + "B," + moduleName + "C\n", true); + prepareModule(moduleDir, moduleName + "A", depth + 1); + prepareModule(moduleDir, moduleName + "B", depth + 1); + prepareModule(moduleDir, moduleName + "C", depth + 1); + } + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/scanner/ScannerPerformanceSuite.java b/tests/src/test/java/org/sonarqube/tests/performance/scanner/ScannerPerformanceSuite.java new file mode 100644 index 00000000000..bfb8fb2e3b4 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/scanner/ScannerPerformanceSuite.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.scanner; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.locator.FileLocation; +import java.io.File; +import java.io.IOException; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.sonarqube.tests.performance.AbstractPerfTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + BootstrappingTest.class, + DuplicationTest.class, + FileSystemTest.class, + HighlightingTest.class, + IssuesModeTest.class, + MemoryTest.class +}) +public class ScannerPerformanceSuite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Orchestrator + .builderEnv() + .addPlugin(FileLocation.byWildcardMavenFilename(new File("../plugins/sonar-xoo-plugin/target"), "sonar-xoo-plugin-*.jar")) + // should not be so high, but required as long embedded h2 is used -> requires more memory on server + .setServerProperty("sonar.web.javaOpts", "-Xmx1G -XX:+HeapDumpOnOutOfMemoryError") + // Needed by DuplicationTest::hugeJavaFile + .setOrchestratorProperty("javaVersion", "LATEST_RELEASE").addPlugin("java") + .restoreProfileAtStartup(FileLocation.ofClasspath("/one-xoo-issue-per-line.xml")) + .build(); + + @BeforeClass + public static void setUp() throws IOException { + // Execute a first analysis to prevent any side effects with cache of plugin JAR files + ORCHESTRATOR.executeBuild(AbstractPerfTest.newScanner("-Xmx512m -server", "sonar.profile", "one-xoo-issue-per-line")); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/server/ComputeEnginePerfTest.java b/tests/src/test/java/org/sonarqube/tests/performance/server/ComputeEnginePerfTest.java new file mode 100644 index 00000000000..e6376641a37 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/server/ComputeEnginePerfTest.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.server; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import com.sonar.orchestrator.locator.FileLocation; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.ServerLogs; + +public class ComputeEnginePerfTest extends AbstractPerfTest { + private static int MAX_HEAP_SIZE_IN_MEGA = 600; + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + @ClassRule + public static Orchestrator orchestrator = Orchestrator + .builderEnv() + .addPlugin(FileLocation.byWildcardMavenFilename(new File("../plugins/sonar-xoo-plugin/target"), "sonar-xoo-plugin-*.jar")) + .setServerProperty( + "sonar.web.javaOpts", + String.format("-Xms%dm -Xmx%dm -XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true", MAX_HEAP_SIZE_IN_MEGA, MAX_HEAP_SIZE_IN_MEGA)) + .restoreProfileAtStartup(FileLocation.ofClasspath("/one-xoo-issue-per-line.xml")) + .build(); + + private static File bigProjectBaseDir; + + @BeforeClass + public static void classSetUp() throws IOException { + bigProjectBaseDir = createProject(4, 10, 20); + } + + @Before + public void before() throws Exception { + orchestrator.resetData(); + } + + @Test + public void analyse_big_project() throws Exception { + SonarScanner scanner = SonarScanner.create() + .setProperties( + "sonar.projectKey", "big-project", + "sonar.projectName", "Big Project", + "sonar.projectVersion", "1.0", + "sonar.sources", "src", + "sonar.profile", "one-xoo-issue-per-line") + .setProjectDir(bigProjectBaseDir); + + orchestrator.executeBuild(scanner); + + assertComputationDurationAround(350_000L); + } + + private void assertComputationDurationAround(long expectedDuration) throws IOException { + Long duration = ServerLogs.extractComputationTotalTime(orchestrator); + + assertDurationAround(duration, expectedDuration); + } + + private static File createProject(int dirDepth, int nbDirByLayer, int nbIssuesByFile) throws IOException { + File rootDir = temp.newFolder(); + File projectProperties = new File(rootDir, "sonar-project.properties"); + + StringBuilder moduleListBuilder = new StringBuilder(nbDirByLayer * ("module".length() + 2)); + + for (int i = 1; i <= nbDirByLayer; i++) { + moduleListBuilder.append("module").append(i); + File moduleDir = new File(rootDir, "module" + i + "/src"); + moduleDir.mkdirs(); + if (i != nbDirByLayer) { + moduleListBuilder.append(","); + } + + createProjectFiles(moduleDir, dirDepth - 1, nbDirByLayer, nbIssuesByFile); + } + + FileUtils.write(projectProperties, "sonar.modules=", true); + FileUtils.write(projectProperties, moduleListBuilder.toString(), true); + FileUtils.write(projectProperties, "\n", true); + FileUtils.write(projectProperties, "sonar.source=src", true); + + return rootDir; + } + + private static void createProjectFiles(File dir, int depth, int nbFilesByDir, int nbIssuesByFile) throws IOException { + dir.mkdir(); + for (int i = 1; i <= nbFilesByDir; i++) { + File xooFile = new File(dir, "file" + i + ".xoo"); + String line = xooFile.getAbsolutePath() + i + "\n"; + FileUtils.write(xooFile, StringUtils.repeat(line, nbIssuesByFile)); + File xooMeasureFile = new File(dir, "file" + i + ".xoo.measures"); + FileUtils.write(xooMeasureFile, "lines:" + nbIssuesByFile); + if (depth > 1) { + createProjectFiles(new File(dir, "dir" + i), depth - 1, nbFilesByDir, nbIssuesByFile); + } + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/server/ServerPerfTest.java b/tests/src/test/java/org/sonarqube/tests/performance/server/ServerPerfTest.java new file mode 100644 index 00000000000..7aa915ce982 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/server/ServerPerfTest.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.server; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.locator.FileLocation; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; +import org.sonarqube.tests.performance.AbstractPerfTest; +import org.sonarqube.tests.performance.ServerLogs; + +import static org.apache.commons.io.FileUtils.readLines; + +public class ServerPerfTest extends AbstractPerfTest { + private static final int TIMEOUT_3_MINUTES = 1000 * 60 * 3; + + @Rule + public Timeout timeout = new Timeout(TIMEOUT_3_MINUTES); + + // ES + TOMCAT + @Test + public void server_startup_and_shutdown() throws Exception { + String defaultWebJavaOptions = "-Xmx768m -XX:+HeapDumpOnOutOfMemoryError -Djava.awt.headless=true -Dfile.encoding=UTF-8"; + Orchestrator orchestrator = Orchestrator.builderEnv() + .addPlugin(FileLocation.byWildcardMavenFilename(new File("../plugins/sonar-xoo-plugin/target"), "sonar-xoo-plugin-*.jar")) + + // See http://wiki.apache.org/tomcat/HowTo/FasterStartUp + // Sometimes source of entropy is too small and Tomcat spends ~20 seconds on the step : + // "Creation of SecureRandom instance for session ID generation using [SHA1PRNG]" + // Using /dev/urandom fixes the issue on linux + .setServerProperty("sonar.web.javaOpts", defaultWebJavaOptions + " -Djava.security.egd=file:/dev/./urandom") + .build(); + try { + ServerLogs.clear(orchestrator); + orchestrator.start(); + + // compare dates of first and last log + long firstLogDate = ServerLogs.extractFirstDate(readLines(orchestrator.getServer().getAppLogs())).getTime(); + long startedAtDate = extractStartedAtDate(orchestrator); + assertDurationAround(startedAtDate - firstLogDate, 25_000); + + ServerLogs.clear(orchestrator); + orchestrator.stop(); + + List<String> lines = readLines(orchestrator.getServer().getAppLogs()); + long firstStopLogDate = ServerLogs.extractFirstDate(lines).getTime(); + long stopDate = extractStopDate(lines); + assertDurationLessThan(stopDate - firstStopLogDate, 10_000); + + } finally { + orchestrator.stop(); + } + } + + private static long extractStartedAtDate(Orchestrator orchestrator) throws IOException { + Date startedAtDate = extractStartedDate(readLines(orchestrator.getServer().getCeLogs())); + // if SQ never starts, the test will fail with timeout + while (startedAtDate == null) { + try { + Thread.sleep(100); + startedAtDate = extractStartedDate(readLines(orchestrator.getServer().getCeLogs())); + } catch (InterruptedException e) { + // ignored + Thread.currentThread().interrupt(); + } + } + return startedAtDate.getTime(); + } + + private static Date extractStartedDate(List<String> lines) { + Collections.reverse(lines); + Date end = null; + for (String line : lines) { + if (line.contains("Compute Engine is operational")) { + end = ServerLogs.extractDate(line); + break; + } + } + return end; + } + + private static long extractStopDate(List<String> lines) throws IOException { + Collections.reverse(lines); + Date end = ServerLogs.extractFirstDate(lines); + return end.getTime(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/performance/server/ServerPerformanceSuite.java b/tests/src/test/java/org/sonarqube/tests/performance/server/ServerPerformanceSuite.java new file mode 100644 index 00000000000..c4c6c66787e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/performance/server/ServerPerformanceSuite.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.performance.server; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ComputeEnginePerfTest.class, + ServerPerfTest.class +}) +public class ServerPerformanceSuite { +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/PluginsSuite.java b/tests/src/test/java/org/sonarqube/tests/plugins/PluginsSuite.java new file mode 100644 index 00000000000..de977606df9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/PluginsSuite.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + PluginsTest.class, +}) +public class PluginsSuite { + +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/PluginsTest.java b/tests/src/test/java/org/sonarqube/tests/plugins/PluginsTest.java new file mode 100644 index 00000000000..45b9262ca3e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/PluginsTest.java @@ -0,0 +1,202 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins; + +import com.google.common.collect.Sets; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.OrchestratorBuilder; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.plugins.checks.AbapCheck; +import org.sonarqube.tests.plugins.checks.CCheck; +import org.sonarqube.tests.plugins.checks.Check; +import org.sonarqube.tests.plugins.checks.CobolCheck; +import org.sonarqube.tests.plugins.checks.CppCheck; +import org.sonarqube.tests.plugins.checks.FlexCheck; +import org.sonarqube.tests.plugins.checks.GroovyCheck; +import org.sonarqube.tests.plugins.checks.JavaCheck; +import org.sonarqube.tests.plugins.checks.JavascriptCheck; +import org.sonarqube.tests.plugins.checks.PhpCheck; +import org.sonarqube.tests.plugins.checks.PliCheck; +import org.sonarqube.tests.plugins.checks.PythonCheck; +import org.sonarqube.tests.plugins.checks.RpgCheck; +import org.sonarqube.tests.plugins.checks.SwiftCheck; +import org.sonarqube.tests.plugins.checks.Validation; +import org.sonarqube.tests.plugins.checks.VbCheck; +import org.sonarqube.tests.plugins.checks.WebCheck; +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +import static com.sonar.orchestrator.locator.FileLocation.byWildcardMavenFilename; +import static org.assertj.core.api.Assertions.fail; + +/** + * Verify that latest releases of the plugins available in update center + * are correctly supported. + */ +public class PluginsTest { + + private static final Set<String> LICENSED_PLUGINS = Sets.newHashSet( + "abap", "cobol", "cpp", "objc", "pli", "plsql", "rpg", + "swift", "vb", "vbnet"); + + private static final List<Check> CHECKS = Arrays.asList( + new AbapCheck(), + new CCheck(), new CppCheck(), + new CobolCheck(), + // FIXME css plugin is temporary disabled as for the moment incompatible with the web plugin + // new CssCheck(), + new FlexCheck(), + new GroovyCheck(), + new JavaCheck(), + new JavascriptCheck(), + new PhpCheck(), + new PliCheck(), + new PythonCheck(), + new RpgCheck(), + new SwiftCheck(), + new VbCheck(), + new WebCheck()); + + private static Orchestrator ORCHESTRATOR; + + @BeforeClass + public static void startServer() { + OrchestratorBuilder builder = Orchestrator.builderEnv() + .setZipFile(byWildcardMavenFilename(new File("../sonar-application/target"), "sonar*.zip").getFile()); + + // FIXME JSON plugin is temporarily disabled as for the moment the github repo doesn't exist anymore installPlugin(builder, "JSON");; + installPlugin(builder, "Sonargraph"); + installPlugin(builder, "abap"); + // FIXME AEM Rules plugin is disabled because it is no more compatible with SonarQube 6.4 (ClassNotFoundException: com.google.common.base.Functions) installPlugin(builder, "aemrules"); + installPlugin(builder, "android"); + installPlugin(builder, "authbitbucket"); + installPlugin(builder, "authgithub"); + installPlugin(builder, "checkstyle"); + installPlugin(builder, "clover"); + installPlugin(builder, "cobol"); + installPlugin(builder, "codecrackercsharp"); + installPlugin(builder, "cpp"); + installPlugin(builder, "csharp"); + // FIXME css plugin is temporarily disabled as for the moment incompatible with the web plugin installPlugin(builder, "css"); + // FIXME erlang plugin is temporarily disabled because it is not compatible with SQ 6.4 until usage of Colorizer API is removed + // FIXME findbugs plugin is temporarily disabled because it is not compatible with SQ 6.4 until usage of Colorizer API is removed + installPlugin(builder, "flex"); + installPlugin(builder, "github"); + installPlugin(builder, "googleanalytics"); + installPlugin(builder, "groovy"); + installPlugin(builder, "java"); + // FIXME javaProperties plugin is temporarily disabled as for the moment the github repo doesn't exist anymore installPlugin(builder, "javaProperties"); + installPlugin(builder, "javascript"); + installPlugin(builder, "jdepend"); + installPlugin(builder, "l10nde"); + installPlugin(builder, "l10nel"); + installPlugin(builder, "l10nes"); + installPlugin(builder, "l10nfr"); + installPlugin(builder, "l10nit"); + installPlugin(builder, "l10nja"); + installPlugin(builder, "l10nko"); + installPlugin(builder, "l10npt"); + installPlugin(builder, "l10nru"); + installPlugin(builder, "l10nzh"); + installPlugin(builder, "ldap"); + installPlugin(builder, "lua"); + installPlugin(builder, "php"); + installPlugin(builder, "pitest"); + installPlugin(builder, "pli"); + installPlugin(builder, "plsql"); + installPlugin(builder, "pmd"); + // FIXME puppet plugin is temporarily disabled because it is not compatible with SQ 6.4 until usage of Colorizer API is removed + installPlugin(builder, "python"); + installPlugin(builder, "rci"); + installPlugin(builder, "rpg"); + installPlugin(builder, "scmclearcase"); + installPlugin(builder, "scmcvs"); + installPlugin(builder, "scmgit"); + installPlugin(builder, "scmjazzrtc"); + installPlugin(builder, "scmmercurial"); + installPlugin(builder, "scmperforce"); + installPlugin(builder, "scmsvn"); + installPlugin(builder, "scmtfvc"); + installPlugin(builder, "softvis3d"); + installPlugin(builder, "sonargraphintegration"); + installPlugin(builder, "status"); + installPlugin(builder, "swift"); + installPlugin(builder, "vb"); + installPlugin(builder, "vbnet"); + installPlugin(builder, "web"); + installPlugin(builder, "xanitizer"); + installPlugin(builder, "xml"); + + activateLicenses(builder); + ORCHESTRATOR = builder.build(); + ORCHESTRATOR.start(); + } + + @Rule + public ErrorCollector errorCollector = new ErrorCollector(); + + @Test + public void analysis_of_project_with_all_supported_languages() { + SonarScanner analysis = newAnalysis(); + BuildResult result = ORCHESTRATOR.executeBuildQuietly(analysis); + if (result.getLastStatus() != 0) { + fail(result.getLogs()); + } + for (Check check : CHECKS) { + System.out.println(check.getClass().getSimpleName() + "..."); + check.validate(new Validation(ORCHESTRATOR, errorCollector)); + } + } + + @Test + public void preview_analysis_of_project_with_all_supported_languages() { + SonarScanner analysis = newAnalysis(); + analysis.setProperty("sonar.analysis.mode", "issues"); + BuildResult result = ORCHESTRATOR.executeBuildQuietly(analysis); + if (result.getLastStatus() != 0) { + fail(result.getLogs()); + } + } + + private static SonarScanner newAnalysis() { + SonarScanner analysis = SonarScanner.create(Project.basedir()); + + // required to bypass usage of build-wrapper + analysis.setProperties("sonar.cfamily.build-wrapper-output.bypass", "true"); + return analysis; + } + + private static void activateLicenses(OrchestratorBuilder builder) { + LICENSED_PLUGINS.forEach(builder::activateLicense); + } + + private static void installPlugin(OrchestratorBuilder builder, String pluginKey) { + builder.setOrchestratorProperty(pluginKey + "Version", "LATEST_RELEASE"); + builder.addPlugin(pluginKey); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/Project.java b/tests/src/test/java/org/sonarqube/tests/plugins/Project.java new file mode 100644 index 00000000000..e6bfdffb015 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/Project.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins; + +import com.google.common.base.Function; +import java.io.File; +import java.util.Collection; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang.StringUtils; +import util.ItUtils; + +import static com.google.common.collect.FluentIterable.from; + +public class Project { + + public static File basedir() { + return ItUtils.projectDir("plugins/project"); + } + + public static Iterable<String> allFilesInDir(final String dirPath) { + Collection<File> files = FileUtils.listFiles(new File(basedir(), dirPath), null, true); + return from(files).transform(new Function<File, String>() { + @Nullable + public String apply(File file) { + // transforms /absolute/path/to/src/java/Foo.java to src/java/Foo.java + String filePath = FilenameUtils.separatorsToUnix(file.getPath()); + return dirPath + StringUtils.substringAfterLast(filePath, dirPath); + } + }); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/VersionPluginTest.java b/tests/src/test/java/org/sonarqube/tests/plugins/VersionPluginTest.java new file mode 100644 index 00000000000..38b91d3f7ac --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/VersionPluginTest.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category3Suite; +import java.io.IOException; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; + +public class VersionPluginTest { + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + private static WsClient wsClient; + + @BeforeClass + public static void init_ws_cient() { + wsClient = newAdminWsClient(orchestrator); + } + + @Before + public void deleteData() throws IOException { + orchestrator.resetData(); + } + + @Test + public void check_functional_version() { + assertThat(wsClient.wsConnector().call(new GetRequest("api/plugins/installed")).content()).contains("1.0.2 (build 42)"); + + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/AbapCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/AbapCheck.java new file mode 100644 index 00000000000..a2fc7080198 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/AbapCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class AbapCheck implements Check { + + public static final String DIR = "src/abap"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/CCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CCheck.java new file mode 100644 index 00000000000..0465a417995 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class CCheck implements Check { + + public static final String DIR = "src/c"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/Check.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/Check.java new file mode 100644 index 00000000000..e85542a6ea7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/Check.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public interface Check { + void validate(Validation validation); +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/CobolCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CobolCheck.java new file mode 100644 index 00000000000..dc23032edde --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CobolCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class CobolCheck implements Check { + + public static final String DIR = "src/cobol"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/CppCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CppCheck.java new file mode 100644 index 00000000000..9cb5359317f --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CppCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class CppCheck implements Check { + + public static final String DIR = "src/cpp"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/CssCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CssCheck.java new file mode 100644 index 00000000000..b2d10039ae2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/CssCheck.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class CssCheck implements Check { + + public static final String DIR = "src/css"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/FlexCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/FlexCheck.java new file mode 100644 index 00000000000..c2443b49363 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/FlexCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class FlexCheck implements Check { + + public static final String DIR = "src/flex"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + validation.mustHaveIssues(DIR + "/HasIssues.as"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/GroovyCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/GroovyCheck.java new file mode 100644 index 00000000000..f8bd3b0d9d5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/GroovyCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class GroovyCheck implements Check { + + public static final String DIR = "src/groovy"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/JavaCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/JavaCheck.java new file mode 100644 index 00000000000..f5c7f3f86fe --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/JavaCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class JavaCheck implements Check { + + public static final String DIR = "src/java"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/JavascriptCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/JavascriptCheck.java new file mode 100644 index 00000000000..40f74d64943 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/JavascriptCheck.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class JavascriptCheck implements Check { + + public static final String SRC_DIR = "src/js"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(SRC_DIR); + validation.mustHaveSize(SRC_DIR); + validation.mustHaveComments(SRC_DIR); + validation.mustHaveComplexity(SRC_DIR); + validation.mustHaveIssues(SRC_DIR + "/HasIssues.js"); + validation.mustHaveMeasuresGreaterThan(SRC_DIR + "/Person.js", 0, "coverage"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/PhpCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/PhpCheck.java new file mode 100644 index 00000000000..f1b5e36aa36 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/PhpCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class PhpCheck implements Check { + + public static final String DIR = "src/php"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/PliCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/PliCheck.java new file mode 100644 index 00000000000..3d08a1baaec --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/PliCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class PliCheck implements Check { + + public static final String DIR = "src/pli"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + validation.mustHaveIssues(DIR + "/hasissues.pli"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/PythonCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/PythonCheck.java new file mode 100644 index 00000000000..d7f901ac817 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/PythonCheck.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +import org.sonarqube.tests.plugins.Project; + +public class PythonCheck implements Check { + + public static final String DIR = "src/python"; + + @Override + public void validate(Validation validation) { + // all files have size measures, even empty __init__.py + validation.mustHaveSize(DIR); + + for (String filePath : Project.allFilesInDir(DIR)) { + if (filePath.endsWith("__init__.py")) { + validation.mustHaveSource(filePath); + } else { + validation.mustHaveNonEmptySource(filePath); + validation.mustHaveComments(filePath); + validation.mustHaveComplexity(filePath); + } + } + + validation.mustHaveIssues(DIR + "/hasissues.py"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/RpgCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/RpgCheck.java new file mode 100644 index 00000000000..eb0010e801f --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/RpgCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class RpgCheck implements Check { + + public static final String DIR = "src/rpg"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/SwiftCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/SwiftCheck.java new file mode 100644 index 00000000000..8360ff253d1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/SwiftCheck.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class SwiftCheck implements Check { + + public static final String DIR = "src/swift"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/Validation.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/Validation.java new file mode 100644 index 00000000000..9b2f9a4ea21 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/Validation.java @@ -0,0 +1,163 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +import com.google.common.base.Joiner; +import com.google.gson.Gson; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.plugins.Project; +import java.io.File; +import java.util.List; +import java.util.Map; +import org.hamcrest.Matchers; +import org.junit.rules.ErrorCollector; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.newAdminWsClient; + +/** + * + * TODO must have syntax highlighting + * TODO must have duplications + * TODO must have issues with debt + * TODO must have tests + * TODO must have coverage + */ +public class Validation { + + private final Orchestrator orchestrator; + private final ErrorCollector errorCollector; + + public Validation(Orchestrator orchestrator, ErrorCollector errorCollector) { + this.orchestrator = orchestrator; + this.errorCollector = errorCollector; + } + + public void mustHaveIssues(String path) { + // TODO use the WS api/issues + mustHaveMeasuresGreaterThan(path, 1, "violations"); + } + + public void mustHaveComments(String path) { + mustHaveMeasuresGreaterThan(path, 0, "comment_lines", "comment_lines_density"); + } + + public void mustHaveComplexity(String path) { + mustHaveMeasuresGreaterThan(path, 0, "complexity"); + } + + public void mustHaveSize(String path) { + mustHaveMeasuresGreaterThan(path, 0, "ncloc", "lines"); + } + + public void mustHaveMeasuresGreaterThan(String path, int min, String... metricKeys) { + for (String filePath : toFiles(path)) { + fileMustHaveMeasures(filePath, metricKeys, min); + } + } + + private void fileMustHaveMeasures(String filePath, String[] metricKeys, int min) { + String componentKey = filePathToKey(filePath); + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, componentKey, metricKeys); + errorCollector.checkThat("Measures " + Joiner.on(",").join(metricKeys) + " are set on file " + filePath, componentKey, notNullValue()); + if (!measures.isEmpty()) { + for (String metricKey : metricKeys) { + Double measure = measures.get(metricKey); + errorCollector.checkThat("Measure " + metricKey + " is set on file " + filePath, measure, notNullValue()); + if (measure != null) { + errorCollector.checkThat("Measure " + metricKey + " is positive on file " + filePath, measure.intValue(), Matchers.greaterThanOrEqualTo(min)); + } + } + } + } + + /** + * Checks that each source file of the given directory is uploaded to server. + * @param path relative path to source directory or source file + */ + public void mustHaveNonEmptySource(String path) { + mustHaveSourceWithAtLeast(path, 1); + } + + public void mustHaveSource(String path) { + mustHaveSourceWithAtLeast(path, 0); + } + + private void mustHaveSourceWithAtLeast(String path, int minLines) { + for (String filePath : toFiles(path)) { + WsResponse response = newAdminWsClient(orchestrator).wsConnector().call(new GetRequest("api/sources/lines").setParam("key", filePathToKey(filePath))); + errorCollector.checkThat("Source is set on file " + filePath, response.isSuccessful(), is(true)); + Sources source = Sources.parse(response.content()); + if (source != null) { + errorCollector.checkThat("Source is empty on file " + filePath, source.getSources().size(), Matchers.greaterThanOrEqualTo(minLines)); + } + } + } + + private Iterable<String> toFiles(String path) { + File fileOrDir = new File(Project.basedir(), path); + if (!fileOrDir.exists()) { + throw new IllegalArgumentException("Path does not exist: " + fileOrDir); + } + if (fileOrDir.isDirectory()) { + return Project.allFilesInDir(path); + } + return asList(path); + } + + private String filePathToKey(String filePath) { + return "all-langs:" + filePath; + } + + public static class Sources { + + private List<Source> sources; + + private Sources(List<Source> sources) { + this.sources = sources; + } + + public List<Source> getSources() { + return sources; + } + + public static Sources parse(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, Sources.class); + } + + public static class Source { + private final String line; + + private Source(String line) { + this.line = line; + } + + public String getLine() { + return line; + } + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/VbCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/VbCheck.java new file mode 100644 index 00000000000..4438277fd49 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/VbCheck.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class VbCheck implements Check { + + public static final String DIR = "src/vb"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + validation.mustHaveComplexity(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/plugins/checks/WebCheck.java b/tests/src/test/java/org/sonarqube/tests/plugins/checks/WebCheck.java new file mode 100644 index 00000000000..8c999254cdd --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/plugins/checks/WebCheck.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.plugins.checks; + +public class WebCheck implements Check { + + public static final String DIR = "src/web"; + + @Override + public void validate(Validation validation) { + validation.mustHaveNonEmptySource(DIR); + validation.mustHaveIssues(DIR); + validation.mustHaveSize(DIR); + validation.mustHaveComments(DIR); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/previewAnalysis/ToDoTest.java b/tests/src/test/java/org/sonarqube/tests/previewAnalysis/ToDoTest.java new file mode 100644 index 00000000000..67122882ba2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/previewAnalysis/ToDoTest.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.previewAnalysis; + +public class ToDoTest { +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/BackgroundTasksTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/BackgroundTasksTest.java new file mode 100644 index 00000000000..0c4e916ebf7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/BackgroundTasksTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.pageobjects.BackgroundTaskItem; +import org.sonarqube.pageobjects.BackgroundTasksPage; +import org.sonarqube.pageobjects.Navigation; +import util.user.UserRule; + +import static com.codeborne.selenide.CollectionCondition.sizeGreaterThan; +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class BackgroundTasksTest { + + private static final String ADMIN_USER_LOGIN = "admin-user"; + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(ORCHESTRATOR); + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + @BeforeClass + public static void beforeClass() { + executeBuild("test-project", "Test Project"); + executeBuild("test-project-2", "Another Test Project"); + } + + @Before + public void before() { + userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); + } + + @After + public void deleteAdminUser() { + userRule.resetUsers(); + } + + @Test + public void should_not_display_failing_and_search_and_filter_elements_on_project_level_page() throws Exception { + runSelenese(ORCHESTRATOR, "/projectAdministration/BackgroundTasksTest/should_not_display_failing_and_search_and_filter_elements_on_project_level_page.html"); + } + + @Test + public void display_scanner_context() { + nav.logIn().submitCredentials(ADMIN_USER_LOGIN); + BackgroundTasksPage page = nav.openBackgroundTasksPage(); + + page.getTasks().shouldHave(sizeGreaterThan(0)); + BackgroundTaskItem task = page.getTasksAsItems().get(0); + task.openActions() + .openScannerContext() + .assertScannerContextContains("SonarQube plugins:") + .assertScannerContextContains("Global properties:"); + } + + @Test + public void display_error_stacktrace() { + Navigation nav = Navigation.create(ORCHESTRATOR); + executeBuild("test-project", "Test Project", "2010-01-01"); + + nav.logIn().submitCredentials(ADMIN_USER_LOGIN); + BackgroundTasksPage page = nav.openBackgroundTasksPage(); + + page.getTasks().shouldHave(sizeGreaterThan(0)); + BackgroundTaskItem task = page.getTasksAsItems().get(0); + task.openActions() + .openErrorStacktrace() + .assertErrorStacktraceContains("Date of analysis cannot be older than the date of the last known analysis"); + } + + private static void executeBuild(String projectKey, String projectName) { + ORCHESTRATOR.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample")) + .setProjectKey(projectKey) + .setProjectName(projectName)); + } + + private static void executeBuild(String projectKey, String projectName, String date) { + ORCHESTRATOR.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample")) + .setProjectKey(projectKey) + .setProjectName(projectName) + .setProperty("sonar.projectDate", date)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/BulkDeletionTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/BulkDeletionTest.java new file mode 100644 index 00000000000..e31020a3700 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/BulkDeletionTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import util.user.UserRule; + +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class BulkDeletionTest { + + private static final String ADMIN_USER_LOGIN = "admin-user"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + @Before + public void deleteData() { + orchestrator.resetData(); + userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); + } + + @After + public void deleteAdminUser() { + userRule.resetUsers(); + } + + /** + * SONAR-2614, SONAR-3805 + */ + @Test + public void test_bulk_deletion_on_selected_projects() throws Exception { + // we must have several projects to test the bulk deletion + executeBuild("cameleon-1", "Sample-Project"); + executeBuild("cameleon-2", "Foo-Application"); + executeBuild("cameleon-3", "Bar-Sonar-Plugin"); + + runSelenese(orchestrator, "/projectAdministration/BulkDeletionTest/bulk-delete-filter-projects.html"); + } + + private void executeBuild(String projectKey, String projectName) { + orchestrator.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample")) + .setProjectKey(projectKey) + .setProjectName(projectName)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectAdministrationTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectAdministrationTest.java new file mode 100644 index 00000000000..7d46eb61e7c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectAdministrationTest.java @@ -0,0 +1,220 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import javax.annotation.Nullable; +import org.apache.commons.lang.time.DateFormatUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.wsclient.SonarClient; +import org.sonar.wsclient.base.HttpException; +import org.sonar.wsclient.user.UserParameters; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.settings.SettingsPage; +import util.user.UserRule; + +import static org.apache.commons.lang.time.DateUtils.addDays; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getComponent; +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class ProjectAdministrationTest { + private static final String DELETE_WS_ENDPOINT = "api/projects/bulk_delete"; + + // take some day in the past + private static final String ANALYSIS_DATE = DateFormatUtils.ISO_DATE_FORMAT.format(addDays(new Date(), -1)); + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private Navigation nav = Navigation.create(orchestrator); + + private static final String PROJECT_KEY = "sample"; + private static final String FILE_KEY = "sample:src/main/xoo/sample/Sample.xoo"; + private String adminUser; + + @Before + public void deleteAnalysisData() throws SQLException { + orchestrator.resetData(); + adminUser = userRule.createAdminUser(); + } + + @Test + public void delete_project_by_web_service() { + scanSampleWithDate(ANALYSIS_DATE); + + assertThat(getComponent(orchestrator, PROJECT_KEY)).isNotNull(); + assertThat(getComponent(orchestrator, FILE_KEY)).isNotNull(); + + orchestrator.getServer().adminWsClient().post(DELETE_WS_ENDPOINT, "keys", PROJECT_KEY); + + assertThat(getComponent(orchestrator, PROJECT_KEY)).isNull(); + assertThat(getComponent(orchestrator, FILE_KEY)).isNull(); + } + + @Test + public void fail_when_trying_to_delete_a_file() { + expectedException.expect(HttpException.class); + scanSampleWithDate(ANALYSIS_DATE); + + assertThat(getComponent(orchestrator, PROJECT_KEY)).isNotNull(); + assertThat(getComponent(orchestrator, FILE_KEY)).isNotNull(); + + // it's forbidden to delete only some files + orchestrator.getServer().adminWsClient().post(DELETE_WS_ENDPOINT, "keys", FILE_KEY); + } + + @Test + public void fail_when_insufficient_privilege() { + expectedException.expect(HttpException.class); + scanSampleWithDate(ANALYSIS_DATE); + + assertThat(getComponent(orchestrator, PROJECT_KEY)).isNotNull(); + + // use wsClient() instead of adminWsClient() + orchestrator.getServer().wsClient().post(DELETE_WS_ENDPOINT, "keys", PROJECT_KEY); + } + + /** + * Test updated for SONAR-3570 and SONAR-5923 + */ + @Test + public void project_deletion() { + String projectAdminUser = "project-deletion-with-admin-permission-on-project"; + SonarClient wsClient = orchestrator.getServer().adminWsClient(); + try { + SonarScanner scan = SonarScanner.create(projectDir("shared/xoo-sample")); + orchestrator.executeBuild(scan); + + // Create user having admin permission on previously analysed project + wsClient.userClient().create( + UserParameters.create().login(projectAdminUser).name(projectAdminUser).password("password").passwordConfirmation("password")); + + wsClient.post("api/permissions/add_user", + "login", projectAdminUser, + "projectKey", "sample", + "permission", "admin"); + + runSelenese(orchestrator, "/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html"); + } finally { + wsClient.userClient().deactivate(projectAdminUser); + } + } + + // SONAR-4203 + @Test + @Ignore("refactor with wsClient") + public void delete_version_of_multimodule_project() { + GregorianCalendar today = new GregorianCalendar(); + SonarScanner build = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")) + .setProperty("sonar.dynamicAnalysis", "false") + .setProperty("sonar.projectDate", (today.get(Calendar.YEAR) - 1) + "-01-01"); + orchestrator.executeBuild(build); + + // The analysis must be run once again to have an history so that it is possible + // to set/delete version on old snapshot + build.setProperty("sonar.projectDate", today.get(Calendar.YEAR) + "-01-01"); + orchestrator.executeBuild(build); + + // There are 7 modules + assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(1); + + runSelenese(orchestrator, "/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-modify-version.html"); + + assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(2); + + runSelenese(orchestrator, "/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-delete-version.html"); + + assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(1); + } + + @Test + public void display_project_settings() throws UnsupportedEncodingException { + scanSample(null, null); + + SettingsPage page = nav.logIn().submitCredentials(adminUser).openSettings("sample") + .assertMenuContains("Analysis Scope") + .assertMenuContains("Category 1") + .assertMenuContains("DEV") + .assertMenuContains("project-only") + .assertMenuContains("Xoo") + .assertSettingDisplayed("sonar.dbcleaner.daysBeforeDeletingClosedIssues"); + + page.openCategory("project-only") + .assertSettingDisplayed("prop_only_on_project"); + + page.openCategory("General") + .assertStringSettingValue("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "30") + .assertStringSettingValue("sonar.leak.period", "previous_version") + .assertBooleanSettingValue("sonar.dbcleaner.cleanDirectory", true) + .setStringValue("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "1") + .assertStringSettingValue("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "1"); + } + + @Test + public void display_module_settings() throws UnsupportedEncodingException { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + nav.logIn().submitCredentials(adminUser) + .openSettings("com.sonarsource.it.samples:multi-modules-sample:module_a") + .assertMenuContains("Analysis Scope") + .assertSettingDisplayed("sonar.coverage.exclusions"); + } + + private void scanSampleWithDate(String date) { + scanSample(date, null); + } + + private void scanSample(@Nullable String date, @Nullable String profile) { + SonarScanner scan = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.cpd.exclusions", "**/*"); + if (date != null) { + scan.setProperty("sonar.projectDate", date); + } + if (profile != null) { + scan.setProfile(profile); + } + orchestrator.executeBuild(scan); + } + + private int count(String condition) { + return orchestrator.getDatabase().countSql("select count(1) from " + condition); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java new file mode 100644 index 00000000000..a1d7f61aba1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java @@ -0,0 +1,182 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectKeyPage; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.WebDriverRunner.url; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +public class ProjectKeyPageTest { + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR; + + private static WsClient wsClient; + + @BeforeClass + public static void setUp() { + wsClient = newAdminWsClient(ORCHESTRATOR); + } + + @Before + public void cleanUp() { + ORCHESTRATOR.resetData(); + } + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + @Test + public void change_key_when_no_modules() { + createProject("sample"); + + ProjectKeyPage page = openPage("sample"); + page.assertSimpleUpdate().trySimpleUpdate("another"); + + assertThat(url()).endsWith("/project/key?id=another"); + } + + @Test + public void fail_to_change_key_when_no_modules() { + createProject("sample"); + createProject("another"); + + ProjectKeyPage page = openPage("sample"); + page.assertSimpleUpdate().trySimpleUpdate("another"); + + $(".alert.alert-danger").shouldBe(visible); + assertThat(url()).endsWith("/project/key?id=sample"); + } + + @Test + public void change_key_of_multi_modules_project() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + + ProjectKeyPage page = openPage("sample"); + page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another"); + + assertThat(url()).endsWith("/project/key?id=another"); + } + + @Test + public void fail_to_change_key_of_multi_modules_project() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + createProject("another"); + + ProjectKeyPage page = openPage("sample"); + page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another"); + + $(".alert.alert-danger").shouldBe(visible); + assertThat(url()).endsWith("/project/key?id=sample"); + } + + @Test + public void change_key_of_module_of_multi_modules_project() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + + ProjectKeyPage page = openPage("sample"); + page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another"); + + $("#update-key-confirmation-form").shouldNotBe(visible); + + nav.openProjectKey("another"); + assertThat(url()).endsWith("/project/key?id=another"); + } + + @Test + public void fail_to_change_key_of_module_of_multi_modules_project() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + createProject("another"); + + ProjectKeyPage page = openPage("sample"); + page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another"); + + $(".alert.alert-danger").shouldBe(visible); + } + + @Test + public void bulk_change() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + + ProjectKeyPage page = openPage("sample"); + page.assertBulkChange().simulateBulkChange("sample", "another"); + + $("#bulk-update-results").shouldBe(visible); + page.assertBulkChangeSimulationResult("sample", "another") + .assertBulkChangeSimulationResult("sample:module_a:module_a1", "another:module_a:module_a1"); + + page.confirmBulkUpdate().assertSuccessfulBulkUpdate(); + } + + @Test + public void fail_to_bulk_change_because_no_changed_key() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + + ProjectKeyPage page = openPage("sample"); + page.assertBulkChange().simulateBulkChange("random", "another"); + + $("#bulk-update-nothing").shouldBe(visible); + $("#bulk-update-results").shouldNotBe(visible); + } + + @Test + public void fail_to_bulk_change_because_of_duplications() { + analyzeProject("shared/xoo-multi-modules-sample", "sample"); + + ProjectKeyPage page = openPage("sample"); + page.assertBulkChange().simulateBulkChange("module_a1", "module_a2"); + + $("#bulk-update-duplicate").shouldBe(visible); + $("#bulk-update-results").shouldBe(visible); + + page.assertBulkChangeSimulationResult("sample:module_a:module_a1", "sample:module_a:module_a2") + .assertDuplicated("sample:module_a:module_a1"); + } + + private ProjectKeyPage openPage(String projectKey) { + nav.logIn().submitCredentials("admin", "admin"); + return nav.openProjectKey(projectKey); + } + + private static void createProject(String projectKey) { + wsClient.wsConnector().call(new PostRequest("api/projects/create") + .setParam("key", projectKey) + .setParam("name", projectKey)); + } + + private static void analyzeProject(String path, String projectKey) { + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path)) + .setProjectKey(projectKey)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectLinksPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectLinksPageTest.java new file mode 100644 index 00000000000..5cb04c3b4e8 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectLinksPageTest.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.codeborne.selenide.Condition; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.WsProjectLinks.CreateWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.projectlinks.CreateWsRequest; +import org.sonarqube.ws.client.projectlinks.DeleteWsRequest; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectLinkItem; +import org.sonarqube.pageobjects.ProjectLinksPage; +import util.user.UserRule; + +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +public class ProjectLinksPageTest { + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR; + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + @Rule + public UserRule userRule = UserRule.from(ORCHESTRATOR); + + private static WsClient wsClient; + private long customLinkId; + private String adminUser; + + @BeforeClass + public static void setUp() { + wsClient = newAdminWsClient(ORCHESTRATOR); + + ORCHESTRATOR.resetData(); + ORCHESTRATOR.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.links.homepage", "http://example.com")); + } + + @Before + public void prepare() { + customLinkId = Long.parseLong(createCustomLink().getLink().getId()); + adminUser = userRule.createAdminUser(); + } + + @After + public void clean() { + deleteLink(customLinkId); + } + + @Test + public void should_list_links() { + ProjectLinksPage page = openPage(); + + page.getLinks().shouldHaveSize(2); + + List<ProjectLinkItem> links = page.getLinksAsItems(); + ProjectLinkItem homepageLink = links.get(0); + ProjectLinkItem customLink = links.get(1); + + homepageLink.getName().should(hasText("Home")); + homepageLink.getType().should(hasText("sonar.links.homepage")); + homepageLink.getUrl().should(hasText("http://example.com")); + homepageLink.getDeleteButton().shouldNot(Condition.present); + + customLink.getName().should(hasText("Custom")); + customLink.getType().shouldNot(Condition.present); + customLink.getUrl().should(hasText("http://example.org/custom")); + customLink.getDeleteButton().shouldBe(Condition.visible); + } + + @Test + public void should_create_link() { + ProjectLinksPage page = openPage(); + + page.getLinks().shouldHaveSize(2); + + $("#create-project-link").click(); + $("#create-link-name").setValue("Test"); + $("#create-link-url").setValue("http://example.com/test"); + $("#create-link-confirm").click(); + + page.getLinks().shouldHaveSize(3); + + ProjectLinkItem testLink = page.getLinksAsItems().get(2); + + testLink.getName().should(hasText("Test")); + testLink.getType().shouldNot(Condition.present); + testLink.getUrl().should(hasText("http://example.com/test")); + testLink.getDeleteButton().shouldBe(Condition.visible); + } + + @Test + public void should_delete_link() { + ProjectLinksPage page = openPage(); + + page.getLinks().shouldHaveSize(2); + + List<ProjectLinkItem> links = page.getLinksAsItems(); + ProjectLinkItem customLink = links.get(1); + + customLink.getDeleteButton().click(); + $("#delete-link-confirm").click(); + + page.getLinks().shouldHaveSize(1); + } + + private CreateWsResponse createCustomLink() { + return wsClient.projectLinks().create(new CreateWsRequest() + .setProjectKey("sample") + .setName("Custom") + .setUrl("http://example.org/custom")); + } + + private void deleteLink(long id) { + try { + wsClient.projectLinks().delete(new DeleteWsRequest().setId(id)); + } catch (Exception e) { + // fail silently + } + } + + private ProjectLinksPage openPage() { + nav.logIn().submitCredentials(adminUser, adminUser); + return nav.openProjectLinks("sample"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectPermissionsTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectPermissionsTest.java new file mode 100644 index 00000000000..2184a9a9506 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectPermissionsTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectPermissionsPage; +import util.user.UserRule; + +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class ProjectPermissionsTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private Navigation nav = Navigation.create(orchestrator); + private String adminUser; + + @BeforeClass + public static void beforeClass() { + executeBuild("project-permissions-project", "Test Project"); + executeBuild("project-permissions-project-2", "Another Test Project"); + } + + @Before + public void before() { + adminUser = userRule.createAdminUser(); + } + + @Test + public void test_project_permissions_page_shows_only_single_project() throws Exception { + runSelenese(orchestrator, "/projectAdministration/ProjectPermissionsTest/test_project_permissions_page_shows_only_single_project.html"); + } + + @Test + public void change_project_visibility() { + ProjectPermissionsPage page = nav.logIn().submitCredentials(adminUser).openProjectPermissions("project-permissions-project"); + page + .shouldBePublic() + .turnToPrivate() + .turnToPublic(); + } + + private static void executeBuild(String projectKey, String projectName) { + orchestrator.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample")) + .setProjectKey(projectKey) + .setProjectName(projectName) + ); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectQualityGatePageTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectQualityGatePageTest.java new file mode 100644 index 00000000000..9fd03897d08 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectQualityGatePageTest.java @@ -0,0 +1,147 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.sonar.wsclient.qualitygate.QualityGate; +import org.sonar.wsclient.qualitygate.QualityGateClient; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.qualitygate.SelectWsRequest; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectQualityGatePage; + +import static util.ItUtils.newAdminWsClient; + +public class ProjectQualityGatePageTest { + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR; + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + private static WsClient wsClient; + + @BeforeClass + public static void prepare() { + wsClient = newAdminWsClient(ORCHESTRATOR); + } + + @Before + public void setUp() { + ORCHESTRATOR.resetData(); + + wsClient.wsConnector().call(new PostRequest("api/projects/create") + .setParam("name", "Sample") + .setParam("key", "sample")); + } + + @Test + public void should_display_default() { + QualityGate customQualityGate = createCustomQualityGate("should_display_default"); + qualityGateClient().setDefault(customQualityGate.id()); + + ProjectQualityGatePage page = openPage(); + SelenideElement selectedQualityGate = page.getSelectedQualityGate(); + selectedQualityGate.should(Condition.hasText("Default")); + selectedQualityGate.should(Condition.hasText(customQualityGate.name())); + } + + @Test + public void should_display_custom() { + QualityGate customQualityGate = createCustomQualityGate("should_display_custom"); + associateWithQualityGate(customQualityGate); + + ProjectQualityGatePage page = openPage(); + SelenideElement selectedQualityGate = page.getSelectedQualityGate(); + selectedQualityGate.shouldNot(Condition.hasText("Default")); + selectedQualityGate.should(Condition.hasText(customQualityGate.name())); + } + + @Test + public void should_display_none() { + qualityGateClient().unsetDefault(); + + ProjectQualityGatePage page = openPage(); + page.assertNotSelected(); + } + + @Test + public void should_set_custom() { + QualityGate customQualityGate = createCustomQualityGate("should_set_custom"); + + ProjectQualityGatePage page = openPage(); + page.setQualityGate(customQualityGate.name()); + + SelenideElement selectedQualityGate = page.getSelectedQualityGate(); + selectedQualityGate.should(Condition.hasText(customQualityGate.name())); + } + + @Test + public void should_set_default() { + QualityGate customQualityGate = createCustomQualityGate("should_set_default"); + qualityGateClient().setDefault(customQualityGate.id()); + + ProjectQualityGatePage page = openPage(); + page.setQualityGate(customQualityGate.name()); + + SelenideElement selectedQualityGate = page.getSelectedQualityGate(); + selectedQualityGate.should(Condition.hasText("Default")); + selectedQualityGate.should(Condition.hasText(customQualityGate.name())); + } + + @Test + @Ignore("find a way to select None") + public void should_set_none() { + qualityGateClient().unsetDefault(); + QualityGate customQualityGate = createCustomQualityGate("should_set_none"); + associateWithQualityGate(customQualityGate); + + ProjectQualityGatePage page = openPage(); + page.setQualityGate(""); + + page.assertNotSelected(); + } + + private ProjectQualityGatePage openPage() { + nav.logIn().submitCredentials("admin", "admin"); + return nav.openProjectQualityGate("sample"); + } + + private static QualityGate createCustomQualityGate(String name) { + return qualityGateClient().create(name); + } + + private void associateWithQualityGate(QualityGate qualityGate) { + wsClient.qualityGates().associateProject(new SelectWsRequest().setProjectKey("sample").setGateId(qualityGate.id())); + } + + private static QualityGateClient qualityGateClient() { + return ORCHESTRATOR.getServer().adminWsClient().qualityGateClient(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectVisibilityTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectVisibilityTest.java new file mode 100644 index 00000000000..45146bbe410 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectVisibilityTest.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectAdministration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.sql.SQLException; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.WsComponents; +import org.sonarqube.ws.client.component.SearchProjectsRequest; +import org.sonarqube.ws.client.permission.RemoveGroupWsRequest; +import org.sonarqube.ws.client.project.UpdateVisibilityRequest; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectsManagementPage; +import util.user.UserRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +public class ProjectVisibilityTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private Navigation nav = Navigation.create(orchestrator); + + private String adminUser; + + @Before + public void initData() throws SQLException { + orchestrator.resetData(); + adminUser = userRule.createAdminUser(); + } + + @Test + public void return_all_projects_even_when_no_permission() throws Exception { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperties("sonar.projectKey", "sample1")); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperties("sonar.projectKey", "sample2")); + newAdminWsClient(orchestrator).projects().updateVisibility(UpdateVisibilityRequest.builder().setProject("sample2").setVisibility("private").build()); + // Remove 'Admin' permission for admin group on project 2 -> No one can access or admin this project, expect System Admin + newAdminWsClient(orchestrator).permissions().removeGroup(new RemoveGroupWsRequest().setProjectKey("sample2").setGroupName("sonar-administrators").setPermission("admin")); + + nav.logIn().submitCredentials(adminUser).openProjectsManagement() + .shouldHaveProject("sample1") + .shouldHaveProject("sample2"); + } + + @Test + public void create_public_project() { + createProjectAndVerify("public"); + } + + @Test + public void create_private_project() { + createProjectAndVerify("private"); + } + + private void createProjectAndVerify(String visibility) { + ProjectsManagementPage page = nav.logIn().submitCredentials(adminUser, adminUser).openProjectsManagement(); + page + .shouldHaveProjectsCount(0) + .createProject("foo", "foo", visibility) + .shouldHaveProjectsCount(1); + + WsComponents.SearchProjectsWsResponse response = newAdminWsClient(orchestrator).components().searchProjects( + SearchProjectsRequest.builder().build()); + assertThat(response.getComponentsCount()).isEqualTo(1); + assertThat(response.getComponents(0).getKey()).isEqualTo("foo"); + assertThat(response.getComponents(0).getName()).isEqualTo("foo"); + assertThat(response.getComponents(0).getVisibility()).isEqualTo(visibility); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectEvent/EventTest.java b/tests/src/test/java/org/sonarqube/tests/projectEvent/EventTest.java new file mode 100644 index 00000000000..4941929515c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectEvent/EventTest.java @@ -0,0 +1,97 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectEvent; + +import com.google.common.collect.Lists; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.sonar.wsclient.services.Event; +import org.sonar.wsclient.services.EventQuery; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsConnector; +import org.sonarqube.ws.client.WsResponse; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +@Ignore("refactor using wsClient") +public class EventTest { + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Before + public void setUp() throws Exception { + orchestrator.resetData(); + } + + @Test + public void old_ws_events_does_not_allow_creating_events_on_modules() { + SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")); + orchestrator.executeBuild(sampleProject); + + WsConnector wsConnector = ItUtils.newAdminWsClient(orchestrator).wsConnector(); + WsResponse response = wsConnector.call(newCreateEventRequest("com.sonarsource.it.samples:multi-modules-sample", "bar")); + assertThat(response.code()) + .isEqualTo(200); + + assertThat(wsConnector.call(newCreateEventRequest("com.sonarsource.it.samples:multi-modules-sample:module_a", "bar")).code()) + .isEqualTo(400); + } + + private static PostRequest newCreateEventRequest(String componentKey, String eventName) { + return new PostRequest("/api/events") + .setParam("resource", componentKey) + .setParam("name", eventName) + .setParam("category", "Foo"); + } + + /** + * SONAR-3308 + */ + @Test + public void keep_only_one_event_per_version_in_project_history() throws Exception { + // first analyse the 1.0-SNAPSHOT version + executeAnalysis(); + // then analyse the 1.0 version + executeAnalysis("sonar.projectVersion", "1.0"); + // and do this all over again + executeAnalysis(); + executeAnalysis("sonar.projectVersion", "1.0"); + + // there should be only 1 "0.1-SNAPSHOT" event and only 1 "0.1" event + List<Event> events = orchestrator.getServer().getWsClient().findAll(new EventQuery().setResourceKey("sample")); + assertThat(events.size()).isEqualTo(2); + List<String> eventNames = Lists.newArrayList(events.get(0).getName(), events.get(1).getName()); + assertThat(eventNames).contains("1.0", "1.0-SNAPSHOT"); + } + + private static void executeAnalysis(String... properties) { + SonarScanner sampleProject = SonarScanner.create(projectDir("shared/xoo-sample")).setProperties(properties); + orchestrator.executeBuild(sampleProject); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectEvent/ProjectActivityPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectEvent/ProjectActivityPageTest.java new file mode 100644 index 00000000000..5a10c83e607 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectEvent/ProjectActivityPageTest.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectEvent; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectActivityPage; +import org.sonarqube.pageobjects.ProjectAnalysisItem; +import util.user.UserRule; + +import static util.ItUtils.projectDir; + +public class ProjectActivityPageTest { + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category4Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(ORCHESTRATOR); + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + @Before + public void setUp() throws Exception { + ORCHESTRATOR.resetData(); + } + + @Test + public void should_list_snapshots() { + analyzeProject("shared/xoo-history-v1", "2014-10-19"); + analyzeProject("shared/xoo-history-v2", "2014-11-13"); + + ProjectActivityPage page = openPage(); + page.getAnalyses().shouldHaveSize(2); + + List<ProjectAnalysisItem> analyses = page.getAnalysesAsItems(); + analyses.get(0) + .shouldHaveEventWithText("1.0-SNAPSHOT") + .shouldNotHaveDeleteButton(); + + analyses.get(1) + .shouldHaveEventWithText("0.9-SNAPSHOT") + .shouldHaveDeleteButton(); + } + + @Test + public void add_change_delete_custom_event() { + analyzeProject(); + openPage().getLastAnalysis() + .addCustomEvent("foo") + .changeLastEvent("bar") + .deleteLastEvent(); + } + + @Test + public void delete_analysis() { + analyzeProject(); + analyzeProject(); + openPage().getFirstAnalysis().delete(); + } + + private ProjectActivityPage openPage() { + String userAdmin = userRule.createAdminUser(); + nav.logIn().submitCredentials(userAdmin, userAdmin); + return nav.openProjectActivity("sample"); + } + + private static void analyzeProject() { + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + } + + private static void analyzeProject(String path, String date) { + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path)).setProperties("sonar.projectDate", date)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectSearch/LeakProjectsPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectSearch/LeakProjectsPageTest.java new file mode 100644 index 00000000000..78c56f8bea2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectSearch/LeakProjectsPageTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectSearch; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category6Suite; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.pageobjects.projects.ProjectsPage; + +import static com.codeborne.selenide.WebDriverRunner.url; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newProjectKey; +import static util.ItUtils.projectDir; +import static util.ItUtils.resetSettings; +import static util.ItUtils.restoreProfile; +import static util.ItUtils.setServerProperty; + +public class LeakProjectsPageTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + private Organization organization; + + @BeforeClass + public static void beforeClass() { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + } + + @AfterClass + public static void tearDown() { + resetSettings(orchestrator, null, "sonar.leak.period"); + } + + @Before + public void setUp() { + organization = tester.organizations().generate(); + restoreProfile(orchestrator, SearchProjectsTest.class.getResource("/projectSearch/SearchProjectsTest/with-many-rules.xml"), organization.getKey()); + } + + @Test + public void should_display_leak_information() { + // This project has 0% duplication on new code + String projectKey2 = newProjectKey(); + analyzeProject(projectKey2, "projectSearch/xoo-history-v1", "2016-12-31"); + analyzeProject(projectKey2, "projectSearch/xoo-history-v2", null); + + // This project has no duplication on new code + String projectKey1 = newProjectKey(); + analyzeProject(projectKey1, "shared/xoo-sample", "2016-12-31"); + analyzeProject(projectKey1, "shared/xoo-sample", null); + + // Check the facets and project cards + ProjectsPage page = tester.openBrowser().openProjects(organization.getKey()); + page.changePerspective("Leak"); + assertThat(url()).endsWith("/projects?view=leak"); + page.shouldHaveTotal(2); + page.getProjectByKey(projectKey2) + .shouldHaveMeasure("new_reliability_rating", "0A") + .shouldHaveMeasure("new_security_rating", "0A") + .shouldHaveMeasure("new_maintainability_rating", "17A") + .shouldHaveMeasure("new_coverage", "–") + .shouldHaveMeasure("new_duplicated_lines_density", "0.0%") + .shouldHaveMeasure("new_lines", "17"); + page.getFacetByProperty("new_duplications") + .shouldHaveValue("1", "1") + .shouldHaveValue("2", "0") + .shouldHaveValue("3", "0") + .shouldHaveValue("4", "0") + .shouldHaveValue("5", "0") + .shouldHaveValue("6", "1"); + } + + private void analyzeProject(String projectKey, String relativePath, @Nullable String analysisDate) { + List<String> keyValueProperties = new ArrayList<>(asList( + "sonar.projectKey", projectKey, + "sonar.organization", organization.getKey(), + "sonar.profile", "with-many-rules", + "sonar.login", "admin", "sonar.password", "admin", + "sonar.scm.disabled", "false")); + if (analysisDate != null) { + keyValueProperties.add("sonar.projectDate"); + keyValueProperties.add(analysisDate); + } + orchestrator.executeBuild(SonarScanner.create(projectDir(relativePath), keyValueProperties.toArray(new String[0]))); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectSearch/ProjectsPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectSearch/ProjectsPageTest.java new file mode 100644 index 00000000000..dde5ace24cb --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectSearch/ProjectsPageTest.java @@ -0,0 +1,203 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectSearch; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.project.DeleteRequest; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.projects.ProjectsPage; + +import static com.codeborne.selenide.Selenide.clearBrowserLocalStorage; +import static com.codeborne.selenide.WebDriverRunner.url; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class ProjectsPageTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + private static final String PROJECT_KEY = "key-foo"; + private static Tester tester = new Tester(orchestrator).disableOrganizations(); + + @ClassRule + public static RuleChain ruleChain = RuleChain.outerRule(orchestrator) + .around(tester); + + @BeforeClass + public static void setUp() { + orchestrator.resetData(); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProjectKey(PROJECT_KEY)); + orchestrator.executeBuild(SonarScanner.create(projectDir("duplications/file-duplications")).setProjectKey("key-bar")); + } + + @AfterClass + public static void tearDown() { + tester.wsClient().projects().delete(DeleteRequest.builder().setKey(PROJECT_KEY).build()); + tester.wsClient().projects().delete(DeleteRequest.builder().setKey("key-bar").build()); + } + + @Before + public void before() { + clearBrowserLocalStorage(); + } + + @Test + public void should_display_projects() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.shouldHaveTotal(2); + page.getProjectByKey(PROJECT_KEY) + .shouldHaveMeasure("reliability_rating", "A") + .shouldHaveMeasure("security_rating", "A") + .shouldHaveMeasure("sqale_rating", "A") + .shouldHaveMeasure("duplicated_lines_density", "0.0%") + .shouldHaveMeasure("ncloc", "13") + .shouldHaveMeasure("ncloc", "Xoo"); + } + + @Test + public void should_display_facets() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.getFacetByProperty("duplications") + .shouldHaveValue("1", "1") + .shouldHaveValue("2", "1") + .shouldHaveValue("3", "1") + .shouldHaveValue("4", "1") + .shouldHaveValue("5", "1") + .shouldHaveValue("6", "0"); + } + + @Test + public void should_filter_using_facet() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.shouldHaveTotal(2); + page.getFacetByProperty("duplications").selectValue("3"); + page.shouldHaveTotal(1); + } + + @Test + public void should_open_default_page() { + // default page can be "All Projects" or "Favorite Projects" depending on your last choice + Navigation nav = tester.openBrowser(); + ProjectsPage page = nav.openProjects(); + + // all projects for anonymous user with default sorting to analysis date + page.shouldHaveTotal(2).shouldDisplayAllProjectsWidthSort("-analysis_date"); + + // all projects by default for logged in user + WsUsers.CreateWsResponse.User administrator = tester.users().generateAdministrator(); + page = nav.logIn().submitCredentials(administrator.getLogin()).openProjects(); + page.shouldHaveTotal(2).shouldDisplayAllProjects(); + + // favorite one project + WsClient administratorWsClient = tester.as(administrator.getLogin()).wsClient(); + administratorWsClient.favorites().add(PROJECT_KEY); + page = nav.openProjects(); + page.shouldHaveTotal(1).shouldDisplayFavoriteProjects(); + + // un-favorite this project + administratorWsClient.favorites().remove(PROJECT_KEY); + page = nav.openProjects(); + page.shouldHaveTotal(2).shouldDisplayAllProjects(); + + // select favorite + page.selectFavoriteProjects(); + page = nav.openProjects(); + page.shouldHaveTotal(0).shouldDisplayFavoriteProjects(); + + // select all + page.selectAllProjects(); + page = nav.openProjects(); + page.shouldHaveTotal(2).shouldDisplayAllProjects(); + } + + @Test + public void should_add_language_to_facet() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.getFacetByProperty("languages") + .selectOptionItem("xoo2") + .shouldHaveValue("xoo2", "0"); + } + + @Test + public void should_add_tag_to_facet() { + // Add some tags to this project + tester.wsClient().wsConnector().call( + new PostRequest("api/project_tags/set") + .setParam("project", PROJECT_KEY) + .setParam("tags", "aa,bb,cc,dd,ee,ff,gg,hh,ii,jj,zz")); + + ProjectsPage page = tester.openBrowser().openProjects(); + page.getFacetByProperty("tags") + .shouldHaveValue("aa", "1") + .shouldHaveValue("ii", "1") + .selectOptionItem("zz") + .shouldHaveValue("zz", "1"); + } + + @Test + public void should_switch_between_perspectives() { + WsUsers.CreateWsResponse.User administrator = tester.users().generateAdministrator(); + ProjectsPage page = tester.openBrowser() + .logIn().submitCredentials(administrator.getLogin()) + .openProjects(); + page.changePerspective("Risk"); + assertThat(url()).endsWith("/projects?view=visualizations&visualization=risk"); + page.changePerspective("Leak"); + assertThat(url()).endsWith("/projects?view=leak"); + } + + @Test + public void should_sort_by_facet() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.sortProjects("Duplications"); + page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "63.7%"); + page.invertSorting(); + page.getProjectByIdx(0).shouldHaveMeasure("duplicated_lines_density", "0.0%"); + } + + @Test + public void should_search_for_project() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.searchProject("s").shouldHaveTotal(2); + page.searchProject("sam").shouldHaveTotal(1); + } + + @Test + public void should_search_for_project_and_keep_other_filters() { + ProjectsPage page = tester.openBrowser().openProjects(); + page.shouldHaveTotal(2); + page.getFacetByProperty("duplications").selectValue("3"); + page.shouldHaveTotal(1); + page.searchProject("sample").shouldHaveTotal(0); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/projectSearch/SearchProjectsTest.java b/tests/src/test/java/org/sonarqube/tests/projectSearch/SearchProjectsTest.java new file mode 100644 index 00000000000..8383935cab8 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectSearch/SearchProjectsTest.java @@ -0,0 +1,306 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.projectSearch; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category6Suite; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.groups.Tuple; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Common; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.WsComponents.Component; +import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse; +import org.sonarqube.ws.client.component.SearchProjectsRequest; +import org.sonarqube.ws.client.project.CreateRequest; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static util.ItUtils.concat; +import static util.ItUtils.newProjectKey; +import static util.ItUtils.projectDir; +import static util.ItUtils.restoreProfile; +import static util.ItUtils.sanitizeTimezones; +import static util.ItUtils.setServerProperty; + +/** + * Tests WS api/components/search_projects + */ +public class SearchProjectsTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + private Organization organization; + + @Before + public void setUp() { + organization = tester.organizations().generate(); + restoreProfile(orchestrator, SearchProjectsTest.class.getResource("/projectSearch/SearchProjectsTest/with-many-rules.xml"), organization.getKey()); + } + + @Test + public void filter_projects_by_measure_values() throws Exception { + String projectKey = newProjectKey(); + analyzeProject(projectKey, "shared/xoo-sample"); + + verifyFilterMatches(projectKey, "ncloc > 1"); + verifyFilterMatches(projectKey, "ncloc > 1 and duplicated_lines_density <= 100"); + verifyFilterDoesNotMatch("ncloc <= 1"); + } + + @Test + public void find_projects_with_no_data() throws Exception { + String projectKey = newProjectKey(); + analyzeProject(projectKey, "shared/xoo-sample"); + + verifyFilterMatches(projectKey, "coverage = NO_DATA"); + verifyFilterDoesNotMatch("ncloc = NO_DATA"); + } + + @Test + public void provisioned_projects_should_be_included_to_results() throws Exception { + String projectKey = newProjectKey(); + tester.wsClient().projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organization.getKey()).build()); + + SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organization.getKey()).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(projectKey); + } + + @Test + public void return_leak_period_date() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_version"); + // This project has a leak period + String projectKey1 = newProjectKey(); + analyzeProject(projectKey1, "shared/xoo-sample", "sonar.projectDate", "2016-12-31"); + analyzeProject(projectKey1, "shared/xoo-sample"); + // This project has only one analysis, so no leak period + String projectKey2 = newProjectKey(); + analyzeProject(projectKey2, "shared/xoo-sample"); + // This project is provisioned, so has no leak period + String projectKey3 = newProjectKey(); + tester.wsClient().projects().create(CreateRequest.builder().setKey(projectKey3).setName(projectKey3).setOrganization(organization.getKey()).build()); + + SearchProjectsWsResponse response = searchProjects( + SearchProjectsRequest.builder().setAdditionalFields(singletonList("leakPeriodDate")).setOrganization(organization.getKey()).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey, Component::hasLeakPeriodDate) + .containsOnly( + tuple(projectKey1, true), + tuple(projectKey2, false), + tuple(projectKey3, false)); + Component project1 = response.getComponentsList().stream().filter(component -> component.getKey().equals(projectKey1)).findFirst() + .orElseThrow(() -> new IllegalStateException("Project1 is not found")); + assertThat(sanitizeTimezones(project1.getLeakPeriodDate())).isEqualTo("2016-12-31T00:00:00+0000"); + } + + @Test + public void filter_by_text_query() throws IOException { + analyzeProject("project1", "shared/xoo-sample", "sonar.projectName", "apachee"); + analyzeProject("project2", "shared/xoo-sample", "sonar.projectName", "Apache"); + analyzeProject("project3", "shared/xoo-multi-modules-sample", "sonar.projectName", "Apache Foundation"); + analyzeProject("project4", "shared/xoo-multi-modules-sample", "sonar.projectName", "Windows"); + + // Search only by text query + assertThat(searchProjects("query = \"apache\"").getComponentsList()).extracting(Component::getKey).containsExactly("project2", "project3", "project1"); + assertThat(searchProjects("query = \"pAch\"").getComponentsList()).extracting(Component::getKey).containsExactly("project2", "project3", "project1"); + assertThat(searchProjects("query = \"hee\"").getComponentsList()).extracting(Component::getKey).containsExactly("project1"); + assertThat(searchProjects("query = \"project1\"").getComponentsList()).extracting(Component::getKey).containsExactly("project1"); + assertThat(searchProjects("query = \"unknown\"").getComponentsList()).isEmpty(); + + // Search by metric criteria and text query + assertThat(searchProjects(SearchProjectsRequest.builder().setFilter("query = \"pAch\" AND ncloc > 50").build()).getComponentsList()) + .extracting(Component::getKey).containsExactly("project3"); + assertThat(searchProjects(SearchProjectsRequest.builder().setFilter("query = \"nd\" AND ncloc > 50").build()).getComponentsList()) + .extracting(Component::getKey).containsExactly("project3", "project4"); + assertThat(searchProjects(SearchProjectsRequest.builder().setFilter("query = \"unknown\" AND ncloc > 50").build()).getComponentsList()).isEmpty(); + ; + + // Check facets + assertThat(searchProjects(SearchProjectsRequest.builder().setFilter("query = \"apache\"").setFacets(singletonList("ncloc")).build()).getFacets().getFacets(0).getValuesList()) + .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) + .containsOnly(tuple("*-1000.0", 3L), tuple("1000.0-10000.0", 0L), tuple("10000.0-100000.0", 0L), tuple("100000.0-500000.0", 0L), tuple("500000.0-*", 0L)); + assertThat(searchProjects(SearchProjectsRequest.builder().setFilter("query = \"unknown\"").setFacets(singletonList("ncloc")).build()).getFacets().getFacets(0) + .getValuesList()).extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) + .containsOnly(tuple("*-1000.0", 0L), tuple("1000.0-10000.0", 0L), tuple("10000.0-100000.0", 0L), tuple("100000.0-500000.0", 0L), tuple("500000.0-*", 0L)); + } + + @Test + public void should_return_facets() throws Exception { + analyzeProject(newProjectKey(), "shared/xoo-sample"); + analyzeProject(newProjectKey(), "shared/xoo-multi-modules-sample"); + + SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organization.getKey()).setFacets(asList( + "alert_status", + "coverage", + "duplicated_lines_density", + "languages", + "ncloc", + "reliability_rating", + "security_rating", + "sqale_rating", + "tags")).build()); + + checkFacet(response, "alert_status", + tuple("OK", 2L), + tuple("WARN", 0L), + tuple("ERROR", 0L)); + checkFacet(response, "coverage", + tuple("NO_DATA", 2L), + tuple("*-30.0", 0L), + tuple("30.0-50.0", 0L), + tuple("50.0-70.0", 0L), + tuple("70.0-80.0", 0L), + tuple("80.0-*", 0L)); + checkFacet(response, "duplicated_lines_density", + tuple("NO_DATA", 0L), + tuple("*-3.0", 2L), + tuple("3.0-5.0", 0L), + tuple("5.0-10.0", 0L), + tuple("10.0-20.0", 0L), + tuple("20.0-*", 0L)); + checkFacet(response, "languages", + tuple("xoo", 2L)); + checkFacet(response, "ncloc", + tuple("*-1000.0", 2L), + tuple("1000.0-10000.0", 0L), + tuple("10000.0-100000.0", 0L), + tuple("100000.0-500000.0", 0L), + tuple("500000.0-*", 0L)); + checkFacet(response, "reliability_rating", + tuple("1", 2L), + tuple("2", 0L), + tuple("3", 0L), + tuple("4", 0L), + tuple("5", 0L)); + checkFacet(response, "security_rating", + tuple("1", 2L), + tuple("2", 0L), + tuple("3", 0L), + tuple("4", 0L), + tuple("5", 0L)); + checkFacet(response, "sqale_rating", + tuple("1", 0L), + tuple("2", 0L), + tuple("3", 0L), + tuple("4", 2L), + tuple("5", 0L)); + checkFacet(response, "tags"); + } + + @Test + public void should_return_facets_on_leak() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + // This project has no duplication on new code + String projectKey1 = newProjectKey(); + analyzeProject(projectKey1, "shared/xoo-sample", "sonar.projectDate", "2016-12-31"); + analyzeProject(projectKey1, "shared/xoo-sample"); + // This project has 0% duplication on new code + String projectKey2 = newProjectKey(); + analyzeProject(projectKey2, "projectSearch/xoo-history-v1", "sonar.projectDate", "2016-12-31"); + analyzeProject(projectKey2, "projectSearch/xoo-history-v2"); + + SearchProjectsWsResponse response = searchProjects(SearchProjectsRequest.builder().setOrganization(organization.getKey()).setFacets(asList( + "new_reliability_rating", "new_security_rating", "new_maintainability_rating", "new_coverage", "new_duplicated_lines_density", "new_lines")).build()); + + checkFacet(response, "new_reliability_rating", + tuple("1", 2L), + tuple("2", 0L), + tuple("3", 0L), + tuple("4", 0L), + tuple("5", 0L)); + checkFacet(response, "new_security_rating", + tuple("1", 2L), + tuple("2", 0L), + tuple("3", 0L), + tuple("4", 0L), + tuple("5", 0L)); + checkFacet(response, "new_maintainability_rating", + tuple("1", 2L), + tuple("2", 0L), + tuple("3", 0L), + tuple("4", 0L), + tuple("5", 0L)); + checkFacet(response, "new_coverage", + tuple("NO_DATA", 2L), + tuple("*-30.0", 0L), + tuple("30.0-50.0", 0L), + tuple("50.0-70.0", 0L), + tuple("70.0-80.0", 0L), + tuple("80.0-*", 0L)); + checkFacet(response, "new_duplicated_lines_density", + tuple("NO_DATA", 1L), + tuple("*-3.0", 1L), + tuple("3.0-5.0", 0L), + tuple("5.0-10.0", 0L), + tuple("10.0-20.0", 0L), + tuple("20.0-*", 0L)); + checkFacet(response, "new_lines", + tuple("*-1000.0", 1L), + tuple("1000.0-10000.0", 0L), + tuple("10000.0-100000.0", 0L), + tuple("100000.0-500000.0", 0L), + tuple("500000.0-*", 0L)); + } + + private void checkFacet(SearchProjectsWsResponse response, String facetKey, Tuple... values) { + Common.Facet facet = response.getFacets().getFacetsList().stream().filter(f -> f.getProperty().equals(facetKey)).findAny().get(); + assertThat(facet.getValuesList()).extracting(Common.FacetValue::getVal, Common.FacetValue::getCount).containsExactlyInAnyOrder(values); + } + + private void analyzeProject(String projectKey, String relativePath, String... properties) { + List<String> keyValueProperties = new ArrayList<>(asList( + "sonar.projectKey", projectKey, + "sonar.organization", organization.getKey(), + "sonar.profile", "with-many-rules", + "sonar.login", "admin", "sonar.password", "admin", + "sonar.scm.disabled", "false")); + orchestrator.executeBuild(SonarScanner.create(projectDir(relativePath), concat(keyValueProperties.toArray(new String[0]), properties))); + } + + private SearchProjectsWsResponse searchProjects(String filter) throws IOException { + return searchProjects(SearchProjectsRequest.builder().setOrganization(organization.getKey()).setFilter(filter).build()); + } + + private SearchProjectsWsResponse searchProjects(SearchProjectsRequest request) throws IOException { + return tester.wsClient().components().searchProjects(request); + } + + private void verifyFilterMatches(String projectKey, String filter) throws IOException { + assertThat(searchProjects(filter).getComponentsList()).extracting(Component::getKey).containsOnly(projectKey); + } + + private void verifyFilterDoesNotMatch(String filter) throws IOException { + assertThat(searchProjects(filter).getComponentsCount()).isZero(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateNotificationTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateNotificationTest.java new file mode 100644 index 00000000000..69e24dddbda --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateNotificationTest.java @@ -0,0 +1,160 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityGate; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.util.Iterator; +import javax.mail.internet.MimeMessage; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.qualitygate.NewCondition; +import org.sonar.wsclient.qualitygate.QualityGate; +import org.sonar.wsclient.qualitygate.QualityGateClient; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.subethamail.wiser.Wiser; +import org.subethamail.wiser.WiserMessage; +import util.user.UserRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.WsMeasures.Measure; +import static util.ItUtils.getMeasure; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.projectDir; +import static util.ItUtils.resetEmailSettings; +import static util.ItUtils.resetPeriod; +import static util.ItUtils.setServerProperty; + +public class QualityGateNotificationTest { + + private static long DEFAULT_QUALITY_GATE; + + private static final String PROJECT_KEY = "sample"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @ClassRule + public static UserRule userRule = UserRule.from(orchestrator); + + private static Wiser smtpServer; + + @BeforeClass + public static void init() throws Exception { + DEFAULT_QUALITY_GATE = qgClient().list().defaultGate().id(); + + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + resetEmailSettings(orchestrator); + + smtpServer = new Wiser(0); + smtpServer.start(); + } + + @AfterClass + public static void resetData() throws Exception { + qgClient().setDefault(DEFAULT_QUALITY_GATE); + + resetPeriod(orchestrator); + resetEmailSettings(orchestrator); + + if (smtpServer != null) { + smtpServer.stop(); + } + } + + @Before + public void cleanUp() { + orchestrator.resetData(); + } + + @Test + public void status_on_metric_variation_and_send_notifications() throws Exception { + setServerProperty(orchestrator, "email.smtp_host.secured", "localhost"); + setServerProperty(orchestrator, "email.smtp_port.secured", Integer.toString(smtpServer.getServer().getPort())); + + // Create user, who will receive notifications for new violations + userRule.createUser("tester", "Tester", "tester@example.org", "tester"); + // Send test email to the test user + newAdminWsClient(orchestrator).wsConnector().call(new PostRequest("api/emails/send") + .setParam("to", "test@example.org") + .setParam("message", "This is a test message from SonarQube")) + .failIfNotSuccessful(); + // Add notifications to the test user + WsClient wsClient = newUserWsClient(orchestrator, "tester", "tester"); + wsClient.wsConnector().call(new PostRequest("api/notifications/add") + .setParam("type", "NewAlerts") + .setParam("channel", "EmailNotificationChannel")) + .failIfNotSuccessful(); + + // Create quality gate with conditions on variations + QualityGate simple = qgClient().create("SimpleWithDifferential"); + qgClient().setDefault(simple.id()); + qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").period(1).operator("EQ").warningThreshold("0")); + + SonarScanner analysis = SonarScanner.create(projectDir("qualitygate/xoo-sample")); + orchestrator.executeBuild(analysis); + assertThat(getGateStatusMeasure().getValue()).isEqualTo("OK"); + + orchestrator.executeBuild(analysis); + assertThat(getGateStatusMeasure().getValue()).isEqualTo("WARN"); + + qgClient().unsetDefault(); + qgClient().destroy(simple.id()); + + waitUntilAllNotificationsAreDelivered(smtpServer); + + Iterator<WiserMessage> emails = smtpServer.getMessages().iterator(); + + MimeMessage message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<test@example.org>"); + assertThat((String) message.getContent()).contains("This is a test message from SonarQube"); + + assertThat(emails.hasNext()).isTrue(); + message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>"); + assertThat((String) message.getContent()).contains("Quality gate status: Orange (was Green)"); + assertThat((String) message.getContent()).contains("Quality gate threshold: Lines of Code variation = 0 since previous analysis"); + assertThat((String) message.getContent()).contains("/dashboard?id=sample"); + assertThat(emails.hasNext()).isFalse(); + } + + private Measure getGateStatusMeasure() { + return getMeasure(orchestrator, PROJECT_KEY, "alert_status"); + } + + private static QualityGateClient qgClient() { + return orchestrator.getServer().adminWsClient().qualityGateClient(); + } + + private static void waitUntilAllNotificationsAreDelivered(Wiser smtpServer) throws InterruptedException { + for (int i = 0; i < 10; i++) { + if (smtpServer.getMessages().size() == 2) { + break; + } + Thread.sleep(1_000); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateOnRatingMeasuresTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateOnRatingMeasuresTest.java new file mode 100644 index 00000000000..db5b8e92bdc --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateOnRatingMeasuresTest.java @@ -0,0 +1,124 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityGate; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.qualitygate.QualityGateClient; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.qualitygate.CreateConditionRequest; +import org.sonarqube.ws.client.qualitygate.QualityGatesService; +import org.sonarqube.ws.client.qualitygate.SelectWsRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasure; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.resetSettings; +import static util.ItUtils.runProjectAnalysis; +import static util.ItUtils.setServerProperty; + +public class QualityGateOnRatingMeasuresTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + private static final String PROJECT_KEY = "sample"; + + static WsClient wsClient; + + static QualityGatesService QUALITY_GATES; + + Long qualityGateId; + + @BeforeClass + public static void init() { + wsClient = newAdminWsClient(orchestrator); + QUALITY_GATES = wsClient.qualityGates(); + } + + @Before + public void prepareData() { + orchestrator.resetData(); + qualityGateId = QUALITY_GATES.create("QualityGate").getId(); + orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_KEY); + QUALITY_GATES.associateProject(new SelectWsRequest().setGateId(qualityGateId).setProjectKey(PROJECT_KEY)); + } + + @After + public void resetData() throws Exception { + qgClient().destroy(qualityGateId); + resetSettings(orchestrator, null, "sonar.leak.period"); + } + + @Test + public void generate_warning_qgate_on_rating_metric() throws Exception { + QUALITY_GATES.createCondition(CreateConditionRequest.builder() + .setQualityGateId(qualityGateId.intValue()) + .setMetricKey("security_rating") + .setOperator("GT") + .setWarning("3") + .build()); + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityGate/QualityGateOnRatingMeasuresTest/with-many-rules.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "with-many-rules"); + + runProjectAnalysis(orchestrator, "qualitygate/xoo-sample"); + + assertThat(getGateStatusMeasure().getValue()).isEqualTo("WARN"); + } + + @Test + public void generate_error_qgate_on_rating_metric_on_leak_period() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + QUALITY_GATES.createCondition(CreateConditionRequest.builder() + .setQualityGateId(qualityGateId.intValue()) + .setMetricKey("new_security_rating") + .setOperator("GT") + .setError("3") + .setPeriod(1) + .build()); + + // Run first analysis with empty quality gate -> quality gate is green + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "empty"); + runProjectAnalysis(orchestrator, "qualitygate/xoo-sample"); + assertThat(getGateStatusMeasure().getValue()).isEqualTo("OK"); + + // Run second analysis with some rules that makes Security Rating to E -> quality gate is red + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityGate/QualityGateOnRatingMeasuresTest/with-many-rules.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "with-many-rules"); + runProjectAnalysis(orchestrator, "qualitygate/xoo-sample"); + assertThat(getGateStatusMeasure().getValue()).isEqualTo("ERROR"); + } + + private WsMeasures.Measure getGateStatusMeasure() { + return getMeasure(orchestrator, PROJECT_KEY, "alert_status"); + } + + private static QualityGateClient qgClient() { + return orchestrator.getServer().adminWsClient().qualityGateClient(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateTest.java new file mode 100644 index 00000000000..fc6a8adc0a0 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateTest.java @@ -0,0 +1,405 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityGate; + +import com.google.gson.Gson; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.qualitygate.NewCondition; +import org.sonar.wsclient.qualitygate.QualityGate; +import org.sonar.wsclient.qualitygate.QualityGateClient; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.WsCe; +import org.sonarqube.ws.WsMeasures.Measure; +import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.qualitygate.ProjectStatusWsRequest; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static util.ItUtils.concat; +import static util.ItUtils.extractCeTaskId; +import static util.ItUtils.getMeasure; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newProjectKey; +import static util.ItUtils.projectDir; + +public class QualityGateTest { + + private static final String TASK_STATUS_SUCCESS = "SUCCESS"; + private static final String QG_STATUS_NO_QG = "null"; + private static final String QG_STATUS_OK = "OK"; + private static final String QG_STATUS_ERROR = "ERROR"; + private static final String QG_STATUS_WARN = "WARN"; + + private static long DEFAULT_QUALITY_GATE; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + private static WsClient wsClient; + + @BeforeClass + public static void startOrchestrator() { + wsClient = newAdminWsClient(orchestrator); + DEFAULT_QUALITY_GATE = qgClient().list().defaultGate().id(); + } + + @AfterClass + public static void restoreDefaultQualitGate() throws Exception { + qgClient().setDefault(DEFAULT_QUALITY_GATE); + } + + @Test + public void do_not_compute_status_if_no_gate() throws IOException { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_NO_QG); + + assertThat(getGateStatusMeasure(projectKey)).isNull(); + } + + @Test + public void status_ok_if_empty_gate() throws IOException { + QualityGate empty = qgClient().create("Empty"); + qgClient().setDefault(empty.id()); + + try { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK); + + assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK"); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(empty.id()); + } + } + + @Test + public void test_status_ok() throws IOException { + QualityGate simple = qgClient().create("SimpleWithHighThreshold"); + qgClient().setDefault(simple.id()); + qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").warningThreshold("40")); + + try { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK); + + assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK"); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(simple.id()); + } + } + + @Test + public void test_status_warning() throws IOException { + QualityGate simple = qgClient().create("SimpleWithLowThreshold"); + qgClient().setDefault(simple.id()); + qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); + + try { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_WARN); + + assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("WARN"); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(simple.id()); + } + } + + @Test + public void test_status_error() throws IOException { + QualityGate simple = qgClient().create("SimpleWithLowThreshold"); + qgClient().setDefault(simple.id()); + qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").errorThreshold("10")); + + try { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR); + + assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("ERROR"); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(simple.id()); + } + } + + @Test + public void use_server_settings_instead_of_default_gate() throws IOException { + QualityGate alert = qgClient().create("AlertWithLowThreshold"); + qgClient().createCondition(NewCondition.create(alert.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); + QualityGate error = qgClient().create("ErrorWithLowThreshold"); + qgClient().createCondition(NewCondition.create(error.id()).metricKey("ncloc").operator("GT").errorThreshold("10")); + + qgClient().setDefault(alert.id()); + String projectKey = newProjectKey(); + orchestrator.getServer().provisionProject(projectKey, projectKey); + associateQualityGateToProject(error.id(), projectKey); + + try { + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR); + + assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("ERROR"); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(alert.id()); + qgClient().destroy(error.id()); + } + } + + @Test + public void conditions_on_multiple_metric_types() throws IOException { + QualityGate allTypes = qgClient().create("AllMetricTypes"); + qgClient().createCondition(NewCondition.create(allTypes.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); + qgClient().createCondition(NewCondition.create(allTypes.id()).metricKey("duplicated_lines_density").operator("GT").warningThreshold("20")); + qgClient().setDefault(allTypes.id()); + + try { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey, "sonar.cpd.xoo.minimumLines", "2", "sonar.cpd.xoo.minimumTokens", "5"); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_WARN); + + Measure alertStatus = getGateStatusMeasure(projectKey); + assertThat(alertStatus.getValue()).isEqualTo("WARN"); + + String qualityGateDetailJson = getMeasure(orchestrator, projectKey, "quality_gate_details").getValue(); + assertThat(QualityGateDetails.parse(qualityGateDetailJson).getConditions()) + .extracting(QualityGateDetails.Conditions::getMetric, QualityGateDetails.Conditions::getOp, QualityGateDetails.Conditions::getWarning) + .contains(tuple("ncloc", "GT", "10"), tuple("duplicated_lines_density", "GT", "20")); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(allTypes.id()); + } + } + + @Test + public void ad_hoc_build_break_strategy() throws IOException { + QualityGate simple = qgClient().create("SimpleWithLowThresholdForBuildBreakStrategy"); + qgClient().setDefault(simple.id()); + qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").errorThreshold("7")); + + try { + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR); + + String taskId = getTaskIdInLocalReport(projectDir("qualitygate/xoo-sample")); + String analysisId = getAnalysisId(taskId); + + ProjectStatusWsResponse projectStatusWsResponse = wsClient.qualityGates().projectStatus(new ProjectStatusWsRequest().setAnalysisId(analysisId)); + ProjectStatusWsResponse.ProjectStatus projectStatus = projectStatusWsResponse.getProjectStatus(); + assertThat(projectStatus.getStatus()).isEqualTo(ProjectStatusWsResponse.Status.ERROR); + assertThat(projectStatus.getConditionsCount()).isEqualTo(1); + ProjectStatusWsResponse.Condition condition = projectStatus.getConditionsList().get(0); + assertThat(condition.getMetricKey()).isEqualTo("ncloc"); + assertThat(condition.getErrorThreshold()).isEqualTo("7"); + } finally { + qgClient().unsetDefault(); + qgClient().destroy(simple.id()); + } + } + + @Test + public void does_not_fail_when_condition_is_on_removed_metric() throws IOException { + String customMetricKey = randomAlphabetic(10); + createCustomIntMetric(orchestrator, customMetricKey); + QualityGate simple = qgClient().create("OnCustomMetric"); + qgClient().setDefault(simple.id()); + qgClient().createCondition(NewCondition.create(simple.id()).metricKey(customMetricKey).operator("GT").warningThreshold("40")); + try { + deleteCustomMetric(orchestrator, customMetricKey); + String projectKey = newProjectKey(); + BuildResult buildResult = executeAnalysis(projectKey); + + verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK); + + assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK"); + } finally { + deleteCustomMetric(orchestrator, customMetricKey); + qgClient().unsetDefault(); + qgClient().destroy(simple.id()); + } + } + + private BuildResult executeAnalysis(String projectKey, String... keyValueProperties) { + return orchestrator.executeBuild(SonarScanner.create( + projectDir("qualitygate/xoo-sample"), concat(keyValueProperties, "sonar.projectKey", projectKey))); + } + + private void verifyQGStatusInPostTask(BuildResult buildResult, String projectKey, String taskStatus, String qgStatus) throws IOException { + List<String> logsLines = FileUtils.readLines(orchestrator.getServer().getCeLogs(), Charsets.UTF_8); + List<String> postTaskLogLines = extractPosttaskPluginLogs(extractCeTaskId(buildResult), logsLines); + + assertThat(postTaskLogLines).hasSize(1); + assertThat(postTaskLogLines.iterator().next()) + .contains("CeTask[" + taskStatus + "]") + .contains("Project[" + projectKey + "]") + .contains("QualityGate[" + qgStatus + "]"); + } + + private String getAnalysisId(String taskId) throws IOException { + WsResponse activity = wsClient + .wsConnector() + .call(new GetRequest("api/ce/task") + .setParam("id", taskId) + .setMediaType(MediaTypes.PROTOBUF)); + WsCe.TaskResponse activityWsResponse = WsCe.TaskResponse.parseFrom(activity.contentStream()); + return activityWsResponse.getTask().getAnalysisId(); + } + + private String getTaskIdInLocalReport(File projectDirectory) throws IOException { + File metadata = new File(projectDirectory, ".sonar/report-task.txt"); + assertThat(metadata).exists().isFile(); + // verify properties + Properties props = new Properties(); + props.load(new StringReader(FileUtils.readFileToString(metadata, StandardCharsets.UTF_8))); + assertThat(props.getProperty("ceTaskId")).isNotEmpty(); + + return props.getProperty("ceTaskId"); + } + + private Measure getGateStatusMeasure(String projectKey) { + return getMeasure(orchestrator, projectKey, "alert_status"); + } + + private static QualityGateClient qgClient() { + return orchestrator.getServer().adminWsClient().qualityGateClient(); + } + + private static void associateQualityGateToProject(long qGateId, String projectKey) { + newAdminWsClient(orchestrator).wsConnector() + .call(new PostRequest("api/qualitygates/select") + .setParam("gateId", qGateId) + .setParam("projectKey", projectKey)) + .failIfNotSuccessful(); + } + + private static List<String> extractPosttaskPluginLogs(String taskUuid, Iterable<String> ceLogs) { + return StreamSupport.stream(ceLogs.spliterator(), false) + .filter(s -> s.contains("POSTASKPLUGIN: finished()")) + .filter(s -> s.contains(taskUuid)) + .collect(Collectors.toList()); + } + + private static void createCustomIntMetric(Orchestrator orchestrator, String metricKey) { + newAdminWsClient(orchestrator).wsConnector().call(new PostRequest("api/metrics/create") + .setParam("key", metricKey) + .setParam("name", metricKey) + .setParam("type", "INT")) + .failIfNotSuccessful(); + } + + private static void deleteCustomMetric(Orchestrator orchestrator, String metricKey) { + newAdminWsClient(orchestrator).wsConnector().call(new PostRequest("api/metrics/delete") + .setParam("keys", metricKey)) + .failIfNotSuccessful(); + } + + static class QualityGateDetails { + + private String level; + + private List<Conditions> conditions = new ArrayList<>(); + + String getLevel() { + return level; + } + + List<Conditions> getConditions() { + return conditions; + } + + public static QualityGateDetails parse(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, QualityGateDetails.class); + } + + public static class Conditions { + private final String metric; + private final String op; + private final String warning; + private final String actual; + private final String level; + + private Conditions(String metric, String op, String values, String actual, String level) { + this.metric = metric; + this.op = op; + this.warning = values; + this.actual = actual; + this.level = level; + } + + String getMetric() { + return metric; + } + + String getOp() { + return op; + } + + String getWarning() { + return warning; + } + + String getActual() { + return actual; + } + + String getLevel() { + return level; + } + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java new file mode 100644 index 00000000000..4404dc7ca80 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateUiTest.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityGate; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import java.util.Date; +import javax.annotation.Nullable; +import org.apache.commons.lang.time.DateFormatUtils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.qualitygate.NewCondition; +import org.sonar.wsclient.qualitygate.QualityGate; +import org.sonar.wsclient.qualitygate.QualityGateClient; +import org.sonar.wsclient.qualitygate.QualityGateCondition; +import org.sonar.wsclient.qualitygate.UpdateCondition; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ProjectActivityPage; + +import static org.apache.commons.lang.time.DateUtils.addDays; +import static util.ItUtils.projectDir; +import static util.ItUtils.resetPeriod; +import static util.ItUtils.setServerProperty; +import static util.selenium.Selenese.runSelenese; + +public class QualityGateUiTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + private static long DEFAULT_QUALITY_GATE; + + @BeforeClass + public static void initPeriod() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + DEFAULT_QUALITY_GATE = qgClient().list().defaultGate().id(); + } + + @AfterClass + public static void resetData() throws Exception { + resetPeriod(orchestrator); + qgClient().setDefault(DEFAULT_QUALITY_GATE); + } + + @Before + public void cleanUp() { + orchestrator.resetData(); + } + + /** + * SONAR-3326 + */ + @Test + public void display_alerts_correctly_in_history_page() { + QualityGateClient qgClient = qgClient(); + QualityGate qGate = qgClient.create("AlertsForHistory"); + qgClient.setDefault(qGate.id()); + + String firstAnalysisDate = DateFormatUtils.ISO_DATE_FORMAT.format(addDays(new Date(), -2)); + String secondAnalysisDate = DateFormatUtils.ISO_DATE_FORMAT.format(addDays(new Date(), -1)); + + // with this configuration, project should have an Orange alert + QualityGateCondition lowThresholds = qgClient.createCondition(NewCondition.create(qGate.id()).metricKey("lines").operator("GT").warningThreshold("5").errorThreshold("50")); + scanSampleWithDate(firstAnalysisDate); + // with this configuration, project should have a Green alert + qgClient.updateCondition(UpdateCondition.create(lowThresholds.id()).metricKey("lines").operator("GT").warningThreshold("5000").errorThreshold("5000")); + scanSampleWithDate(secondAnalysisDate); + + Navigation nav = Navigation.create(orchestrator); + ProjectActivityPage page = nav.openProjectActivity("sample"); + page + .assertFirstAnalysisOfTheDayHasText(secondAnalysisDate, "Green (was Orange)") + .assertFirstAnalysisOfTheDayHasText(firstAnalysisDate, "Orange"); + + qgClient.unsetDefault(); + qgClient.destroy(qGate.id()); + } + + @Test + public void should_display_quality_gates_page() { + runSelenese(orchestrator, "/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html"); + } + + private void scanSampleWithDate(String date) { + scanSample(date, null); + } + + private void scanSample(@Nullable String date, @Nullable String profile) { + SonarScanner scan = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.cpd.exclusions", "**/*"); + if (date != null) { + scan.setProperty("sonar.projectDate", date); + } + if (profile != null) { + scan.setProfile(profile); + } + orchestrator.executeBuild(scan); + } + + private static QualityGateClient qgClient() { + return orchestrator.getServer().adminWsClient().qualityGateClient(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/DebtConfigurationRule.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/DebtConfigurationRule.java new file mode 100644 index 00000000000..ee4648373b7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/DebtConfigurationRule.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.sonar.orchestrator.Orchestrator; +import java.util.Set; +import org.junit.rules.ExternalResource; + +import static com.google.common.base.Preconditions.checkState; +import static util.ItUtils.setServerProperty; + +/** + * This rule should be used when dealing with technical debt properties, in order to always be sure that the properties are correctly reset between each tests. + */ +public class DebtConfigurationRule extends ExternalResource { + + private static final String DEV_COST_PROPERTY = "sonar.technicalDebt.developmentCost"; + private static final String RATING_GRID_PROPERTY = "sonar.technicalDebt.ratingGrid"; + + private static final String DEV_COST_LANGUAGE_PROPERTY = "languageSpecificParameters"; + private static final String DEV_COST_LANGUAGE_NAME_PROPERTY = DEV_COST_LANGUAGE_PROPERTY + ".0.language"; + private static final String DEV_COST_LANGUAGE_COST_PROPERTY = DEV_COST_LANGUAGE_PROPERTY + ".0.man_days"; + + private static final Joiner COMA_JOINER = Joiner.on(","); + + private static final Set<String> DEV_COST_PROPERTIES = ImmutableSet.of( + DEV_COST_PROPERTY, + DEV_COST_LANGUAGE_PROPERTY, + DEV_COST_LANGUAGE_NAME_PROPERTY, + DEV_COST_LANGUAGE_COST_PROPERTY, + RATING_GRID_PROPERTY); + + private final Orchestrator orchestrator; + + private DebtConfigurationRule(Orchestrator orchestrator) { + this.orchestrator = orchestrator; + } + + public static DebtConfigurationRule create(Orchestrator orchestrator) { + return new DebtConfigurationRule(orchestrator); + } + + @Override + protected void before() throws Throwable { + reset(); + } + + @Override + protected void after() { + reset(); + } + + public void reset() { + resetDevelopmentCost(); + resetRatingGrid(); + } + + public DebtConfigurationRule updateDevelopmentCost(int developmentCost) { + setProperty(DEV_COST_PROPERTY, Integer.toString(developmentCost)); + return this; + } + + public DebtConfigurationRule updateLanguageDevelopmentCost(String language, int developmentCost) { + setServerProperty(orchestrator, DEV_COST_LANGUAGE_PROPERTY, "0"); + setServerProperty(orchestrator, DEV_COST_LANGUAGE_NAME_PROPERTY, language); + setServerProperty(orchestrator, DEV_COST_LANGUAGE_COST_PROPERTY, Integer.toString(developmentCost)); + return this; + } + + public void resetDevelopmentCost() { + for (String property : DEV_COST_PROPERTIES) { + resetProperty(property); + } + } + + public DebtConfigurationRule updateRatingGrid(Double... ratingGrid) { + checkState(ratingGrid.length == 4, "Rating grid must contains 4 values"); + setProperty(RATING_GRID_PROPERTY, COMA_JOINER.join(ratingGrid)); + return this; + } + + public DebtConfigurationRule resetRatingGrid() { + resetProperty(RATING_GRID_PROPERTY); + return this; + } + + private void setProperty(String property, String value) { + setServerProperty(orchestrator, property, value); + } + + private void resetProperty(String property) { + setProperty(property, null); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/MaintainabilityMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/MaintainabilityMeasureTest.java new file mode 100644 index 00000000000..13c310481f2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/MaintainabilityMeasureTest.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasureAsDouble; +import static util.ItUtils.projectDir; + +/** + * SONAR-4715 + */ +public class MaintainabilityMeasureTest { + + private static final String PROJECT = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + private static final String SUB_MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"; + private static final String DIRECTORY = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1"; + private static final String FILE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo"; + + private static final String CODE_SMELLS_METRIC = "code_smells"; + private static final String MAINTAINABILITY_REMEDIATION_EFFORT_METRIC = "sqale_index"; + private static final String MAINTAINABILITY_RATING_METRIC = "sqale_rating"; + private static final String EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC = "effort_to_reach_maintainability_rating_a"; + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Rule + public DebtConfigurationRule debtConfiguration = DebtConfigurationRule.create(orchestrator); + + @Before + public void init() { + orchestrator.resetData(); + + // Set rating grid values to not depend from default value + debtConfiguration.updateRatingGrid(0.1d, 0.2d, 0.5d, 1d); + + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + } + + @Test + public void verify_maintainability_measures_when_code_smells_rules_activated() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/with-many-rules.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules"); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, CODE_SMELLS_METRIC)).isEqualTo(71); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, MAINTAINABILITY_REMEDIATION_EFFORT_METRIC)).isEqualTo(445); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, MAINTAINABILITY_RATING_METRIC)).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC)).isEqualTo(292); + + assertThat(getMeasureAsDouble(orchestrator, MODULE, CODE_SMELLS_METRIC)).isEqualTo(43); + assertThat(getMeasureAsDouble(orchestrator, MODULE, MAINTAINABILITY_REMEDIATION_EFFORT_METRIC)).isEqualTo(231); + assertThat(getMeasureAsDouble(orchestrator, MODULE, MAINTAINABILITY_RATING_METRIC)).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, MODULE, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC)).isEqualTo(150); + + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, CODE_SMELLS_METRIC)).isEqualTo(19); + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, MAINTAINABILITY_REMEDIATION_EFFORT_METRIC)).isEqualTo(113); + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, MAINTAINABILITY_RATING_METRIC)).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC)).isEqualTo(77); + + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, CODE_SMELLS_METRIC)).isEqualTo(18); + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, MAINTAINABILITY_REMEDIATION_EFFORT_METRIC)).isEqualTo(28); + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, MAINTAINABILITY_RATING_METRIC)).isEqualTo(1); + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC)).isZero(); + + assertThat(getMeasureAsDouble(orchestrator, FILE, CODE_SMELLS_METRIC)).isEqualTo(18); + assertThat(getMeasureAsDouble(orchestrator, FILE, MAINTAINABILITY_REMEDIATION_EFFORT_METRIC)).isEqualTo(28); + assertThat(getMeasureAsDouble(orchestrator, FILE, MAINTAINABILITY_RATING_METRIC)).isEqualTo(1); + assertThat(getMeasureAsDouble(orchestrator, FILE, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC)).isZero(); + } + + @Test + public void verify_reliability_measures_when_no_code_smells_rule() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/without-type-code-smells.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "without-type-code-smells"); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, CODE_SMELLS_METRIC)).isZero(); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, MAINTAINABILITY_REMEDIATION_EFFORT_METRIC)).isZero(); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, MAINTAINABILITY_RATING_METRIC)).isEqualTo(1); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_METRIC)).isZero(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/MaintainabilityRatingMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/MaintainabilityRatingMeasureTest.java new file mode 100644 index 00000000000..01834898461 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/MaintainabilityRatingMeasureTest.java @@ -0,0 +1,164 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasureAsDouble; +import static util.ItUtils.projectDir; + +/** + * SONAR-4715 + */ +public class MaintainabilityRatingMeasureTest { + + private static final String PROJECT = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + private static final String SUB_MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"; + private static final String DIRECTORY = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1"; + private static final String FILE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo"; + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Rule + public DebtConfigurationRule debtConfiguration = DebtConfigurationRule.create(orchestrator); + + @Before + public void init() { + orchestrator.resetData(); + + // Set rating grid values to not depend from default value + debtConfiguration.updateRatingGrid(0.1d, 0.2d, 0.5d, 1d); + } + + @Test + public void sqale_rating_measures() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/with-many-rules.xml")); + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules"); + + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, "sqale_rating")).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, MODULE, "sqale_rating")).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, "sqale_rating")).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, "sqale_rating")).isEqualTo(1); + assertThat(getMeasureAsDouble(orchestrator, FILE, "sqale_rating")).isEqualTo(1); + } + + @Test + public void sqale_debt_ratio_measures() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/with-many-rules.xml")); + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules"); + + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, "sqale_debt_ratio")).isEqualTo(29.1d); + assertThat(getMeasureAsDouble(orchestrator, MODULE, "sqale_debt_ratio")).isEqualTo(28.5d); + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, "sqale_debt_ratio")).isEqualTo(31.4d); + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, "sqale_debt_ratio")).isEqualTo(7.8d); + assertThat(getMeasureAsDouble(orchestrator, FILE, "sqale_debt_ratio")).isEqualTo(7.8d); + } + + @Test + public void use_development_cost_parameter() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, "sample", "sqale_rating")).isEqualTo(1); + + debtConfiguration.updateDevelopmentCost(2); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, "sample", "sqale_rating")).isEqualTo(4); + } + + @Test + public void use_language_specific_parameters() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/one-issue-per-line.xml")); + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "one-issue-per-line"); + + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, "sqale_rating")).isEqualTo(1); + + debtConfiguration.updateLanguageDevelopmentCost("xoo", 1); + orchestrator.executeBuild( + SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")) + .setProfile("one-issue-per-line")); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, "sqale_rating")).isEqualTo(5); + } + + @Test + public void use_rating_grid_parameter() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, "sample", "sqale_rating")).isEqualTo(1); + + debtConfiguration.updateRatingGrid(0.001d, 0.005d, 0.01d, 0.015d); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, "sample", "sqale_rating")).isEqualTo(5); + } + + @Test + public void effort_to_reach_maintainability_rating_a() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/with-many-rules.xml")); + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules"); + + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertThat(getMeasureAsDouble(orchestrator, PROJECT, "sqale_rating")).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, PROJECT, "effort_to_reach_maintainability_rating_a")).isEqualTo(292); + + assertThat(getMeasureAsDouble(orchestrator, MODULE, "sqale_rating")).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, MODULE, "effort_to_reach_maintainability_rating_a")).isEqualTo(150); + + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, "sqale_rating")).isEqualTo(3); + assertThat(getMeasureAsDouble(orchestrator, SUB_MODULE, "effort_to_reach_maintainability_rating_a")).isEqualTo(77); + + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, "sqale_rating")).isEqualTo(1); + assertThat(getMeasureAsDouble(orchestrator, DIRECTORY, "effort_to_reach_maintainability_rating_a")).isZero(); + + assertThat(getMeasureAsDouble(orchestrator, FILE, "sqale_rating")).isEqualTo(1); + assertThat(getMeasureAsDouble(orchestrator, FILE, "effort_to_reach_maintainability_rating_a")).isZero(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/NewDebtRatioMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/NewDebtRatioMeasureTest.java new file mode 100644 index 00000000000..90192354c83 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/NewDebtRatioMeasureTest.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category2Suite; +import java.util.Date; +import javax.annotation.Nullable; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.apache.commons.lang.time.DateUtils.addDays; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; +import static util.ItUtils.formatDate; +import static util.ItUtils.getLeakPeriodValue; +import static util.ItUtils.resetPeriod; +import static util.ItUtils.setServerProperty; +import static util.ItUtils.toDate; + +/** + * SONAR-5876 + */ +public class NewDebtRatioMeasureTest { + + private static final String NEW_DEBT_RATIO_METRIC_KEY = "new_sqale_debt_ratio"; + + private static final Date FIRST_COMMIT_DATE = toDate("2016-09-01"); + private static final Date SECOND_COMMIT_DATE = toDate("2016-09-17"); + private static final Date THIRD_COMMIT_DATE = toDate("2016-09-20"); + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @AfterClass + public static void reset() throws Exception { + resetPeriod(orchestrator); + } + + @Before + public void cleanUpAnalysisData() { + orchestrator.resetData(); + } + + @Test + public void new_debt_ratio_is_computed_from_new_debt_and_new_ncloc_count_per_file() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + + // run analysis on the day of after the first commit, with 'one-issue-per-line' profile + defineQualityProfile("one-issue-per-line"); + provisionSampleProject(); + setSampleProjectQualityProfile("one-issue-per-line"); + runSampleProjectAnalysis("v1", "sonar.projectDate", formatDate(addDays(FIRST_COMMIT_DATE, 1))); + + // first analysis, no previous snapshot => periods not resolved => no value + assertNoNewDebtRatio(); + + // run analysis on the day after of second commit 'one-issue-per-line' profile* + // => 3 new issues will be created + runSampleProjectAnalysis("v2", "sonar.projectDate", formatDate(addDays(SECOND_COMMIT_DATE, 1))); + assertNewDebtRatio(4.44); + + // run analysis on the day after of third commit 'one-issue-per-line' profile* + // => 4 new issues will be created + runSampleProjectAnalysis("v3", "sonar.projectDate", formatDate(addDays(THIRD_COMMIT_DATE, 1))); + assertNewDebtRatio(4.17); + } + + @Test + public void compute_new_debt_ratio_using_number_days_in_leak_period() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "30"); + + // run analysis on the day of after the first commit, with 'one-issue-per-line' profile + defineQualityProfile("one-issue-per-line"); + provisionSampleProject(); + setSampleProjectQualityProfile("one-issue-per-line"); + runSampleProjectAnalysis("v1", "sonar.projectDate", formatDate(addDays(FIRST_COMMIT_DATE, 1))); + + // first analysis, no previous snapshot => periods not resolved => no value + assertNoNewDebtRatio(); + + // run analysis on the day after of second commit 'one-issue-per-line' profile* + // => 3 new issues will be created + runSampleProjectAnalysis("v2", "sonar.projectDate", formatDate(addDays(SECOND_COMMIT_DATE, 1))); + assertNewDebtRatio(4.44); + + // run analysis on the day after of third commit 'one-issue-per-line' profile* + // => previous 3 issues plus 4 new issues will be taking into account + runSampleProjectAnalysis("v3", "sonar.projectDate", formatDate(addDays(THIRD_COMMIT_DATE, 1))); + assertNewDebtRatio(4.28); + } + + private void assertNoNewDebtRatio() { + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", NEW_DEBT_RATIO_METRIC_KEY)).isZero(); + } + + private void assertNewDebtRatio(@Nullable Double valuePeriod) { + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", NEW_DEBT_RATIO_METRIC_KEY)).isEqualTo(valuePeriod, within(0.01)); + } + + private void setSampleProjectQualityProfile(String qualityProfileKey) { + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", qualityProfileKey); + } + + private void provisionSampleProject() { + orchestrator.getServer().provisionProject("sample", "sample"); + } + + private void defineQualityProfile(String qualityProfileKey) { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/measure/" + qualityProfileKey + ".xml")); + } + + private void runSampleProjectAnalysis(String projectVersion, String... properties) { + ItUtils.runVerboseProjectAnalysis( + NewDebtRatioMeasureTest.orchestrator, + "measure/xoo-new-debt-ratio-" + projectVersion, + ItUtils.concat(properties, + // disable standard scm support so that it does not interfere with Xoo Scm sensor + "sonar.scm.disabled", "false")); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java new file mode 100644 index 00000000000..3afaa189ea9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.Map; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures; +import util.ItUtils; + +import static java.lang.Double.parseDouble; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresByMetricKey; +import static util.ItUtils.projectDir; + +public class ReliabilityMeasureTest { + + private static final String PROJECT = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + private static final String SUB_MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"; + private static final String DIRECTORY = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1"; + private static final String FILE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo"; + + private static final String BUGS_METRIC = "bugs"; + private static final String RELIABILITY_REMEDIATION_EFFORT_METRIC = "reliability_remediation_effort"; + private static final String RELIABILITY_RATING_METRIC = "reliability_rating"; + + private static final String[] METRICS = new String[] {BUGS_METRIC, RELIABILITY_RATING_METRIC, RELIABILITY_REMEDIATION_EFFORT_METRIC}; + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Before + public void init() { + orchestrator.resetData(); + + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + } + + @Test + public void verify_reliability_measures_when_bug_rules_activated() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/with-many-rules.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules"); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertMeasures(PROJECT, 61, 305, 4); + assertMeasures(MODULE, 37, 185, 4); + assertMeasures(SUB_MODULE, 16, 80, 4); + assertMeasures(DIRECTORY, 16, 80, 4); + assertMeasures(FILE, 16, 80, 4); + } + + @Test + public void verify_reliability_measures_when_no_bug_rule() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/without-type-bug.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "without-type-bug"); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertMeasures(PROJECT, 0, 0, 1); + } + + private void assertMeasures(String componentKey, int expectedBugs, int expectedReliabilityRemediationEffort, int expectedReliabilityRating) { + Map<String, WsMeasures.Measure> measures = getMeasuresByMetricKey(orchestrator, componentKey, METRICS); + assertThat(parseDouble(measures.get(BUGS_METRIC).getValue())).isEqualTo(expectedBugs); + assertThat(parseDouble(measures.get(RELIABILITY_REMEDIATION_EFFORT_METRIC).getValue())).isEqualTo(expectedReliabilityRemediationEffort); + assertThat(parseDouble(measures.get(RELIABILITY_RATING_METRIC).getValue())).isEqualTo(expectedReliabilityRating); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/SecurityMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/SecurityMeasureTest.java new file mode 100644 index 00000000000..5aa1d0b1428 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/SecurityMeasureTest.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.Map; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures; +import util.ItUtils; + +import static java.lang.Double.parseDouble; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresByMetricKey; +import static util.ItUtils.projectDir; + +public class SecurityMeasureTest { + + private static final String PROJECT = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + private static final String SUB_MODULE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"; + private static final String DIRECTORY = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1"; + private static final String FILE = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo"; + + private static final String VULNERABILITIES_METRIC = "vulnerabilities"; + private static final String SECURITY_REMEDIATION_EFFORT_METRIC = "security_remediation_effort"; + private static final String SECURITY_RATING_METRIC = "security_rating"; + + private static final String[] METRICS = new String[] {VULNERABILITIES_METRIC, SECURITY_REMEDIATION_EFFORT_METRIC, SECURITY_RATING_METRIC}; + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Before + public void init() { + orchestrator.resetData(); + + orchestrator.getServer().provisionProject(PROJECT, PROJECT); + } + + @Test + public void verify_security_measures_when_vulnerability_rules_activated() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/with-many-rules.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules"); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertMeasures(PROJECT, 4, 340, 5); + assertMeasures(MODULE, 2, 170, 5); + assertMeasures(SUB_MODULE, 1, 85, 5); + assertMeasures(DIRECTORY, 0, 0, 1); + assertMeasures(FILE, 0, 0, 1); + } + + @Test + public void verify_security_measures_when_no_vulnerability_rule() { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/without-type-vulnerability.xml")); + orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "without-type-vulnerability"); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"))); + + assertMeasures(PROJECT, 0, 0, 1); + } + + private void assertMeasures(String componentKey, int expectedVulnerabilities, int expectedReliabilityRemediationEffort, int expectedReliabilityRating) { + Map<String, WsMeasures.Measure> measures = getMeasuresByMetricKey(orchestrator, componentKey, METRICS); + assertThat(parseDouble(measures.get(VULNERABILITIES_METRIC).getValue())).isEqualTo(expectedVulnerabilities); + assertThat(parseDouble(measures.get(SECURITY_REMEDIATION_EFFORT_METRIC).getValue())).isEqualTo(expectedReliabilityRemediationEffort); + assertThat(parseDouble(measures.get(SECURITY_RATING_METRIC).getValue())).isEqualTo(expectedReliabilityRating); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtInIssueChangelogTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtInIssueChangelogTest.java new file mode 100644 index 00000000000..cb181386895 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtInIssueChangelogTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueClient; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonarqube.ws.Issues; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +/** + * SONAR-4834 + */ +public class TechnicalDebtInIssueChangelogTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Rule + public DebtConfigurationRule debtConfiguration = DebtConfigurationRule.create(orchestrator); + + @Before + public void deleteAnalysisData() { + orchestrator.resetData(); + } + + @Test + public void display_debt_in_issue_changelog() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/one-issue-per-file.xml")); + orchestrator.getServer().provisionProject("sample", "sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-file"); + + // Execute a first analysis to have a past snapshot + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + // Second analysis, existing issues on OneIssuePerFile will have their technical debt updated with the effort to fix + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperties("sonar.oneIssuePerFile.effortToFix", "10")); + + IssueClient issueClient = orchestrator.getServer().wsClient().issueClient(); + Issue issue = issueClient.find(IssueQuery.create()).list().get(0); + + List<Issues.ChangelogWsResponse.Changelog> changes = changelog(issue.key()).getChangelogList(); + assertThat(changes).hasSize(1); + assertThat(changes.get(0).getDiffsList()) + .extracting(Issues.ChangelogWsResponse.Changelog.Diff::getKey, Issues.ChangelogWsResponse.Changelog.Diff::getOldValue, Issues.ChangelogWsResponse.Changelog.Diff::getNewValue) + .containsOnly(tuple("effort", "10", "100")); + } + + private static Issues.ChangelogWsResponse changelog(String issueKey) { + return newAdminWsClient(orchestrator).issues().changelog(issueKey); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtMeasureVariationTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtMeasureVariationTest.java new file mode 100644 index 00000000000..5ea52641e4c --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtMeasureVariationTest.java @@ -0,0 +1,154 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category2Suite; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getLeakPeriodValue; +import static util.ItUtils.setServerProperty; + +/** + * SONAR-4776 + */ +public class TechnicalDebtMeasureVariationTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @AfterClass + public static void resetPeriod() throws Exception { + ItUtils.resetPeriod(orchestrator); + } + + @Before + public void cleanUpAnalysisData() { + orchestrator.resetData(); + } + + @Test + public void new_technical_debt_measures_from_new_issues() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + + // Execute an analysis in the past to have a past snapshot without any issues + provisionSampleProject(); + setSampleProjectQualityProfile("empty"); + runSampleProjectAnalysis("sonar.projectDate", "2013-01-01"); + + // Second analysis -> issues will be created + defineQualityProfile("one-issue-per-line"); + setSampleProjectQualityProfile("one-issue-per-line"); + runSampleProjectAnalysis(); + + // New technical debt only comes from new issues + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isEqualTo(17d); + + // Third analysis, with exactly the same profile -> no new issues so no new technical debt + runSampleProjectAnalysis(); + // No variation => measure is 0 (best value) + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isZero(); + } + + @Test + public void new_technical_debt_measures_from_technical_debt_update_since_previous_analysis() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + + // Execute twice analysis + defineQualityProfile("one-issue-per-file"); + provisionSampleProject(); + setSampleProjectQualityProfile("one-issue-per-file"); + runSampleProjectAnalysis(); + runSampleProjectAnalysis(); + + // Third analysis, existing issues on OneIssuePerFile will have their technical debt updated with the effort to fix + runSampleProjectAnalysis("sonar.oneIssuePerFile.effortToFix", "10"); + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isEqualTo(90); + + // Fourth analysis, with exactly the same profile -> no new issues so no new technical debt since previous analysis + runSampleProjectAnalysis("sonar.oneIssuePerFile.effortToFix", "10"); + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isZero(); + } + + @Test + public void new_technical_debt_measures_from_technical_debt_update_since_30_days() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "30"); + + // Execute an analysis in the past to have a past snapshot without any issues + provisionSampleProject(); + setSampleProjectQualityProfile("empty"); + runSampleProjectAnalysis("sonar.projectDate", "2013-01-01"); + + // Second analysis -> issues will be created + String profileXmlFile = "one-issue-per-file"; + defineQualityProfile(profileXmlFile); + setSampleProjectQualityProfile("one-issue-per-file"); + runSampleProjectAnalysis(); + + // Third analysis, existing issues on OneIssuePerFile will have their technical debt updated with the effort to fix + runSampleProjectAnalysis("sonar.oneIssuePerFile.effortToFix", "10"); + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isEqualTo(90); + + // Fourth analysis, with exactly the same profile -> no new issues so no new technical debt since previous analysis but still since 30 + // days + runSampleProjectAnalysis("sonar.oneIssuePerFile.effortToFix", "10"); + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isEqualTo(90); + } + + /** + * SONAR-5059 + */ + @Test + public void new_technical_debt_measures_should_never_be_negative() throws Exception { + setServerProperty(orchestrator, "sonar.leak.period", "previous_analysis"); + + // Execute an analysis with a big effort to fix + defineQualityProfile("one-issue-per-file"); + provisionSampleProject(); + setSampleProjectQualityProfile("one-issue-per-file"); + runSampleProjectAnalysis("sonar.oneIssuePerFile.effortToFix", "100"); + + // Execute a second analysis with a smaller effort to fix -> Added technical debt should be 0, not negative + runSampleProjectAnalysis("sonar.oneIssuePerFile.effortToFix", "10"); + assertThat(getLeakPeriodValue(orchestrator, "sample:src/main/xoo/sample/Sample.xoo", "new_technical_debt")).isZero(); + } + + private void setSampleProjectQualityProfile(String qualityProfileKey) { + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", qualityProfileKey); + } + + private void provisionSampleProject() { + orchestrator.getServer().provisionProject("sample", "sample"); + } + + private void defineQualityProfile(String qualityProfileKey) { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/measure/" + qualityProfileKey + ".xml")); + } + + private void runSampleProjectAnalysis(String... properties) { + ItUtils.runVerboseProjectAnalysis(TechnicalDebtMeasureVariationTest.orchestrator, "shared/xoo-sample", properties); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtTest.java new file mode 100644 index 00000000000..6eb57cc0ea3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/TechnicalDebtTest.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityModel; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueQuery; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class TechnicalDebtTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Rule + public DebtConfigurationRule debtConfiguration = DebtConfigurationRule.create(orchestrator); + + @Before + public void deleteAnalysisData() { + orchestrator.resetData(); + } + + /** + * SONAR-4716 + */ + @Test + public void technical_debt_on_issue() throws Exception { + ItUtils.restoreProfile(orchestrator, getClass().getResource("/qualityModel/one-issue-per-line.xml")); + orchestrator.getServer().provisionProject("sample", "sample"); + orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", "one-issue-per-line"); + + // Generate some issues + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + + // All the issues should have a technical debt + List<Issue> issues = orchestrator.getServer().wsClient().issueClient().find(IssueQuery.create()).list(); + assertThat(issues).isNotEmpty(); + for (Issue issue : issues) { + assertThat(issue.debt()).isEqualTo("1min"); + } + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/BuiltInQualityProfilesTest.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/BuiltInQualityProfilesTest.java new file mode 100644 index 00000000000..82719d800f3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityProfile/BuiltInQualityProfilesTest.java @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityProfile; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import java.util.function.Predicate; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Session; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.QualityProfiles; +import org.sonarqube.ws.QualityProfiles.CreateWsResponse; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.qualityprofile.ChangeParentRequest; +import org.sonarqube.ws.client.qualityprofile.CopyRequest; +import org.sonarqube.ws.client.qualityprofile.QualityProfilesService; +import org.sonarqube.ws.client.qualityprofile.SearchWsRequest; +import org.sonarqube.ws.client.qualityprofile.SetDefaultRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static util.ItUtils.expectBadRequestError; + +public class BuiltInQualityProfilesTest { + private static final String RULE_ONE_BUG_PER_LINE = "xoo:OneBugIssuePerLine"; + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Test + public void built_in_profiles_are_available_in_new_organization() { + Organization org = tester.organizations().generate(); + SearchWsResponse result = tester.qProfiles().service().search(new SearchWsRequest().setOrganizationKey(org.getKey())); + + assertThat(result.getProfilesList()) + .extracting(QualityProfile::getName, QualityProfile::getLanguage, QualityProfile::getIsBuiltIn, QualityProfile::getIsDefault) + .containsExactlyInAnyOrder( + tuple("Basic", "xoo", true, true), + tuple("empty", "xoo", true, false), + tuple("Basic", "xoo2", true, true)); + } + + @Test + public void built_in_profiles_are_available_in_default_organization() { + SearchWsResponse result = tester.qProfiles().service().search(new SearchWsRequest().setOrganizationKey("default-organization")); + + assertThat(result.getProfilesList()) + .extracting(QualityProfile::getOrganization, QualityProfile::getName, QualityProfile::getLanguage, QualityProfile::getIsBuiltIn, QualityProfile::getIsDefault) + .containsExactlyInAnyOrder( + tuple("default-organization", "Basic", "xoo", true, true), + tuple("default-organization", "empty", "xoo", true, false), + tuple("default-organization", "Basic", "xoo2", true, true)); + } + + @Test + public void cannot_delete_built_in_profile_even_when_not_the_default_profile() { + Organization org = tester.organizations().generate(); + QualityProfile builtInProfile = getProfile(org, p -> p.getIsBuiltIn() && p.getIsDefault() && "Basic".equals(p.getName()) && "xoo".equals(p.getLanguage())); + + CreateWsResponse.QualityProfile profileInOrg = tester.qProfiles().createXooProfile(org); + tester.qProfiles().service().setDefault(new SetDefaultRequest(profileInOrg.getKey())); + + expectBadRequestError(() -> tester.qProfiles().service().delete(builtInProfile.getKey())); + } + + @Test + public void built_in_profile_cannot_be_modified() { + Organization org = tester.organizations().generate(); + QualityProfile builtInProfile = getProfile(org, p -> p.getIsBuiltIn() && p.getIsDefault() && "Basic".equals(p.getName()) && "xoo".equals(p.getLanguage())); + + QualityProfilesService service = tester.qProfiles().service(); + expectBadRequestError(() -> tester.qProfiles().activateRule(builtInProfile.getKey(), RULE_ONE_BUG_PER_LINE)); + expectBadRequestError(() -> service.deactivateRule(builtInProfile.getKey(), RULE_ONE_BUG_PER_LINE)); + expectBadRequestError(() -> service.delete(builtInProfile.getKey())); + } + + @Test + public void copy_built_in_profile_to_a_custom_profile() { + Organization org = tester.organizations().generate(); + User administrator = tester.users().generateAdministrator(org); + QualityProfile builtInProfile = getProfile(org, p -> p.getIsBuiltIn() && "Basic".equals(p.getName()) && "xoo".equals(p.getLanguage())); + Session adminSession = tester.as(administrator.getLogin()); + + QualityProfiles.CopyWsResponse copyResponse = adminSession.qProfiles().service().copy(new CopyRequest(builtInProfile.getKey(), "My copy")); + + assertThat(copyResponse.getIsDefault()).isFalse(); + assertThat(copyResponse.getKey()).isNotEmpty().isNotEqualTo(builtInProfile.getKey()); + assertThat(copyResponse.getLanguage()).isEqualTo(builtInProfile.getLanguage()); + assertThat(copyResponse.getName()).isEqualTo("My copy"); + assertThat(copyResponse.getIsInherited()).isFalse(); + + QualityProfile copy = getProfile(org, p -> "My copy".equals(p.getName()) && "xoo".equals(p.getLanguage())); + assertThat(copy.getIsBuiltIn()).isFalse(); + assertThat(copy.getIsDefault()).isFalse(); + assertThat(builtInProfile.getActiveRuleCount()).isGreaterThan(0); + adminSession.qProfiles().assertThatNumberOfActiveRulesEqualsTo(copy.getKey(), (int) builtInProfile.getActiveRuleCount()); + } + + @Test + public void can_inherit_and_disinherit_from_built_in_profile_to_a_custom_profile() { + Organization org = tester.organizations().generate(); + User administrator = tester.users().generateAdministrator(org); + QualityProfile builtInProfile = getProfile(org, p -> p.getIsBuiltIn() && "Basic".equals(p.getName()) && "xoo".equals(p.getLanguage())); + Session adminSession = tester.as(administrator.getLogin()); + + QualityProfiles.CopyWsResponse copyResponse = adminSession.qProfiles().service().copy(new CopyRequest(builtInProfile.getKey(), "My copy")); + adminSession.qProfiles().service().changeParent( + ChangeParentRequest.builder().setParentKey(builtInProfile.getKey()).setProfileKey(copyResponse.getKey()).build()); + + QualityProfile inheritedQualityPropfile = getProfile(org, p -> p.getKey().equals(copyResponse.getKey())); + + assertThat(inheritedQualityPropfile.getParentKey()).isEqualTo(builtInProfile.getKey()); + assertThat(inheritedQualityPropfile.getParentName()).isEqualTo(builtInProfile.getName()); + + // Remove inheritance + adminSession.qProfiles().service().changeParent( + new ChangeParentRequest(ChangeParentRequest.builder().setProfileKey(inheritedQualityPropfile.getKey()))); + + inheritedQualityPropfile = getProfile(org, p -> p.getKey().equals(copyResponse.getKey())); + + assertThat(inheritedQualityPropfile.getParentKey()).isEmpty(); + assertThat(inheritedQualityPropfile.getParentName()).isEmpty(); + } + + private QualityProfile getProfile(Organization organization, Predicate<QualityProfile> filter) { + return tester.qProfiles().service().search(new SearchWsRequest() + .setOrganizationKey(organization.getKey())).getProfilesList() + .stream() + .filter(filter) + .findAny().orElseThrow(IllegalStateException::new); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/CustomQualityProfilesTest.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/CustomQualityProfilesTest.java new file mode 100644 index 00000000000..0be4b07d90f --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityProfile/CustomQualityProfilesTest.java @@ -0,0 +1,328 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityProfile; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category6Suite; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.QProfileTester; +import org.sonarqube.tests.Session; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.QualityProfiles; +import org.sonarqube.ws.QualityProfiles.CreateWsResponse.QualityProfile; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.qualityprofile.AddProjectRequest; +import org.sonarqube.ws.client.qualityprofile.ChangeParentRequest; +import org.sonarqube.ws.client.qualityprofile.CopyRequest; +import org.sonarqube.ws.client.qualityprofile.CreateRequest; +import org.sonarqube.ws.client.qualityprofile.SearchWsRequest; +import org.sonarqube.ws.client.qualityprofile.SetDefaultRequest; +import util.ItUtils; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.expectForbiddenError; +import static util.ItUtils.expectMissingError; +import static util.ItUtils.expectUnauthorizedError; +import static util.ItUtils.projectDir; + +public class CustomQualityProfilesTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Test + public void activation_of_rules_is_isolated_among_organizations() { + // create two profiles with same names in two organizations + Organization org1 = tester.organizations().generate(); + Organization org2 = tester.organizations().generate(); + QualityProfile profileInOrg1 = tester.qProfiles().createXooProfile(org1, p -> p.setProfileName("foo")); + QualityProfile profileInOrg2 = tester.qProfiles().createXooProfile(org2, p -> p.setProfileName("foo")); + + tester.qProfiles() + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg1, 0) + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg2, 0); + + tester.qProfiles() + .activateRule(profileInOrg1, "xoo:OneIssuePerLine") + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg1, 1) + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg2, 0); + + tester.qProfiles() + .activateRule(profileInOrg1, "xoo:OneIssuePerFile") + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg1, 2) + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg2, 0); + + tester.qProfiles() + .deactivateRule(profileInOrg1, "xoo:OneIssuePerFile") + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg1, 1) + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg2, 0); + + tester.qProfiles() + .activateRule(profileInOrg2, "xoo:OneIssuePerFile") + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg1, 1) + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg2, 1); + + delete(profileInOrg1); + tester.qProfiles() + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg1, 0) + .assertThatNumberOfActiveRulesEqualsTo(profileInOrg2, 1); + } + + @Test + public void an_organization_administrator_can_manage_the_profiles_of_organization() { + Organization org = tester.organizations().generate(); + User user = tester.users().generateAdministrator(org); + + QProfileTester adminSession = tester.as(user.getLogin()).qProfiles(); + QualityProfile profile = adminSession.createXooProfile(org); + adminSession.assertThatNumberOfActiveRulesEqualsTo(profile, 0); + + adminSession + .activateRule(profile, "xoo:OneIssuePerFile") + .assertThatNumberOfActiveRulesEqualsTo(profile, 1); + + adminSession.service().delete(profile.getKey()); + adminSession.assertThatNumberOfActiveRulesEqualsTo(profile, 0); + } + + @Test + public void deleting_an_organization_delete_all_profiles_on_this_organization() { + Organization org = tester.organizations().generate(); + User user = tester.users().generateAdministrator(org); + + QProfileTester adminSession = tester.as(user.getLogin()).qProfiles(); + // Profile + QualityProfile parentProfile = adminSession.createXooProfile(org); + + // Copied profile + QualityProfiles.SearchWsResponse.QualityProfile builtInProfile = getProfile(org, p -> p.getIsBuiltIn() && "Basic".equals(p.getName()) && "xoo".equals(p.getLanguage())); + QualityProfiles.CopyWsResponse copyResponse = adminSession.service().copy(new CopyRequest(builtInProfile.getKey(), "My copy")); + + // Inherited profile from custom + QualityProfile inheritedProfile1 = adminSession.service().create( + CreateRequest.builder() + .setLanguage(parentProfile.getLanguage()) + .setOrganizationKey(org.getKey()) + .setProfileName("inherited_profile") + .build()) + .getProfile(); + + adminSession.service().changeParent( + ChangeParentRequest.builder().setParentKey(parentProfile.getKey()).setProfileKey(inheritedProfile1.getKey()).build()); + + // Inherited profile from builtIn + QualityProfile inheritedProfile2 = adminSession.service().create( + CreateRequest.builder() + .setLanguage(parentProfile.getLanguage()) + .setOrganizationKey(org.getKey()) + .setProfileName("inherited_profile2") + .build()) + .getProfile(); + + adminSession.service().changeParent( + ChangeParentRequest.builder().setParentKey(builtInProfile.getKey()).setProfileKey(inheritedProfile2.getKey()).build()); + + tester.organizations().service().delete(org.getKey()); + + expectMissingError(() -> tester.qProfiles().service().search(new SearchWsRequest() + .setOrganizationKey(org.getKey()))); + + tester.qProfiles().service().search(new SearchWsRequest()).getProfilesList() + .forEach(p -> { + assertThat(p.getOrganization()).isNotEqualTo(org.getKey()); + assertThat(p.getKey()).isNotIn(parentProfile.getKey(), copyResponse.getKey(), inheritedProfile1.getKey(), inheritedProfile2.getKey()); + }); + } + + @Test + public void an_organization_administrator_cannot_manage_the_profiles_of_other_organizations() { + Organization org1 = tester.organizations().generate(); + Organization org2 = tester.organizations().generate(); + QualityProfile profileInOrg2 = tester.qProfiles().createXooProfile(org2); + User adminOfOrg1 = tester.users().generateAdministrator(org1); + + QProfileTester adminSession = tester.as(adminOfOrg1.getLogin()).qProfiles(); + + expectForbiddenError(() -> adminSession.createXooProfile(org2)); + expectForbiddenError(() -> adminSession.service().delete(profileInOrg2.getKey())); + expectForbiddenError(() -> adminSession.activateRule(profileInOrg2, "xoo:OneIssuePerFile")); + expectForbiddenError(() -> adminSession.deactivateRule(profileInOrg2, "xoo:OneIssuePerFile")); + } + + private void delete(QualityProfile profile) { + tester.qProfiles().service().delete(profile.getKey()); + } + + @Test + public void anonymous_cannot_manage_the_profiles_of_an_organization() { + Organization org = tester.organizations().generate(); + QualityProfile profile = tester.qProfiles().createXooProfile(org); + + Session anonymousSession = tester.asAnonymous(); + + expectUnauthorizedError(() -> anonymousSession.qProfiles().createXooProfile(org)); + expectUnauthorizedError(() -> anonymousSession.qProfiles().service().delete(profile.getKey())); + expectUnauthorizedError(() -> anonymousSession.qProfiles().activateRule(profile, "xoo:OneIssuePerFile")); + expectUnauthorizedError(() -> anonymousSession.qProfiles().deactivateRule(profile, "xoo:OneIssuePerFile")); + } + + @Test + public void root_can_manage_the_profiles_of_any_organization() { + Organization org = tester.organizations().generate(); + + User orgAdmin = tester.users().generateAdministrator(org); + Session adminSession = tester.as(orgAdmin.getLogin()); + QualityProfile profile = adminSession.qProfiles().createXooProfile(org); + + // root can activate rule and delete the profile + tester.qProfiles() + .activateRule(profile, "xoo:OneIssuePerFile") + .assertThatNumberOfActiveRulesEqualsTo(profile, 1); + tester.qProfiles().service().delete(profile.getKey()); + tester.qProfiles().assertThatNumberOfActiveRulesEqualsTo(profile, 0); + } + + @Test + public void can_inherit_and_disinherit_and__from_another_custom_profile() { + Organization org = tester.organizations().generate(); + User user = tester.users().generateAdministrator(org); + + Session adminSession = tester.as(user.getLogin()); + QualityProfile parentProfile = adminSession.qProfiles().createXooProfile(org); + QualityProfile inheritedProfile = adminSession.qProfiles().service().create( + CreateRequest.builder() + .setLanguage(parentProfile.getLanguage()) + .setOrganizationKey(org.getKey()) + .setProfileName("inherited_profile") + .build()) + .getProfile(); + + adminSession.qProfiles().service().changeParent( + ChangeParentRequest.builder().setParentKey(parentProfile.getKey()).setProfileKey(inheritedProfile.getKey()).build()); + + QualityProfiles.SearchWsResponse.QualityProfile inheritedQualityPropfile = getProfile(org, p -> p.getKey().equals(inheritedProfile.getKey())); + + assertThat(inheritedQualityPropfile.getParentKey()).isEqualTo(parentProfile.getKey()); + assertThat(inheritedQualityPropfile.getParentName()).isEqualTo(parentProfile.getName()); + + // Remove inheritance + adminSession.qProfiles().service().changeParent( + new ChangeParentRequest(ChangeParentRequest.builder().setProfileKey(inheritedQualityPropfile.getKey()))); + + inheritedQualityPropfile = getProfile(org, p -> p.getKey().equals(inheritedProfile.getKey())); + + assertThat(inheritedQualityPropfile.getParentKey()).isEmpty(); + assertThat(inheritedQualityPropfile.getParentName()).isEmpty(); + } + + @Test + public void analysis_must_use_default_profile() { + Organization org = tester.organizations().generate(); + User admin = tester.users().generateAdministrator(org); + + Session adminSession = tester.as(admin.getLogin()); + + String projectKey = randomAlphanumeric(10); + String projectName = randomAlphanumeric(10); + orchestrator.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample"), + "sonar.login", admin.getLogin(), + "sonar.password", admin.getLogin(), + "sonar.organization", org.getKey()) + .setProjectKey(projectKey) + .setProjectName(projectName)); + + QualityProfiles.SearchWsResponse.QualityProfile defaultProfile = getProfile(org, p -> "xoo".equals(p.getLanguage()) && + p.getIsDefault()); + assertThatQualityProfileIsUsedFor(projectKey, defaultProfile.getKey()); + + QualityProfile newXooProfile = adminSession.qProfiles().createXooProfile(org); + adminSession.qProfiles().service().setDefault(new SetDefaultRequest(newXooProfile.getKey())); + + orchestrator.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample"), + "sonar.login", admin.getLogin(), + "sonar.password", admin.getLogin(), + "sonar.organization", org.getKey()) + .setProjectKey(projectKey) + .setProjectName(projectName)); + + assertThatQualityProfileIsUsedFor(projectKey, newXooProfile.getKey()); + } + + @Test + public void analysis_must_use_associated_profile() { + Organization org = tester.organizations().generate(); + User admin = tester.users().generateAdministrator(org); + String projectKey = randomAlphanumeric(10); + String projectName = randomAlphanumeric(10); + Session adminSession = tester.as(admin.getLogin()); + QualityProfile newXooProfile = adminSession.qProfiles().createXooProfile(org); + + adminSession.wsClient().wsConnector().call(new PostRequest("api/projects/create") + .setParam("project", projectKey) + .setParam("name", projectName) + .setParam("organization", org.getKey())); + + adminSession.qProfiles().service().addProject(AddProjectRequest.builder() + .setProfileKey(newXooProfile.getKey()) + .setProjectKey(projectKey) + .build()); + + orchestrator.executeBuild( + SonarScanner.create(projectDir("shared/xoo-sample"), + "sonar.login", admin.getLogin(), + "sonar.password", admin.getLogin(), + "sonar.organization", org.getKey()) + .setProjectKey(projectKey) + .setProjectName(projectName)); + + assertThatQualityProfileIsUsedFor(projectKey, newXooProfile.getKey()); + } + + private void assertThatQualityProfileIsUsedFor(String projectKey, String qualityProfileKey) { + GetRequest request = new GetRequest("api/navigation/component") + .setParam("componentKey", projectKey); + Map components = ItUtils.jsonToMap(tester.wsClient().wsConnector().call(request).content()); + + assertThat(((Map) ((List) components.get("qualityProfiles")).get(0)).get("key")).isEqualTo(qualityProfileKey); + } + + private QualityProfiles.SearchWsResponse.QualityProfile getProfile(Organization organization, Predicate<QualityProfiles.SearchWsResponse.QualityProfile> filter) { + return tester.qProfiles().service().search(new SearchWsRequest() + .setOrganizationKey(organization.getKey())).getProfilesList() + .stream() + .filter(filter) + .findAny().orElseThrow(IllegalStateException::new); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/OrganizationQualityProfilesUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/OrganizationQualityProfilesUiTest.java new file mode 100644 index 00000000000..1d9b9c1e420 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityProfile/OrganizationQualityProfilesUiTest.java @@ -0,0 +1,181 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityProfile; + +import com.codeborne.selenide.Condition; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category6Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.qualityprofile.AddProjectRequest; +import org.sonarqube.ws.client.qualityprofile.ChangeParentRequest; +import org.sonarqube.pageobjects.Navigation; + +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.projectDir; + +public class OrganizationQualityProfilesUiTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + private Organizations.Organization organization; + + @Before + public void setUp() { + // key and name are overridden for HTML Selenese tests + organization = tester.organizations().generate(o -> o.setKey("test-org").setName("test-org")); + tester.users().generateAdministrator(organization, u -> u.setLogin("admin2").setPassword("admin2")); + createProfile("xoo", "sample"); + inheritProfile("xoo", "sample", "Basic"); + analyzeProject("shared/xoo-sample"); + addProfileToProject("xoo", "sample", "sample"); + } + + @Test + public void testNoGlobalPage() { + Navigation nav = tester.openBrowser(); + nav.open("/profiles"); + $(".page-wrapper-simple").should(Condition.visible); + } + + @Test + public void testHomePage() { + tester.runHtmlTests( + "/organization/OrganizationQualityProfilesUiTest/should_display_list.html", + "/organization/OrganizationQualityProfilesUiTest/should_open_from_list.html", + "/organization/OrganizationQualityProfilesUiTest/should_filter_by_language.html"); + } + + @Test + public void testProfilePage() { + tester.runHtmlTests( + "/organization/OrganizationQualityProfilesUiTest/should_display_profile_rules.html", + "/organization/OrganizationQualityProfilesUiTest/should_display_profile_inheritance.html", + "/organization/OrganizationQualityProfilesUiTest/should_display_profile_projects.html", + "/organization/OrganizationQualityProfilesUiTest/should_display_profile_exporters.html"); + } + + @Test + public void testNotFound() { + Navigation nav = tester.openBrowser(); + nav.open("/organizations/" + organization.getKey() + "/quality_profiles/show?key=unknown"); + $(".quality-profile-not-found").should(Condition.visible); + + nav.open("/organizations/" + organization.getKey() + "/quality_profiles/show?language=xoo&name=unknown"); + $(".quality-profile-not-found").should(Condition.visible); + } + + @Test + public void testProfileChangelog() { + tester.runHtmlTests( + "/organization/OrganizationQualityProfilesUiTest/should_display_changelog.html"); + } + + @Ignore("to be replaced by selenide test in order to inject profile key") + @Test + public void testComparison() { + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_compare.html"); + } + + @Test + public void testCreation() { + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_create.html"); + } + + @Test + public void testDeletion() { + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_delete.html"); + } + + @Test + public void testCopying() { + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_copy.html"); + } + + @Test + public void testRenaming() { + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_rename.html"); + } + + @Test + public void testSettingDefault() { + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_set_default.html"); + } + + @Test + public void testRestoration() { + deleteProfile("xoo", "empty"); + + tester.runHtmlTests("/organization/OrganizationQualityProfilesUiTest/should_restore.html"); + } + + private void createProfile(String language, String name) { + tester.wsClient().wsConnector().call( + new PostRequest("api/qualityprofiles/create") + .setParam("language", language) + .setParam("name", name) + .setParam("organization", organization.getKey())); + } + + private void inheritProfile(String language, String name, String parentName) { + tester.wsClient().qualityProfiles().changeParent(ChangeParentRequest.builder() + .setLanguage(language) + .setProfileName(name) + .setParentName(parentName) + .setOrganization(organization.getKey()) + .build()); + } + + private void analyzeProject(String path) { + orchestrator.executeBuild(SonarScanner.create(projectDir(path)).setProperties( + "sonar.organization", organization.getKey(), + "sonar.login", "admin", + "sonar.password", "admin")); + } + + private void addProfileToProject(String language, String profileName, String projectKey) { + tester.wsClient().qualityProfiles().addProject(AddProjectRequest.builder() + .setLanguage(language) + .setProfileName(profileName) + .setProjectKey(projectKey) + .setOrganization(organization.getKey()) + .build()); + } + + private void deleteProfile(String language, String name) { + tester.wsClient().wsConnector().call( + new PostRequest("api/qualityprofiles/delete") + .setParam("language", language) + .setParam("profileName", name) + .setParam("organization", organization.getKey())); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesUiTest.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesUiTest.java new file mode 100644 index 00000000000..25c126037bb --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesUiTest.java @@ -0,0 +1,196 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.qualityProfile; + +import com.codeborne.selenide.Condition; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.qualityprofile.AddProjectRequest; +import org.sonarqube.ws.client.qualityprofile.ChangeParentRequest; +import org.sonarqube.ws.client.qualityprofile.CreateRequest; +import org.sonarqube.pageobjects.Navigation; +import util.user.UserRule; + +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.projectDir; + +public class QualityProfilesUiTest { + + private static final String ADMIN_USER_LOGIN = "admin-user"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private static WsClient adminWsClient; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Before + public void initAdminUser() throws Exception { + userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); + } + + @After + public void deleteAdminUser() { + userRule.resetUsers(); + } + + @Before + public void createSampleProfile() { + createProfile("xoo", "sample"); + inheritProfile("xoo", "sample", "Basic"); + analyzeProject("shared/xoo-sample"); + addProfileToProject("xoo", "sample", "sample"); + } + + @After + public void tearDown() { + setDefault("xoo", "Basic"); + deleteProfile("xoo", "sample"); + deleteProfile("xoo", "new name"); + } + + @Test + public void testHomePage() { + tester.runHtmlTests( + "/qualityProfile/QualityProfilesUiTest/should_display_list.html", + "/qualityProfile/QualityProfilesUiTest/should_open_from_list.html", + "/qualityProfile/QualityProfilesUiTest/should_filter_by_language.html"); + } + + @Test + public void testProfilePage() { + tester.runHtmlTests( + "/qualityProfile/QualityProfilesUiTest/should_display_profile_rules.html", + "/qualityProfile/QualityProfilesUiTest/should_display_profile_inheritance.html", + "/qualityProfile/QualityProfilesUiTest/should_display_profile_projects.html", + "/qualityProfile/QualityProfilesUiTest/should_display_profile_exporters.html"); + } + + @Test + public void testNotFound() { + Navigation nav = tester.openBrowser(); + + nav.open("/profiles/show?key=unknown"); + $(".quality-profile-not-found").should(Condition.visible); + + nav.open("/profiles/show?language=xoo&name=unknown"); + $(".quality-profile-not-found").should(Condition.visible); + } + + @Test + public void testProfileChangelog() { + tester.runHtmlTests( + "/qualityProfile/QualityProfilesUiTest/should_display_changelog.html"); + } + + @Ignore("find a way to know profile key inside selenium tests") + @Test + public void testComparison() { + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_compare.html"); + } + + @Test + public void testCreation() { + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_create.html"); + } + + @Test + public void testDeletion() { + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_delete.html"); + } + + @Test + public void testCopying() { + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_copy.html"); + } + + @Test + public void testRenaming() { + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_rename.html"); + } + + @Test + public void testSettingDefault() { + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_set_default.html"); + } + + @Test + public void testRestore() { + deleteProfile("xoo", "empty"); + + tester.runHtmlTests("/qualityProfile/QualityProfilesUiTest/should_restore.html"); + } + + private void createProfile(String language, String name) { + tester.wsClient().qualityProfiles().create(CreateRequest.builder() + .setLanguage(language) + .setProfileName(name) + .build()); + } + + private void inheritProfile(String language, String name, String parentName) { + tester.wsClient().qualityProfiles().changeParent(ChangeParentRequest.builder() + .setLanguage(language) + .setProfileName(name) + .setParentName(parentName) + .build()); + } + + private static void analyzeProject(String path) { + orchestrator.executeBuild(SonarScanner.create(projectDir(path))); + } + + private void addProfileToProject(String language, String profileName, String projectKey) { + tester.wsClient().qualityProfiles().addProject(AddProjectRequest.builder() + .setLanguage(language) + .setProfileName(profileName) + .setProjectKey(projectKey) + .build()); + } + + private void deleteProfile(String language, String name) { + tester.wsClient().wsConnector().call( + new PostRequest("api/qualityprofiles/delete") + .setParam("language", language) + .setParam("profileName", name)); + } + + private void setDefault(String language, String name) { + tester.wsClient().wsConnector().call( + new PostRequest("api/qualityprofiles/set_default") + .setParam("language", language) + .setParam("profileName", name)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/rule/RuleTagsTest.java b/tests/src/test/java/org/sonarqube/tests/rule/RuleTagsTest.java new file mode 100644 index 00000000000..16e9397c713 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/rule/RuleTagsTest.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.rule; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import java.util.List; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.client.PostRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RuleTagsTest { + + private static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + private static Tester tester = new Tester(orchestrator); + + @ClassRule + public static TestRule chain = RuleChain.outerRule(orchestrator) + .around(tester); + + private static Organizations.Organization organization1; + private static Organizations.Organization organization2; + + @BeforeClass + public static void setUp() { + organization1 = tester.organizations().generate(); + organization2 = tester.organizations().generate(); + } + + @Test + public void should_not_show_tags_of_other_organization() { + updateTag("foo-tag", organization1); + updateTag("bar-tag", organization2); + assertThat(showRuleTags(organization1)).containsExactly("foo-tag"); + assertThat(showRuleTags(organization2)).containsExactly("bar-tag"); + } + + @Test + public void should_not_list_tags_of_other_organization() { + updateTag("foo-tag", organization1); + updateTag("bar-tag", organization2); + assertThat(listTags(organization1)) + .contains("foo-tag") + .doesNotContain("bar-tag"); + } + + @Test + public void should_not_show_removed_tags() { + updateTag("foo-tag", organization1); + assertThat(showRuleTags(organization1)).contains("foo-tag"); + + updateTag("", organization1); + assertThat(showRuleTags(organization1)).isEmpty(); + } + + @Test + public void should_not_list_removed_tags() { + updateTag("foo-tag", organization1); + assertThat(listTags(organization1)).contains("foo-tag"); + + updateTag("", organization1); + assertThat(listTags(organization1)).doesNotContain("foo-tag"); + } + + private List<String> listTags(Organizations.Organization organization) { + String json = orchestrator.getServer().newHttpCall("/api/rules/tags") + .setParam("organization", organization.getKey()) + .execute() + .getBodyAsString(); + return (List<String>) ItUtils.jsonToMap(json).get("tags"); + } + + private List<String> showRuleTags(Organizations.Organization organization) { + return tester.wsClient().rules().show(organization.getKey(), "xoo:OneIssuePerFile") + .getRule().getTags().getTagsList(); + } + + private void updateTag(String tag, Organizations.Organization organization) { + tester.wsClient().wsConnector().call(new PostRequest("/api/rules/update") + .setParam("organization", organization.getKey()) + .setParam("key", "xoo:OneIssuePerFile") + .setParam("tags", tag)) + .failIfNotSuccessful(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/scm/ScmTest.java b/tests/src/test/java/org/sonarqube/tests/scm/ScmTest.java new file mode 100644 index 00000000000..16cc5fbe942 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/scm/ScmTest.java @@ -0,0 +1,139 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.scm; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.assertj.core.data.MapEntry; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.wsclient.jsonsimple.JSONArray; +import org.sonar.wsclient.jsonsimple.JSONObject; +import org.sonar.wsclient.jsonsimple.JSONValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class ScmTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + private static final SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + + @Before + public void delete_data() { + orchestrator.resetData(); + } + + @Test + public void scm_optimization() throws Exception { + SonarScanner build = SonarScanner.create(projectDir("scm/xoo-sample-with-scm")) + .setProperty("sonar.scm.provider", "xoo") + .setProperty("sonar.scm.disabled", "false"); + + // First run + BuildResult buildResult = orchestrator.executeBuild(build); + + assertThat(getScmData("sample-scm:src/main/xoo/sample/Sample.xoo")) + .containsExactly( + MapEntry.entry(1, new LineData("1", "2013-01-04T00:00:00+0000", "jhenry")), + MapEntry.entry(3, new LineData("2", "2013-01-04T00:00:00+0000", "jhenry")), + MapEntry.entry(4, new LineData("1", "2013-01-04T00:00:00+0000", "jhenry")), + MapEntry.entry(8, new LineData("3", "2014-01-04T00:00:00+0000", "toto"))); + + assertThat(buildResult.getLogs()).containsSequence("1 files to be analyzed", "1/1 files analyzed"); + + // Second run with same file should not trigger blame but SCM data are copied from previous analysis + buildResult = orchestrator.executeBuild(build); + + assertThat(getScmData("sample-scm:src/main/xoo/sample/Sample.xoo")) + .containsExactly( + MapEntry.entry(1, new LineData("1", "2013-01-04T00:00:00+0000", "jhenry")), + MapEntry.entry(3, new LineData("2", "2013-01-04T00:00:00+0000", "jhenry")), + MapEntry.entry(4, new LineData("1", "2013-01-04T00:00:00+0000", "jhenry")), + MapEntry.entry(8, new LineData("3", "2014-01-04T00:00:00+0000", "toto"))); + + assertThat(buildResult.getLogs()).doesNotContain("1 files to be analyzed"); + assertThat(buildResult.getLogs()).doesNotContain("1/1 files analyzed"); + + // Now if SCM is explicitely disabled it should clear SCM data on server side + buildResult = orchestrator.executeBuild(build.setProperty("sonar.scm.disabled", "true")); + + assertThat(getScmData("sample-scm:src/main/xoo/sample/Sample.xoo")).isEmpty(); + + assertThat(buildResult.getLogs()).doesNotContain("1 files to be analyzed"); + assertThat(buildResult.getLogs()).doesNotContain("1/1 files analyzed"); + } + + private class LineData { + + final String revision; + final Date date; + final String author; + + public LineData(String revision, String datetime, String author) throws ParseException { + this.revision = revision; + this.date = DATETIME_FORMAT.parse(datetime); + this.author = author; + } + + @Override + public boolean equals(Object obj) { + return EqualsBuilder.reflectionEquals(this, obj); + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(revision).append(date).append(author).toHashCode(); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE); + } + } + + private Map<Integer, LineData> getScmData(String fileKey) throws ParseException { + Map<Integer, LineData> result = new HashMap<>(); + String json = orchestrator.getServer().adminWsClient().get("api/sources/scm", "key", fileKey); + JSONObject obj = (JSONObject) JSONValue.parse(json); + JSONArray array = (JSONArray) obj.get("scm"); + for (Object anArray : array) { + JSONArray item = (JSONArray) anArray; + String datetime = (String) item.get(2); + result.put(((Long) item.get(0)).intValue(), new LineData((String) item.get(3), datetime, (String) item.get(1))); + } + return result; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/ClusterTest.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/ClusterTest.java new file mode 100644 index 00000000000..ff110377844 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/ClusterTest.java @@ -0,0 +1,215 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.server.StartupLogWatcher; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.sonarqube.ws.Issues; +import org.sonarqube.ws.Settings; +import org.sonarqube.ws.client.rule.SearchWsRequest; +import org.sonarqube.ws.client.setting.ValuesRequest; +import util.ItUtils; + +import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newWsClient; + +@Ignore("temporarily ignored") +public class ClusterTest { + + private static final String CONF_FILE_PATH = "conf/sonar.properties"; + + /** + * SONAR-7899 + */ + @Test + public void secondary_nodes_do_not_write_to_datastores_at_startup() throws Exception { + // start "startup leader", which creates and populates datastores + Orchestrator orchestrator = Orchestrator.builderEnv() + .setServerProperty("sonar.cluster.enabled", "true") + .setServerProperty("sonar.cluster.name", "secondary_nodes_do_not_write_to_datastores_at_startup") + .setServerProperty("sonar.cluster.web.startupLeader", "true") + .setServerProperty("sonar.log.level", "TRACE") + .addPlugin(ItUtils.xooPlugin()) + .build(); + orchestrator.start(); + + expectLog(orchestrator, "Cluster enabled (startup leader)"); + expectWriteOperations(orchestrator, true); + // verify that datastores are populated by requesting rules + assertThat(newWsClient(orchestrator).rules().search(new SearchWsRequest()).getTotal()).isGreaterThan(0); + + FileUtils.write(orchestrator.getServer().getWebLogs(), "", false); + updateSonarPropertiesFile(orchestrator, ImmutableMap.of("sonar.cluster.web.startupLeader", "false")); + orchestrator.restartServer(); + + expectLog(orchestrator, "Cluster enabled (startup follower)"); + expectWriteOperations(orchestrator, false); + + orchestrator.stop(); + } + + @Test + public void start_cluster_of_elasticsearch_and_web_nodes() throws IOException { + Orchestrator elasticsearch = null; + Orchestrator web = null; + + try { + ElasticsearchStartupWatcher esWatcher = new ElasticsearchStartupWatcher(); + elasticsearch = Orchestrator.builderEnv() + .setServerProperty("sonar.cluster.enabled", "true") + .setServerProperty("sonar.cluster.name", "start_cluster_of_elasticsearch_and_web_nodes") + .setServerProperty("sonar.cluster.web.disabled", "true") + .setServerProperty("sonar.cluster.ce.disabled", "true") + .setStartupLogWatcher(esWatcher) + .build(); + elasticsearch.start(); + assertThat(esWatcher.port).isGreaterThan(0); + assertThat(FileUtils.readFileToString(elasticsearch.getServer().getAppLogs())).doesNotContain("Process[web]"); + + web = Orchestrator.builderEnv() + .setServerProperty("sonar.cluster.enabled", "true") + .setServerProperty("sonar.cluster.name", "start_cluster_of_elasticsearch_and_web_nodes") + .setServerProperty("sonar.cluster.web.startupLeader", "true") + .setServerProperty("sonar.cluster.search.disabled", "true") + .setServerProperty("sonar.cluster.search.hosts", "localhost:" + esWatcher.port) + // no need for compute engine in this test. Disable it for faster test. + .setServerProperty("sonar.cluster.ce.disabled", "true") + // override the default watcher provided by Orchestrator + // which waits for Compute Engine to be up + .setStartupLogWatcher(log -> log.contains("SonarQube is up")) + .build(); + web.start(); + + String coreId = getPropertyValue(web, "sonar.core.id"); + String startTime = getPropertyValue(web, "sonar.core.startTime"); + + assertThat(FileUtils.readFileToString(web.getServer().getAppLogs())).doesNotContain("Process[es]"); + // call a web service that requires Elasticsearch + Issues.SearchWsResponse wsResponse = newWsClient(web).issues().search(new org.sonarqube.ws.client.issue.SearchWsRequest()); + assertThat(wsResponse.getIssuesCount()).isEqualTo(0); + + web.restartServer(); + + // sonar core id must not change after restart + assertThat(getPropertyValue(web, "sonar.core.id")).isEqualTo(coreId); + // startTime must change at each startup + assertThat(getPropertyValue(web, "sonar.core.startTime")).isNotEqualTo(startTime); + } finally { + if (web != null) { + web.stop(); + } + if (elasticsearch != null) { + elasticsearch.stop(); + } + } + } + + private static String getPropertyValue(Orchestrator web, String property) { + Settings.ValuesWsResponse response = ItUtils.newAdminWsClient(web).settings().values(ValuesRequest.builder().setKeys(property).build()); + List<Settings.Setting> settingsList = response.getSettingsList(); + if (settingsList.isEmpty()) { + return null; + } + assertThat(settingsList).hasSize(1); + return settingsList.iterator().next().getValue(); + } + + private static class ElasticsearchStartupWatcher implements StartupLogWatcher { + private final Pattern pattern = Pattern.compile("Elasticsearch listening on .*:(\\d+)"); + private int port = -1; + + @Override + public boolean isStarted(String log) { + Matcher matcher = pattern.matcher(log); + if (matcher.find()) { + port = Integer.parseInt(matcher.group(1)); + } + return log.contains("Process[es] is up"); + } + } + + private static void expectLog(Orchestrator orchestrator, String expectedLog) throws IOException { + File logFile = orchestrator.getServer().getWebLogs(); + try (Stream<String> lines = Files.lines(logFile.toPath())) { + assertThat(lines.anyMatch(s -> StringUtils.containsIgnoreCase(s, expectedLog))).isTrue(); + } + } + + private static void expectWriteOperations(Orchestrator orchestrator, boolean expected) throws IOException { + try (Stream<String> lines = Files.lines(orchestrator.getServer().getWebLogs().toPath())) { + List<String> writeOperations = lines.filter(ClusterTest::isWriteOperation).collect(Collectors.toList()); + if (expected) { + assertThat(writeOperations).isNotEmpty(); + } else { + assertThat(writeOperations).as("Unexpected write operations: " + Joiner.on('\n').join(writeOperations)).isEmpty(); + + } + } + } + + private static boolean isWriteOperation(String log) { + return isDbWriteOperation(log) || isEsWriteOperation(log); + } + + private static boolean isDbWriteOperation(String log) { + return log.contains("web[][sql]") && (containsIgnoreCase(log, "sql=insert") || + containsIgnoreCase(log, "sql=update") || + containsIgnoreCase(log, "sql=delete") || + containsIgnoreCase(log, "sql=create")); + } + + private static boolean isEsWriteOperation(String log) { + return log.contains("web[][es]") && (containsIgnoreCase(log, "Create index") || + containsIgnoreCase(log, "Create type") || + containsIgnoreCase(log, "put mapping request") || + containsIgnoreCase(log, "refresh request") || + containsIgnoreCase(log, "index request")); + } + + private static void updateSonarPropertiesFile(Orchestrator orchestrator, Map<String, String> props) throws IOException { + Properties propsFile = new Properties(); + try (FileInputStream conf = FileUtils.openInputStream(new File(orchestrator.getServer().getHome(), CONF_FILE_PATH))) { + propsFile.load(conf); + propsFile.putAll(props); + } + try (FileOutputStream conf = FileUtils.openOutputStream(new File(orchestrator.getServer().getHome(), CONF_FILE_PATH))) { + propsFile.store(conf, ""); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/HttpHeadersTest.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/HttpHeadersTest.java new file mode 100644 index 00000000000..7ac7d9ba0cc --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/HttpHeadersTest.java @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import okhttp3.CacheControl; +import okhttp3.Response; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import static com.google.common.io.Files.getFileExtension; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.call; + +public class HttpHeadersTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + private static String JS_HASH; + + @BeforeClass + public static void setUp() throws Exception { + JS_HASH = getJsHash(); + } + + @Test + public void verify_headers_of_base_url() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/"); + + verifySecurityHeaders(response); + verifyContentType(response, "text/html;charset=utf-8"); + + // SONAR-6964 + assertNoCacheInBrowser(response); + } + + @Test + public void verify_headers_of_ws() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/api/issues/search"); + + verifySecurityHeaders(response); + verifyContentType(response, "application/json"); + assertNoCacheInBrowser(response); + } + + @Test + public void verify_headers_of_images() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/images/logo.svg"); + + verifySecurityHeaders(response); + verifyContentType(response, "image/svg+xml"); + assertCacheInBrowser(response); + } + + @Test + public void verify_headers_of_css() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/css/sonar." + JS_HASH + ".css"); + + verifySecurityHeaders(response); + verifyContentType(response, "text/css"); + assertCacheInBrowser(response); + } + + @Test + public void verify_headers_of_js() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/js/app." + JS_HASH + ".js"); + + verifySecurityHeaders(response); + verifyContentType(response, "application/javascript"); + } + + @Test + public void verify_headers_of_images_provided_by_plugins() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/static/uiextensionsplugin/cute.jpg"); + + verifySecurityHeaders(response); + verifyContentType(response, "image/jpeg"); + } + + @Test + public void verify_headers_of_js_provided_by_plugins() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/static/uiextensionsplugin/extension.js"); + + verifySecurityHeaders(response); + verifyContentType(response, "application/javascript"); + } + + @Test + public void verify_headers_of_html_provided_by_plugins() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/static/uiextensionsplugin/file.html"); + + verifySecurityHeaders(response); + verifyContentType(response, "text/html"); + } + + private static void assertCacheInBrowser(Response httpResponse) { + CacheControl cacheControl = httpResponse.cacheControl(); + assertThat(cacheControl.mustRevalidate()).isFalse(); + assertThat(cacheControl.noCache()).isFalse(); + assertThat(cacheControl.noStore()).isFalse(); + } + + private static void assertNoCacheInBrowser(Response httpResponse) { + CacheControl cacheControl = httpResponse.cacheControl(); + assertThat(cacheControl.mustRevalidate()).isTrue(); + assertThat(cacheControl.noCache()).isTrue(); + assertThat(cacheControl.noStore()).isTrue(); + } + + /** + * SONAR-8247 + */ + private static void verifySecurityHeaders(Response httpResponse) { + assertThat(httpResponse.isSuccessful()).as("Code is %s", httpResponse.code()).isTrue(); + assertThat(httpResponse.headers().get("X-Frame-Options")).isEqualTo("SAMEORIGIN"); + assertThat(httpResponse.headers().get("X-XSS-Protection")).isEqualTo("1; mode=block"); + assertThat(httpResponse.headers().get("X-Content-Type-Options")).isEqualTo("nosniff"); + } + + private static void verifyContentType(Response httpResponse, String expectedContentType) { + assertThat(httpResponse.headers().get("Content-Type")).isEqualTo(expectedContentType); + } + + /** + * Every JS and CSS files contains a hash between the file name and the extension. + */ + private static String getJsHash() throws IOException { + File cssFolder = new File(orchestrator.getServer().getHome(), "web/css"); + String fileName = Files.list(cssFolder.toPath()) + .map(path -> path.toFile().getName()) + .filter(name -> getFileExtension(name).equals("css")) + .findFirst() + .orElseThrow(() -> new IllegalStateException("sonar.css hasn't been found")); + return fileName.replace("sonar.", "").replace(".css", ""); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/LogsTest.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/LogsTest.java new file mode 100644 index 00000000000..e9eb13360e9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/LogsTest.java @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.input.ReversedLinesFileReader; +import org.assertj.core.util.Files; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.SearchWsRequest; +import util.ItUtils; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; + +public class LogsTest { + + public static final String ACCESS_LOGS_PATTERN = "\"%reqAttribute{ID}\" \"%reqAttribute{LOGIN}\" \"%r\" %s"; + private static final String PATH = "/called/from/LogsTest"; + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Before + public void cleanDatabase() { + orchestrator.resetData(); + } + + /** + * SONAR-7581 + */ + @Test + public void verify_login_in_access_logs() throws Exception { + // log "-" for anonymous + sendHttpRequest(ItUtils.newWsClient(orchestrator), PATH); + assertThat(accessLogsFile()).isFile().exists(); + verifyLastAccessLogLine("-", PATH, 200); + + sendHttpRequest(ItUtils.newAdminWsClient(orchestrator), PATH); + verifyLastAccessLogLine("admin", PATH, 200); + } + + @Test + public void verify_request_id_in_access_logs() throws IOException { + sendHttpRequest(ItUtils.newWsClient(orchestrator), PATH); + String lastAccessLog = readLastAccessLog(); + assertThat(lastAccessLog).doesNotStartWith("\"\"").startsWith("\""); + int firstQuote = lastAccessLog.indexOf('"'); + String requestId = lastAccessLog.substring(firstQuote + 1, lastAccessLog.indexOf('"', firstQuote + 1)); + assertThat(requestId.length()).isGreaterThanOrEqualTo(20); + } + + @Test + public void info_log_in_sonar_log_file_when_SQ_is_done_starting() throws IOException { + List<String> logs = FileUtils.readLines(orchestrator.getServer().getAppLogs()); + String sqIsUpMessage = "SonarQube is up"; + assertThat(logs.stream().filter(str -> str.contains(sqIsUpMessage)).findFirst()).describedAs("message is there").isNotEmpty(); + assertThat(logs.get(logs.size() - 1)).describedAs("message is the last line of logs").contains(sqIsUpMessage); + } + + @Test + public void test_ws_change_log_level() throws IOException { + generateSqlAndEsLogsInWebAndCe(); + + assertThat(logLevelsOf(orchestrator.getServer().getWebLogs())).doesNotContain("DEBUG", "TRACE"); + assertThat(logLevelsOf(orchestrator.getServer().getCeLogs())).doesNotContain("DEBUG", "TRACE"); + + orchestrator.getServer().adminWsClient().post("api/system/change_log_level", "level", "TRACE"); + + generateSqlAndEsLogsInWebAndCe(); + + // there is hardly DEBUG logs, but we are sure there must be TRACE logs for SQL and ES requests + assertThat(logLevelsOf(orchestrator.getServer().getWebLogs())).contains("TRACE"); + assertThat(logLevelsOf(orchestrator.getServer().getCeLogs())).contains("TRACE"); + + // reset log files to empty and level to INFO + orchestrator.getServer().adminWsClient().post("api/system/change_log_level", "level", "INFO"); + FileUtils.write(orchestrator.getServer().getWebLogs(), ""); + FileUtils.write(orchestrator.getServer().getCeLogs(), ""); + + generateSqlAndEsLogsInWebAndCe(); + + assertThat(logLevelsOf(orchestrator.getServer().getWebLogs())).doesNotContain("DEBUG", "TRACE"); + assertThat(logLevelsOf(orchestrator.getServer().getCeLogs())).doesNotContain("DEBUG", "TRACE"); + } + + private void generateSqlAndEsLogsInWebAndCe() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + ItUtils.newAdminWsClient(orchestrator).issues().search(new SearchWsRequest() + .setProjectKeys(Collections.singletonList("sample"))); + } + + private Collection<String> logLevelsOf(File webLogs) { + return Files.linesOf(webLogs, "UTF-8").stream() + .filter(str -> str.length() >= 25) + .map(str -> str.substring(20, 25)) + .map(String::trim) + .collect(Collectors.toSet()); + } + + private void verifyLastAccessLogLine(String login, String path, int status) throws IOException { + assertThat(readLastAccessLog()).endsWith(format("\"%s\" \"GET %s HTTP/1.1\" %d", login, path, status)); + } + + private String readLastAccessLog() throws IOException { + try (ReversedLinesFileReader tailer = new ReversedLinesFileReader(accessLogsFile())) { + return tailer.readLine(); + } + } + + private void sendHttpRequest(WsClient client, String path) { + client.wsConnector().call(new GetRequest(path)); + } + + private File accessLogsFile() { + return new File(orchestrator.getServer().getHome(), "logs/access.log"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/PingTest.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/PingTest.java new file mode 100644 index 00000000000..b1276875ff7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/PingTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.http.HttpResponse; +import org.sonarqube.tests.Category4Suite; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PingTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Test + public void ping_answers_pong() throws Exception { + HttpResponse response = orchestrator.getServer().newHttpCall("/api/system/ping").execute(); + + assertThat(response.getBodyAsString()).isEqualTo("pong"); + assertThat(response.getHeader("Content-Type")).isEqualTo("text/plain"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/RestartTest.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/RestartTest.java new file mode 100644 index 00000000000..ee69d37febb --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/RestartTest.java @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.sonar.orchestrator.Orchestrator; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.SystemUtils; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newWsClient; + +/** + * This class starts a new orchestrator on each test case + */ +public class RestartTest { + + private Orchestrator orchestrator; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(900)); + + @After + public void stop() { + if (orchestrator != null) { + orchestrator.stop(); + } + } + + @Test + public void restart_in_prod_mode_requires_sysadmin_permission_and_restarts() throws Exception { + // server classloader locks Jar files on Windows + if (!SystemUtils.IS_OS_WINDOWS) { + orchestrator = Orchestrator.builderEnv() + .setOrchestratorProperty("orchestrator.keepWorkspace", "true") + .build(); + orchestrator.start(); + + verifyFailWith403(() -> newWsClient(orchestrator).system().restart()); + + createNonSystemAdministrator("john", "doe"); + verifyFailWith403(() -> ItUtils.newUserWsClient(orchestrator, "john", "doe").system().restart()); + + createSystemAdministrator("big", "boss"); + ItUtils.newUserWsClient(orchestrator, "big", "boss").system().restart(); + WsResponse wsResponse = newAdminWsClient(orchestrator).wsConnector().call(new GetRequest("/api/system/status")).failIfNotSuccessful(); + assertThat(wsResponse.content()).contains("RESTARTING"); + + // we just wait five seconds, for a lack of a better approach to waiting for the restart process to start in SQ + Thread.sleep(5000); + + assertThat(FileUtils.readFileToString(orchestrator.getServer().getWebLogs())) + .contains("SonarQube restart requested by big"); + } + } + + /** + * SONAR-4843 + */ + @Test + public void restart_on_dev_mode() throws Exception { + // server classloader locks Jar files on Windows + if (!SystemUtils.IS_OS_WINDOWS) { + orchestrator = Orchestrator.builderEnv() + .setServerProperty("sonar.web.dev", "true") + .build(); + orchestrator.start(); + + newAdminWsClient(orchestrator).system().restart(); + assertThat(FileUtils.readFileToString(orchestrator.getServer().getWebLogs())) + .contains("Fast restarting WebServer...") + .contains("WebServer restarted"); + } + } + + private static void verifyFailWith403(Runnable runnable) { + try { + runnable.run(); + fail(); + } catch (Exception e) { + assertThat(e.getMessage()).contains("403"); + } + } + + private void createSystemAdministrator(String login, String password) { + WsClient wsClient = newAdminWsClient(orchestrator); + createNonSystemAdministrator(wsClient, login, password); + wsClient.permissions().addUser(new AddUserWsRequest().setLogin(login).setPermission("admin")); + } + + private void createNonSystemAdministrator(String login, String password) { + createNonSystemAdministrator(newAdminWsClient(orchestrator), login, password); + } + + private static void createNonSystemAdministrator(WsClient wsClient, String login, String password) { + wsClient.wsConnector().call( + new PostRequest("api/users/create") + .setParam("login", login) + .setParam("name", login) + .setParam("password", password)); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/ServerSystemRestartingOrchestrator.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/ServerSystemRestartingOrchestrator.java new file mode 100644 index 00000000000..02e7887b6cf --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/ServerSystemRestartingOrchestrator.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.locator.FileLocation; +import java.io.File; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.newWsClient; + +/** + * This class start a new orchestrator on each test case + */ +public class ServerSystemRestartingOrchestrator { + + Orchestrator orchestrator; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @After + public void stop() { + if (orchestrator != null) { + orchestrator.stop(); + } + } + + /** + * SONAR-3516 + */ + @Test + public void check_minimal_sonar_version_at_startup() throws Exception { + try { + orchestrator = Orchestrator.builderEnv() + .addPlugin(FileLocation.of(new File(ServerSystemRestartingOrchestrator.class.getResource("/serverSystem/ServerSystemTest/incompatible-plugin-1.0.jar").toURI()))) + .build(); + orchestrator.start(); + fail(); + } catch (Exception e) { + assertThat(FileUtils.readFileToString(orchestrator.getServer().getWebLogs())).contains( + "Plugin incompatible-plugin [incompatibleplugin] requires at least SonarQube 100"); + } + } + + @Test + public void support_install_dir_with_whitespaces() throws Exception { + String dirName = "target/has space"; + FileUtils.deleteDirectory(new File(dirName)); + orchestrator = Orchestrator.builderEnv() + .setOrchestratorProperty("orchestrator.workspaceDir", dirName) + .build(); + orchestrator.start(); + + WsResponse statusResponse = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/system/status")); + Map<String, Object> json = ItUtils.jsonToMap(statusResponse.content()); + assertThat(json.get("status")).isEqualTo("UP"); + } + + // SONAR-4748 + @Test + public void should_create_in_temp_folder() throws Exception { + orchestrator = Orchestrator.builderEnv() + .addPlugin(ItUtils.pluginArtifact("server-plugin")) + .setServerProperty("sonar.createTempFiles", "true") + .build(); + orchestrator.start(); + + File tempDir = new File(orchestrator.getServer().getHome(), "temp/tmp"); + + String logs = FileUtils.readFileToString(orchestrator.getServer().getWebLogs()); + assertThat(logs).contains("Creating temp directory: " + tempDir.getAbsolutePath() + File.separator + "sonar-it"); + assertThat(logs).contains("Creating temp file: " + tempDir.getAbsolutePath() + File.separator + "sonar-it"); + + // Verify temp folder is created + assertThat(new File(tempDir, "sonar-it")).isDirectory().exists(); + + orchestrator.stop(); + + // Verify temp folder is deleted after shutdown + assertThat(new File(tempDir, "sonar-it")).doesNotExist(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/serverSystem/ServerSystemTest.java b/tests/src/test/java/org/sonarqube/tests/serverSystem/ServerSystemTest.java new file mode 100644 index 00000000000..ad4adf46519 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/serverSystem/ServerSystemTest.java @@ -0,0 +1,223 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.serverSystem; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import okhttp3.Response; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.json.simple.JSONValue; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.ServerId.ShowWsResponse; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.ServerIdPage; +import util.ItUtils; +import util.user.UserRule; + +import static org.apache.commons.lang.StringUtils.startsWithAny; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.call; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newWsClient; +import static util.selenium.Selenese.runSelenese; + +public class ServerSystemTest { + + private static final String ADMIN_USER_LOGIN = "admin-user"; + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + @Before + public void initAdminUser() { + userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); + } + + @After + public void deleteAdminUser() { + userRule.resetUsers(); + } + + @Test + public void get_sonarqube_version() { + Map<String, Object> json = callStatus(); + + String version = (String) json.get("version"); + if (!startsWithAny(version, new String[] {"6."})) { + fail("Bad version: " + version); + } + } + + @Test + public void get_server_status() { + Map<String, Object> json = callStatus(); + assertThat(json.get("status")).isEqualTo("UP"); + } + + @Test + public void generate_server_id() throws IOException { + Navigation nav = Navigation.create(orchestrator).openHome().logIn().submitCredentials(ADMIN_USER_LOGIN); + String validIpAddress = getValidIpAddress(); + + nav.openServerId() + .setOrganization("Name with invalid chars like $") + .setIpAddress(validIpAddress) + .submitForm() + .assertError(); + + nav.openServerId() + .setOrganization("DEMO") + .setIpAddress("invalid_address") + .submitForm() + .assertError(); + + ServerIdPage page = nav.openServerId() + .setOrganization("DEMO") + .setIpAddress(validIpAddress) + .submitForm(); + + String serverId = page.serverIdInput().val(); + assertThat(serverId).isNotEmpty(); + } + + private Map<String, Object> callStatus() { + WsResponse statusResponse = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/system/status")); + return ItUtils.jsonToMap(statusResponse.content()); + } + + @Test + public void display_system_info() { + runSelenese(orchestrator, "/serverSystem/ServerSystemTest/system_info.html"); + } + + @Test + public void download_system_info() throws Exception { + waitForComputeEngineToBeUp(orchestrator); + + WsResponse response = newAdminWsClient(orchestrator).wsConnector().call( + new GetRequest("api/system/info")); + + assertThat(response.code()).isEqualTo(200); + + assertThat(response.content()).contains( + // SONAR-7436 monitor ES and CE + "\"Compute Engine Database Connection\":", "\"Compute Engine State\":", "\"Compute Engine Tasks\":", + "\"Elasticsearch\":", "\"State\":\"GREEN\"", + + // SONAR-7271 get settings + "\"Settings\":", "\"sonar.jdbc.url\":", "\"sonar.path.data\":"); + } + + private static void waitForComputeEngineToBeUp(Orchestrator orchestrator) throws IOException { + for (int i = 0; i < 10_000; i++) { + File logs = orchestrator.getServer().getCeLogs(); + if (FileUtils.readFileToString(logs).contains("Compute Engine is operational")) { + return; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignored + } + } + throw new IllegalStateException("Compute Engine is not operational"); + } + + /** + * See http://jira.codehaus.org/browse/SONAR-2727 + */ + @Test + public void display_warnings_when_using_h2() { + if (orchestrator.getConfiguration().getString("sonar.jdbc.dialect").equals("h2")) { + runSelenese(orchestrator, "/serverSystem/ServerSystemTest/derby-warning.html"); + } + } + + /** + * See http://jira.codehaus.org/browse/SONAR-2840 + */ + @Test + public void hide_jdbc_settings_to_non_admin() { + runSelenese(orchestrator, "/serverSystem/ServerSystemTest/hide-jdbc-settings.html"); + } + + @Test + public void http_response_should_be_gzipped() throws IOException { + String url = orchestrator.getServer().getUrl() + "/api/rules/search"; + Response metricsResponse = call(url); + assertThat(metricsResponse.isSuccessful()).as("Response code is %s", metricsResponse.code()).isTrue(); + assertThat(metricsResponse.header("Content-Encoding")).isNull(); + + Response homeResponse = call(url, "Accept-Encoding", "gzip, deflate"); + assertThat(homeResponse.isSuccessful()).as("Response code is %s", metricsResponse.code()).isTrue(); + assertThat(homeResponse.header("Content-Encoding")).isEqualToIgnoringCase("gzip"); + } + + /** + * SONAR-3962 + */ + // TODO should be moved elsewhere + @Test + public void not_fail_with_url_ending_by_jsp() { + orchestrator.executeBuild(SonarScanner.create(ItUtils.projectDir("shared/xoo-sample")) + .setProperty("sonar.projectKey", "myproject.jsp")); + // Access dashboard + runSelenese(orchestrator, "/serverSystem/ServerSystemTest/url_ending_by_jsp.html"); + } + + /** + * SONAR-5197 + */ + // TODO should be moved elsewhere + @Test + public void api_ws_shortcut() throws Exception { + Response response = call(orchestrator.getServer().getUrl() + "/api"); + assertThat(response.isSuccessful()).as("Response code is %s", response.code()).isTrue(); + String json = IOUtils.toString(response.body().byteStream()); + Map jsonAsMap = (Map) JSONValue.parse(json); + assertThat(jsonAsMap.get("webServices")).isNotNull(); + } + + private String getValidIpAddress() throws IOException { + WsClient adminWsClient = newAdminWsClient(orchestrator); + ShowWsResponse response = ShowWsResponse.parseFrom(adminWsClient.wsConnector().call( + new GetRequest("api/server_id/show").setMediaType(MediaTypes.PROTOBUF)).contentStream()); + assertThat(response.getValidIpAddressesCount()).isGreaterThan(0); + return response.getValidIpAddresses(0); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/DeprecatedPropertiesWsTest.java b/tests/src/test/java/org/sonarqube/tests/settings/DeprecatedPropertiesWsTest.java new file mode 100644 index 00000000000..4a0bd037333 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/DeprecatedPropertiesWsTest.java @@ -0,0 +1,417 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.google.common.base.Throwables; +import com.google.gson.Gson; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import okhttp3.Credentials; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.setting.SetRequest; +import org.sonarqube.ws.client.setting.SettingsService; +import util.user.UserRule; + +import static java.net.URLEncoder.encode; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.newWsClient; +import static util.ItUtils.resetSettings; +import static util.ItUtils.runProjectAnalysis; + +public class DeprecatedPropertiesWsTest { + + private final static String PROJECT_KEY = "com.sonarsource.it.samples:multi-modules-sample"; + private static final String MODULE_KEY = "com.sonarsource.it.samples:multi-modules-sample:module_a"; + private static final String SUB_MODULE_KEY = "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"; + + private static final String PROJECT_SETTING_KEY = "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay"; + + private static String USER_LOGIN = "john"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @ClassRule + public static UserRule userRule = UserRule.from(orchestrator); + + static WsClient adminWsClient; + static WsClient userWsClient; + static WsClient anonymousWsClient; + + static SettingsService adminSettingsService; + + @BeforeClass + public static void init() throws Exception { + orchestrator.resetData(); + userRule.createUser(USER_LOGIN, "password"); + adminWsClient = newAdminWsClient(orchestrator); + userWsClient = newUserWsClient(orchestrator, USER_LOGIN, "password"); + anonymousWsClient = newWsClient(orchestrator); + adminSettingsService = newAdminWsClient(orchestrator).settings(); + runProjectAnalysis(orchestrator, "shared/xoo-multi-modules-sample"); + } + + @AfterClass + public static void resetAfterClass() throws Exception { + doResetSettings(); + userRule.deactivateUsers(USER_LOGIN); + } + + @Before + public void resetBefore() throws Exception { + doResetSettings(); + } + + private static void doResetSettings() { + resetSettings(orchestrator, null, "some-property", "custom-property", "int", "multi", "boolean", "hidden", "not_defined", "setting.secured", "setting.license.secured", "list", + "undefined"); + resetSettings(orchestrator, PROJECT_KEY, PROJECT_SETTING_KEY, "sonar.coverage.exclusions", "project.setting"); + } + + @Test + public void get_default_global_value() throws Exception { + assertThat(getProperty("some-property", null).getValue()).isEqualTo("aDefaultValue"); + } + + @Test + public void get_global_value() throws Exception { + setProperty("some-property", "value", null); + + assertThat(getProperty("some-property", null).getValue()).isEqualTo("value"); + } + + @Test + public void get_multi_values() throws Exception { + setProperty("multi", asList("value1", "value2", "value,3"), null); + + Properties.Property setting = getProperty("multi", null); + assertThat(setting.getValue()).isEqualTo("value1,value2,value%2C3"); + assertThat(setting.getValues()).containsOnly("value1", "value2", "value,3"); + } + + @Test + public void get_hidden_setting() throws Exception { + setProperty("hidden", "value", null); + + assertThat(getProperty("hidden", null).getValue()).isEqualTo("value"); + } + + @Test + public void get_secured_setting() throws Exception { + setProperty("setting.secured", "value", null); + + assertThat(getProperty("setting.secured", null).getValue()).isEqualTo("value"); + } + + @Test + public void get_license_setting() throws Exception { + setProperty("setting.license.secured", "value", null); + + assertThat(getProperty("setting.license.secured", null).getValue()).isEqualTo("value"); + } + + @Test + public void get_not_defined_setting() throws Exception { + setProperty("not_defined", "value", null); + + assertThat(getProperty("not_defined", null).getValue()).isEqualTo("value"); + } + + @Test + public void secured_setting_not_returned_to_not_admin() throws Exception { + setProperty("setting.secured", "value", null); + + // Admin can see the secured setting + assertThat(getProperties(null)).extracting(Properties.Property::getKey).contains("setting.secured"); + + // Not admin cannot see the secured setting + assertThat(getProperties(userWsClient, null)).extracting(Properties.Property::getKey).doesNotContain("setting.secured"); + assertThat(getProperties(anonymousWsClient, null)).extracting(Properties.Property::getKey).doesNotContain("setting.secured"); + } + + @Test + public void license_setting_not_returned_to_not_logged() throws Exception { + setProperty("setting.license.secured", "value", null); + + // Admin and user can see the license setting + assertThat(getProperties(null)).extracting(Properties.Property::getKey).contains("setting.license.secured"); + assertThat(getProperties(userWsClient, null)).extracting(Properties.Property::getKey).contains("setting.license.secured"); + + // Anonymous cannot see the license setting + assertThat(getProperties(anonymousWsClient, null)).extracting(Properties.Property::getKey).doesNotContain("setting.license.secured"); + } + + @Test + public void get_all_global_settings() throws Exception { + List<Properties.Property> properties = getProperties(null); + assertThat(properties).isNotEmpty(); + assertThat(properties).extracting("key") + .contains("sonar.core.id", "some-property", "boolean") + .doesNotContain("hidden"); + } + + @Test + public void get_default_component_value() throws Exception { + // Check default value is returned + assertThat(getProperty(PROJECT_SETTING_KEY, PROJECT_KEY).getValue()).isEqualTo("24"); + assertThat(getProperty(PROJECT_SETTING_KEY, MODULE_KEY).getValue()).isEqualTo("24"); + assertThat(getProperty(PROJECT_SETTING_KEY, SUB_MODULE_KEY).getValue()).isEqualTo("24"); + } + + @Test + public void get_global_component_value() throws Exception { + // Check global value is returned + setProperty(PROJECT_SETTING_KEY, "30", null); + assertThat(getProperty(PROJECT_SETTING_KEY, PROJECT_KEY).getValue()).isEqualTo("30"); + assertThat(getProperty(PROJECT_SETTING_KEY, MODULE_KEY).getValue()).isEqualTo("30"); + assertThat(getProperty(PROJECT_SETTING_KEY, SUB_MODULE_KEY).getValue()).isEqualTo("30"); + } + + @Test + public void get_component_value() throws Exception { + setProperty("sonar.coverage.exclusions", asList("file"), PROJECT_KEY); + + assertThat(getProperty("sonar.coverage.exclusions", PROJECT_KEY).getValue()).isEqualTo("file"); + } + + @Test + public void get_global_value_when_component_is_unknown() throws Exception { + setProperty("some-property", "value", null); + + assertThat(getProperty("some-property", PROJECT_KEY).getValue()).isEqualTo("value"); + } + + @Test + public void get_all_component_settings() throws Exception { + List<Properties.Property> properties = getProperties(PROJECT_KEY); + assertThat(properties).isNotEmpty(); + assertThat(properties).extracting("key") + .contains("sonar.dbcleaner.cleanDirectory", "sonar.dbcleaner.weeksBeforeDeletingAllSnapshots") + .doesNotContain("hidden"); + } + + @Test + public void get_global_value_using_id_parameter() throws Exception { + setProperty("some-property", "value", null); + + assertThat(getProperty(adminWsClient, "some-property", null, true).getValue()).isEqualTo("value"); + } + + @Test + public void put_property() throws Exception { + putProperty("some-property", "some-value", null, false); + + assertThat(getProperty("some-property", null).getValue()).isEqualTo("some-value"); + } + + @Test + public void put_property_using_id_parameter() throws Exception { + putProperty("some-property", "some-value", null, true); + + assertThat(getProperty("some-property", null).getValue()).isEqualTo("some-value"); + } + + @Test + public void put_property_on_project() throws Exception { + putProperty("project.setting", "some-value", PROJECT_KEY, false); + + assertThat(getProperty("project.setting", PROJECT_KEY).getValue()).isEqualTo("some-value"); + } + + @Test + public void put_property_for_undefined_setting() throws Exception { + putProperty("undefined", "some-value", null, false); + + assertThat(getProperty("undefined", null).getValue()).isEqualTo("some-value"); + } + + @Test + public void put_property_multi_values() throws Exception { + putProperty("multi", "value1,value2,value3", null, false); + + Properties.Property setting = getProperty("multi", null); + assertThat(setting.getValue()).isEqualTo("value1,value2,value3"); + assertThat(setting.getValues()).containsOnly("value1", "value2", "value3"); + } + + @Test + public void fail_with_error_400_when_put_property_without_id() throws Exception { + Response response = putProperty("", "some-value", null, false); + assertThat(response.code()).isEqualTo(400); + } + + @Test + public void delete_property() throws Exception { + setProperty("custom-property", "value", null); + + deleteProperty("custom-property", null, false); + + assertThat(getProperty("custom-property", null)).isNull(); + } + + @Test + public void delete_property_using_id_parameter() throws Exception { + setProperty("custom-property", "value", null); + + deleteProperty("custom-property", null, true); + + assertThat(getProperty("custom-property", null)).isNull(); + } + + @Test + public void delete_property_on_project() throws Exception { + setProperty("project.setting", "value", PROJECT_KEY); + + deleteProperty("project.setting", PROJECT_KEY, false); + + assertThat(getProperty("project.setting", PROJECT_KEY)).isNull(); + } + + private static void setProperty(String key, String value, @Nullable String componentKey) { + adminSettingsService.set(SetRequest.builder().setKey(key).setValue(value).setComponent(componentKey).build()); + } + + private static void setProperty(String key, List<String> values, @Nullable String componentKey) { + adminSettingsService.set(SetRequest.builder().setKey(key).setValues(values).setComponent(componentKey).build()); + } + + private static List<Properties.Property> getProperties(@Nullable String componentKey) { + return getProperties(adminWsClient, componentKey); + } + + private static List<Properties.Property> getProperties(WsClient wsClient, @Nullable String componentKey) { + WsResponse response = wsClient.wsConnector() + .call(new GetRequest("api/properties") + .setParam("resource", componentKey)) + .failIfNotSuccessful(); + return asList(Properties.parse(response.content())); + } + + private static Properties.Property getProperty(String key, @Nullable String componentKey) throws UnsupportedEncodingException { + return getProperty(adminWsClient, key, componentKey, false); + } + + @CheckForNull + private static Properties.Property getProperty(WsClient wsClient, String key, @Nullable String componentKey, boolean useIdParameter) throws UnsupportedEncodingException { + GetRequest getRequest = useIdParameter ? new GetRequest("api/properties").setParam("id", encode(key, "UTF-8")).setParam("resource", componentKey) + : new GetRequest("api/properties/" + encode(key, "UTF-8")).setParam("resource", componentKey); + WsResponse response = wsClient.wsConnector() + .call(getRequest) + .failIfNotSuccessful(); + Properties.Property[] properties = Properties.parse(response.content()); + return Arrays.stream(properties).findFirst().orElseGet(() -> null); + } + + private static Response putProperty(String key, String value, @Nullable String componentKey, boolean useIdParameter) throws UnsupportedEncodingException { + String url = useIdParameter ? orchestrator.getServer().getUrl() + "/api/properties?id=" + encode(key, "UTF-8") + "&value=" + value + : orchestrator.getServer().getUrl() + "/api/properties/" + encode(key, "UTF-8") + "?value=" + value; + url += componentKey != null ? "&resource=" + componentKey : ""; + return call(new Request.Builder() + .put(new FormBody.Builder().build()) + .url(url)); + } + + private static Response deleteProperty(String key, @Nullable String componentKey, boolean useIdParameter) throws UnsupportedEncodingException { + String url = useIdParameter ? orchestrator.getServer().getUrl() + "/api/properties?id=" + encode(key, "UTF-8") + : orchestrator.getServer().getUrl() + "/api/properties/" + encode(key, "UTF-8"); + url += componentKey != null ? "?resource=" + componentKey : ""; + return call(new Request.Builder() + .delete(new FormBody.Builder().build()) + .url(url)); + } + + private static Response call(Request.Builder requestBuilder) { + try { + requestBuilder.header("Authorization", Credentials.basic("admin", "admin")); + return new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + .newCall(requestBuilder.build()) + .execute(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public static class Properties { + + private List<Property> properties; + + private Properties(List<Property> properties) { + this.properties = properties; + } + + public List<Property> getProperties() { + return properties; + } + + public static Property[] parse(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, Property[].class); + } + + public static class Property { + private final String key; + private final String value; + private final String[] values; + + private Property(String key, String value, String[] values) { + this.key = key; + this.value = value; + this.values = values; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public String[] getValues() { + return values; + } + } + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/EmailsTest.java b/tests/src/test/java/org/sonarqube/tests/settings/EmailsTest.java new file mode 100644 index 00000000000..86967abb6c2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/EmailsTest.java @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import java.util.Iterator; +import javax.annotation.Nullable; +import javax.mail.internet.MimeMessage; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.Settings; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.setting.SettingsService; +import org.sonarqube.ws.client.setting.ValuesRequest; +import org.subethamail.wiser.Wiser; +import org.subethamail.wiser.WiserMessage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.resetEmailSettings; +import static util.ItUtils.setServerProperty; + +public class EmailsTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + static Wiser SMTP_SERVER; + static WsClient ADMIN_WS_CLIENT; + static SettingsService SETTINGS; + + @BeforeClass + public static void before() throws Exception { + ADMIN_WS_CLIENT = newAdminWsClient(orchestrator); + SETTINGS = ADMIN_WS_CLIENT.settings(); + + SMTP_SERVER = new Wiser(0); + SMTP_SERVER.start(); + System.out.println("SMTP Server port: " + SMTP_SERVER.getServer().getPort()); + } + + @AfterClass + public static void stop() { + if (SMTP_SERVER != null) { + SMTP_SERVER.stop(); + } + resetEmailSettings(orchestrator); + } + + @Before + public void prepare() { + orchestrator.resetData(); + SMTP_SERVER.getMessages().clear(); + resetEmailSettings(orchestrator); + } + + @Test + public void update_email_settings() throws Exception { + updateEmailSettings("localhost", "42", "noreply@email.com", "[EMAIL]", "ssl", "john", "123456"); + + Settings.ValuesWsResponse response = SETTINGS.values(ValuesRequest.builder() + .setKeys("email.smtp_host.secured", "email.smtp_port.secured", "email.smtp_secure_connection.secured", "email.smtp_username.secured", "email.smtp_password.secured", + "email.from", "email.prefix") + .build()); + + assertThat(response.getSettingsList()).extracting(Settings.Setting::getKey, Settings.Setting::getValue) + .containsOnly( + tuple("email.smtp_host.secured", "localhost"), + tuple("email.smtp_port.secured", "42"), + tuple("email.smtp_secure_connection.secured", "ssl"), + tuple("email.smtp_username.secured", "john"), + tuple("email.smtp_password.secured", "123456"), + tuple("email.from", "noreply@email.com"), + tuple("email.prefix", "[EMAIL]")); + } + + @Test + public void send_test_email() throws Exception { + updateEmailSettings("localhost", Integer.toString(SMTP_SERVER.getServer().getPort()), null, null, null, null, null); + + sendEmail("test@example.org", "Test Message from SonarQube", "This is a test message from SonarQube"); + + // We need to wait until all notifications will be delivered + waitUntilAllNotificationsAreDelivered(); + Iterator<WiserMessage> emails = SMTP_SERVER.getMessages().iterator(); + MimeMessage message = emails.next().getMimeMessage(); + assertThat(message.getHeader("To", null)).isEqualTo("<test@example.org>"); + assertThat(message.getSubject()).contains("Test Message from SonarQube"); + assertThat((String) message.getContent()).contains("This is a test message from SonarQube"); + assertThat(emails.hasNext()).isFalse(); + } + + private static void waitUntilAllNotificationsAreDelivered() throws InterruptedException { + Thread.sleep(10000); + } + + private static void updateEmailSettings(@Nullable String host, @Nullable String port, @Nullable String from, @Nullable String prefix, @Nullable String secure, + @Nullable String username, @Nullable String password) { + setServerProperty(orchestrator, "email.smtp_host.secured", host); + setServerProperty(orchestrator, "email.smtp_port.secured", port); + setServerProperty(orchestrator, "email.smtp_secure_connection.secured", secure); + setServerProperty(orchestrator, "email.smtp_username.secured", username); + setServerProperty(orchestrator, "email.smtp_password.secured", password); + setServerProperty(orchestrator, "email.from", from); + setServerProperty(orchestrator, "email.prefix", prefix); + } + + private static void sendEmail(String to, String subject, String message) { + ADMIN_WS_CLIENT.wsConnector().call( + new PostRequest("/api/emails/send") + .setParam("to", to) + .setParam("subject", subject) + .setParam("message", message)) + .failIfNotSuccessful(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/LicensesPageTest.java b/tests/src/test/java/org/sonarqube/tests/settings/LicensesPageTest.java new file mode 100644 index 00000000000..5f0ebe0078d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/LicensesPageTest.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.Settings.ValuesWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.setting.ValuesRequest; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.licenses.LicenseItem; +import org.sonarqube.pageobjects.licenses.LicensesPage; +import util.user.UserRule; + +import static com.codeborne.selenide.Condition.text; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.pluginArtifact; + +public class LicensesPageTest { + private static Orchestrator orchestrator; + private static WsClient wsClient; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private String adminUser; + + @BeforeClass + public static void start() { + orchestrator = Orchestrator.builderEnv() + .addPlugin(pluginArtifact("license-plugin")) + .build(); + orchestrator.start(); + + wsClient = newAdminWsClient(orchestrator); + } + + @AfterClass + public static void stop() { + if (orchestrator != null) { + orchestrator.stop(); + } + } + + @Before + public void before() { + adminUser = userRule.createAdminUser(); + } + + @Test + public void display_licenses() { + LicensesPage page = Navigation.create(orchestrator).logIn().submitCredentials(adminUser).openLicenses(); + + page.getLicenses().shouldHaveSize(2); + page.getLicensesAsItems().get(0).getName().shouldHave(text("Typed property")); + page.getLicensesAsItems().get(1).getName().shouldHave(text("Property without license type")); + } + + @Test + public void change_licenses() { + String EXAMPLE_LICENSE = "TmFtZTogRGV2ZWxvcHBlcnMKUGx1Z2luOiBhdXRvY29udHJvbApFeHBpcmVzOiAyMDEyLTA0LTAxCktleTogNjI5N2MxMzEwYzg2NDZiZTE5MDU1MWE4ZmZmYzk1OTBmYzEyYTIyMgo="; + + LicensesPage page = Navigation.create(orchestrator).logIn().submitCredentials(adminUser).openLicenses(); + LicenseItem licenseItem = page.getLicenseByKey("typed.license.secured"); + licenseItem.setLicense(EXAMPLE_LICENSE); + + ValuesWsResponse response = wsClient.settings() + .values(ValuesRequest.builder().setKeys("typed.license.secured").build()); + assertThat(response.getSettings(0).getValue()).isEqualTo(EXAMPLE_LICENSE); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/PropertySetsTest.java b/tests/src/test/java/org/sonarqube/tests/settings/PropertySetsTest.java new file mode 100644 index 00000000000..d30b68168e3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/PropertySetsTest.java @@ -0,0 +1,153 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.Settings; +import org.sonarqube.ws.client.setting.SetRequest; +import org.sonarqube.ws.client.setting.SettingsService; +import org.sonarqube.ws.client.setting.ValuesRequest; +import org.sonarqube.pageobjects.Navigation; +import org.sonarqube.pageobjects.settings.SettingsPage; +import util.user.UserRule; + +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.resetSettings; + +public class PropertySetsTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private Navigation nav = Navigation.create(orchestrator); + + static SettingsService SETTINGS; + private String adminUser; + + @BeforeClass + public static void initSettingsService() throws Exception { + SETTINGS = newAdminWsClient(orchestrator).settings(); + } + + @Before + public void before() { + adminUser = userRule.createAdminUser(); + } + + @After + public void reset_settings() throws Exception { + resetSettings(orchestrator, null, "sonar.demo", "sonar.autogenerated", "sonar.test.jira.servers"); + } + + @Test + public void support_property_sets() throws UnsupportedEncodingException { + SettingsPage page = nav.logIn().submitCredentials(adminUser).openSettings(null).openCategory("DEV") + .assertSettingDisplayed("sonar.test.jira.servers"); + + page.getPropertySetInput("sonar.test.jira.servers") + .setFieldValue("key", "jira1") + .setFieldValue("url", "http://jira") + .setFieldValue("port", "12345") + .save(); + + assertPropertySet("sonar.test.jira.servers", asList( + entry("key", "jira1"), + entry("url", "http://jira"), + entry("port", "12345"))); + } + + @Test + public void support_property_sets_with_auto_generated_keys() throws UnsupportedEncodingException { + SettingsPage page = nav.logIn().submitCredentials(adminUser).openSettings(null).openCategory("DEV") + .assertSettingDisplayed("sonar.autogenerated"); + + page.getPropertySetInput("sonar.autogenerated") + .setFieldValue(0, "value", "FIRST") + .setFieldValue(1, "value", "SECOND") + .setFieldValue(2, "value", "THIRD") + .save(); + + assertPropertySet("sonar.autogenerated", + asList(entry("value", "FIRST")), + asList(entry("value", "SECOND")), + asList(entry("value", "THIRD"))); + } + + @Test + public void edit_property_set() { + SETTINGS.set(SetRequest.builder() + .setKey("sonar.test.jira.servers") + .setFieldValues(newArrayList( + "{\"key\":\"jira1\", \"url\":\"http://jira1\", \"port\":\"12345\"}", + "{\"key\":\"jira2\", \"url\":\"http://jira2\", \"port\":\"54321\"}")) + .build()); + + assertPropertySet("sonar.test.jira.servers", + asList(entry("key", "jira1"), entry("url", "http://jira1"), entry("port", "12345")), + asList(entry("key", "jira2"), entry("url", "http://jira2"), entry("port", "54321"))); + } + + @Test + public void delete_property_set() throws Exception { + SETTINGS.set(SetRequest.builder() + .setKey("sonar.test.jira.servers") + .setFieldValues(newArrayList("{\"url\":\"http://jira1\"}", "{\"port\":\"12345\"}")) + .build()); + + resetSettings(orchestrator, null, "sonar.test.jira.servers"); + + assertThat(SETTINGS.values(ValuesRequest.builder().setKeys("sonar.test.jira.servers").build()).getSettingsList()).isEmpty(); + } + + private void assertPropertySet(String baseSettingKey, List<Map.Entry<String, String>>... fieldsValues) { + Settings.Setting setting = getSetting(baseSettingKey); + assertThat(setting.getFieldValues().getFieldValuesList()).hasSize(fieldsValues.length); + int index = 0; + for (Settings.FieldValues.Value fieldValue : setting.getFieldValues().getFieldValuesList()) { + assertThat(fieldValue.getValue()).containsOnly(fieldsValues[index].toArray(new Map.Entry[] {})); + index++; + } + } + + private Settings.Setting getSetting(String key) { + Settings.ValuesWsResponse response = SETTINGS.values(ValuesRequest.builder().setKeys(key).build()); + List<Settings.Setting> settings = response.getSettingsList(); + assertThat(settings).hasSize(1); + return settings.get(0); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/SettingsTest.java b/tests/src/test/java/org/sonarqube/tests/settings/SettingsTest.java new file mode 100644 index 00000000000..de7d213db2b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/SettingsTest.java @@ -0,0 +1,208 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.google.common.collect.ImmutableMap; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import java.io.IOException; +import java.util.List; +import javax.annotation.CheckForNull; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.Settings; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.permission.AddGroupWsRequest; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.permission.RemoveGroupWsRequest; +import org.sonarqube.ws.client.setting.ResetRequest; +import org.sonarqube.ws.client.setting.SetRequest; +import org.sonarqube.ws.client.setting.SettingsService; +import org.sonarqube.ws.client.setting.ValuesRequest; +import util.user.UserRule; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonarqube.ws.Settings.Setting; +import static org.sonarqube.ws.Settings.ValuesWsResponse; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.newWsClient; +import static util.ItUtils.resetSettings; + +public class SettingsTest { + + /** + * This setting is defined by server-plugin + */ + private final static String PLUGIN_SETTING_KEY = "some-property"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @ClassRule + public static UserRule userRule = UserRule.from(orchestrator); + + private static WsClient adminWsClient; + private static SettingsService anonymousSettingsService; + private static SettingsService userSettingsService; + private static SettingsService scanSettingsService; + private static SettingsService adminSettingsService; + + @BeforeClass + public static void initSettingsService() throws Exception { + userRule.createUser("setting-user", "setting-user"); + userRule.createUser("scanner-user", "scanner-user"); + adminWsClient = newAdminWsClient(orchestrator); + // Remove 'Execute Analysis' permission from anyone + adminWsClient.permissions().removeGroup(new RemoveGroupWsRequest().setGroupName("anyone").setPermission("scan")); + + // Anonymous user, without 'Execute Analysis' permission + anonymousSettingsService = newWsClient(orchestrator).settings(); + + // Authenticated user, without 'Execute Analysis' permission + userSettingsService = newUserWsClient(orchestrator, "setting-user", "setting-user").settings(); + + // User with 'Execute Analysis' permission + adminWsClient.permissions().addUser(new AddUserWsRequest().setLogin("scanner-user").setPermission("scan")); + scanSettingsService = newUserWsClient(orchestrator, "scanner-user", "scanner-user").settings(); + + // User with 'Administer System' permission but without 'Execute Analysis' permission + adminSettingsService = adminWsClient.settings(); + } + + @AfterClass + public static void tearDown() throws Exception { + userRule.deactivateUsers("setting-user", "scanner-user"); + // Restore 'Execute Analysis' permission to anyone + adminWsClient.permissions().addGroup(new AddGroupWsRequest().setGroupName("anyone").setPermission("scan")); + } + + @After + public void reset_settings() throws Exception { + resetSettings(orchestrator, null, PLUGIN_SETTING_KEY, "globalPropertyChange.received", "hidden", "setting.secured", "setting.license.secured"); + } + + /** + * SONAR-3320 + */ + @Test + public void global_property_change_extension_point() throws IOException { + adminSettingsService.set(SetRequest.builder().setKey("globalPropertyChange.received").setValue("NEWVALUE").build()); + assertThat(FileUtils.readFileToString(orchestrator.getServer().getWebLogs())) + .contains("Received change: [key=globalPropertyChange.received, newValue=NEWVALUE]"); + } + + @Test + public void get_default_value() { + Setting setting = getSetting(PLUGIN_SETTING_KEY, anonymousSettingsService); + assertThat(setting.getValue()).isEqualTo("aDefaultValue"); + assertThat(setting.getInherited()).isTrue(); + } + + @Test + public void set_setting() { + adminSettingsService.set(SetRequest.builder().setKey(PLUGIN_SETTING_KEY).setValue("some value").build()); + + String value = getSetting(PLUGIN_SETTING_KEY, anonymousSettingsService).getValue(); + assertThat(value).isEqualTo("some value"); + } + + @Test + public void remove_setting() { + adminSettingsService.set(SetRequest.builder().setKey(PLUGIN_SETTING_KEY).setValue("some value").build()); + adminSettingsService.set(SetRequest.builder().setKey("sonar.links.ci").setValue("http://localhost").build()); + + adminSettingsService.reset(ResetRequest.builder().setKeys(PLUGIN_SETTING_KEY, "sonar.links.ci").build()); + assertThat(getSetting(PLUGIN_SETTING_KEY, anonymousSettingsService).getValue()).isEqualTo("aDefaultValue"); + assertThat(getSetting("sonar.links.ci", anonymousSettingsService)).isNull(); + } + + @Test + public void hidden_setting() { + adminSettingsService.set(SetRequest.builder().setKey("hidden").setValue("test").build()); + assertThat(getSetting("hidden", anonymousSettingsService).getValue()).isEqualTo("test"); + } + + @Test + public void secured_setting() { + adminSettingsService.set(SetRequest.builder().setKey("setting.secured").setValue("test").build()); + assertThat(getSetting("setting.secured", anonymousSettingsService)).isNull(); +// assertThat(getSetting("setting.secured", userSettingsService)).isNull(); + assertThat(getSetting("setting.secured", scanSettingsService).getValue()).isEqualTo("test"); + assertThat(getSetting("setting.secured", adminSettingsService).getValue()).isEqualTo("test"); + } + + @Test + public void license_setting() { + adminSettingsService.set(SetRequest.builder().setKey("setting.license.secured").setValue("test").build()); + assertThat(getSetting("setting.license.secured", anonymousSettingsService)).isNull(); + assertThat(getSetting("setting.license.secured", userSettingsService).getValue()).isEqualTo("test"); + assertThat(getSetting("setting.license.secured", scanSettingsService).getValue()).isEqualTo("test"); + assertThat(getSetting("setting.license.secured", adminSettingsService).getValue()).isEqualTo("test"); + } + + @Test + public void multi_values_setting() throws Exception { + adminSettingsService.set(SetRequest.builder().setKey("multi").setValues(asList("value1", "value2", "value3")).build()); + assertThat(getSetting("multi", anonymousSettingsService).getValues().getValuesList()).containsOnly("value1", "value2", "value3"); + } + + @Test + public void property_set_setting() throws Exception { + adminSettingsService.set(SetRequest.builder().setKey("sonar.jira").setFieldValues(asList( + "{\"key\":\"jira1\", \"url\":\"http://jira1\", \"port\":\"12345\", \"type\":\"A\"}", + "{\"key\":\"jira2\", \"url\":\"http://jira2\", \"port\":\"54321\"}")) + .build()); + + assertThat(getSetting("sonar.jira", anonymousSettingsService).getFieldValues().getFieldValuesList()).extracting(Settings.FieldValues.Value::getValue).containsOnly( + ImmutableMap.of("key", "jira1", "url", "http://jira1", "port", "12345", "type", "A"), + ImmutableMap.of("key", "jira2", "url", "http://jira2", "port", "54321")); + } + + @Test + public void return_defined_settings_when_no_key_provided() throws Exception { + adminSettingsService.set(SetRequest.builder().setKey(PLUGIN_SETTING_KEY).setValue("some value").build()); + adminSettingsService.set(SetRequest.builder().setKey("hidden").setValue("test").build()); + + assertThat(adminSettingsService.values(ValuesRequest.builder().build()).getSettingsList()) + .extracting(Setting::getKey) + .contains(PLUGIN_SETTING_KEY, "hidden", "sonar.forceAuthentication", + // Settings for scanner + "sonar.core.startTime"); + + assertThat(adminSettingsService.values(ValuesRequest.builder().build()).getSettingsList()) + .extracting(Setting::getKey, Setting::getValue) + .contains(tuple(PLUGIN_SETTING_KEY, "some value"), tuple("hidden", "test")); + } + + @CheckForNull + private static Setting getSetting(String key, SettingsService settingsService) { + ValuesWsResponse response = settingsService.values(ValuesRequest.builder().setKeys(key).build()); + List<Settings.Setting> settings = response.getSettingsList(); + return settings.isEmpty() ? null : settings.get(0); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/SettingsTestRestartingOrchestrator.java b/tests/src/test/java/org/sonarqube/tests/settings/SettingsTestRestartingOrchestrator.java new file mode 100644 index 00000000000..60d690257e9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/SettingsTestRestartingOrchestrator.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonarqube.pageobjects.EncryptionPage; +import org.sonarqube.pageobjects.Navigation; +import util.user.UserRule; + +import static com.codeborne.selenide.Condition.visible; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.projectDir; +import static util.ItUtils.xooPlugin; + +/** + * This class start a new orchestrator on each test case + */ +public class SettingsTestRestartingOrchestrator { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Orchestrator orchestrator; + + private UserRule userRule; + + @After + public void stop() { + if (orchestrator != null) { + userRule.resetUsers(); + orchestrator.stop(); + } + } + + @Test + public void test_settings() throws UnsupportedEncodingException { + URL secretKeyUrl = getClass().getResource("/settings/SettingsTest/sonar-secret.txt"); + orchestrator = Orchestrator.builderEnv() + .addPlugin(pluginArtifact("settings-plugin")) + .addPlugin(pluginArtifact("license-plugin")) + .setServerProperty("sonar.secretKeyPath", secretKeyUrl.getFile()) + .build(); + startOrchestrator(); + + String adminUser = userRule.createAdminUser(); + Navigation nav = Navigation.create(orchestrator).openHome().logIn().submitCredentials(adminUser); + + nav.openSettings(null) + .assertMenuContains("General") + .assertSettingDisplayed("sonar.dbcleaner.cleanDirectory") + .assertSettingNotDisplayed("settings.extension.hidden") + .assertSettingNotDisplayed("settings.extension.global"); + + EncryptionPage encryptionPage = nav.openEncryption(); + assertThat(encryptionPage.encryptValue("clear")).isEqualTo("{aes}4aQbfYe1lrEjiRzv/ETbyg=="); + encryptionPage.generateNewKey(); + encryptionPage.generationForm().shouldBe(visible).submit(); + encryptionPage.generationForm().shouldNotBe(visible); + encryptionPage.newSecretKey().shouldBe(visible); + } + + @Test + public void property_relocation() throws UnsupportedEncodingException { + orchestrator = Orchestrator.builderEnv() + .addPlugin(pluginArtifact("property-relocation-plugin")) + .addPlugin(xooPlugin()) + .setServerProperty("sonar.deprecatedKey", "true") + .build(); + startOrchestrator(); + + SonarScanner withDeprecatedKey = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.deprecatedKey", "true"); + SonarScanner withNewKey = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.newKey", "true"); + // should not fail + orchestrator.executeBuilds(withDeprecatedKey, withNewKey); + + String adminUser = userRule.createAdminUser(); + Navigation.create(orchestrator).openHome().logIn().submitCredentials(adminUser).openSettings(null) + .assertMenuContains("General") + .assertSettingDisplayed("sonar.newKey") + .assertSettingNotDisplayed("sonar.deprecatedKey"); + } + + private void startOrchestrator() { + orchestrator.start(); + userRule = UserRule.from(orchestrator); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/sourceCode/EncodingTest.java b/tests/src/test/java/org/sonarqube/tests/sourceCode/EncodingTest.java new file mode 100644 index 00000000000..985a361fb90 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/sourceCode/EncodingTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.sourceCode; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; + +import static util.ItUtils.runProjectAnalysis; + +public class EncodingTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void support_japanese_charset() { + runProjectAnalysis(orchestrator, "sourceCode/japanese-charset", "sonar.sourceEncoding", "Shift_JIS"); + + tester.runHtmlTests("/sourceCode/EncodingTest/japanese_sources.html"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/sourceCode/HighlightingTest.java b/tests/src/test/java/org/sonarqube/tests/sourceCode/HighlightingTest.java new file mode 100644 index 00000000000..6e682a74176 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/sourceCode/HighlightingTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.sourceCode; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; + +import static util.ItUtils.runProjectAnalysis; + +public class HighlightingTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void highlight_source_code_and_symbols_usage() { + runProjectAnalysis(orchestrator, "highlighting/xoo-sample-with-highlighting-v2"); + + // SONAR-3893 & SONAR-4247 + tester.runHtmlTests("/sourceCode/HighlightingTest/syntax-highlighting.html"); + + // SONAR-4249 & SONAR-4250 + tester.runHtmlTests("/sourceCode/HighlightingTest/symbol-usages-highlighting.html"); + } + + // Check that E/S index is updated when file content is unchanged but plugin generates different syntax/symbol highlighting + @Test + public void update_highlighting_even_when_code_unchanged() { + runProjectAnalysis(orchestrator, "highlighting/xoo-sample-with-highlighting-v1"); + + tester.runHtmlTests("/sourceCode/HighlightingTest/syntax-highlighting-v1.html"); + + runProjectAnalysis(orchestrator, "highlighting/xoo-sample-with-highlighting-v2"); + + tester.runHtmlTests("/sourceCode/HighlightingTest/syntax-highlighting-v2.html"); + tester.runHtmlTests("/sourceCode/HighlightingTest/symbol-usages-highlighting.html"); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/sourceCode/ProjectCodeTest.java b/tests/src/test/java/org/sonarqube/tests/sourceCode/ProjectCodeTest.java new file mode 100644 index 00000000000..b79114be2ac --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/sourceCode/ProjectCodeTest.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.sourceCode; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category1Suite; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; + +import static util.ItUtils.projectDir; + +public class ProjectCodeTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void test_project_code_page() { + executeBuild("shared/xoo-sample", "project-for-code", "Project For Code"); + + tester.runHtmlTests( + "/sourceCode/ProjectCodeTest/test_project_code_page.html", + "/sourceCode/ProjectCodeTest/search.html", + "/sourceCode/ProjectCodeTest/permalink.html"); + } + + @Test + public void code_page_should_expand_root_dir() { + executeBuild("shared/xoo-sample-with-root-dir", "project-for-code-root-dir", "Project For Code"); + + tester.runHtmlTests("/sourceCode/ProjectCodeTest/code_page_should_expand_root_dir.html"); + } + + private void executeBuild(String projectLocation, String projectKey, String projectName) { + orchestrator.executeBuild( + SonarScanner.create(projectDir(projectLocation)) + .setProjectKey(projectKey) + .setProjectName(projectName)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/test/CoverageTest.java b/tests/src/test/java/org/sonarqube/tests/test/CoverageTest.java new file mode 100644 index 00000000000..1680429d0b0 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/test/CoverageTest.java @@ -0,0 +1,221 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.test; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.io.File; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.TrueFileFilter; +import org.apache.commons.lang.StringUtils; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.sonarqube.tests.Tester; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.projectDir; + +public class CoverageTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + private static final String[] ALL_COVERAGE_METRICS = new String[] { + "line_coverage", "lines_to_cover", "uncovered_lines", "branch_coverage", "conditions_to_cover", "uncovered_conditions", "coverage", + "it_line_coverage", "it_lines_to_cover", "it_uncovered_lines", "it_branch_coverage", "it_conditions_to_cover", "it_uncovered_conditions", "it_coverage", + "overall_line_coverage", "overall_lines_to_cover", "overall_uncovered_lines", "overall_branch_coverage", "overall_conditions_to_cover", "overall_uncovered_conditions", + "overall_coverage" + }; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void coverage() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-ut-coverage"))); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "sample-ut-coverage", ALL_COVERAGE_METRICS); + assertThat(measures.get("line_coverage")).isEqualTo(50.0); + assertThat(measures.get("lines_to_cover")).isEqualTo(4d); + assertThat(measures.get("uncovered_lines")).isEqualTo(2d); + assertThat(measures.get("branch_coverage")).isEqualTo(50.0); + assertThat(measures.get("conditions_to_cover")).isEqualTo(2d); + assertThat(measures.get("uncovered_conditions")).isEqualTo(1d); + assertThat(measures.get("coverage")).isEqualTo(50.0); + + assertThat(measures.get("it_coverage")).isNull(); + + assertThat(measures.get("overall_coverage")).isNull(); + + String coverage = cleanupScmAndDuplication(orchestrator.getServer().adminWsClient().get("api/sources/lines", "key", "sample-ut-coverage:src/main/xoo/sample/Sample.xoo")); + // Use strict checking to be sure IT coverage is not present + JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/CoverageTest/unit_test_coverage-expected.json"), "UTF-8"), coverage, true); + + verifyComputeEngineTempDirIsEmpty(); + } + + private String cleanupScmAndDuplication(String coverage) { + coverage = StringUtils.remove(coverage, ",\"scmAuthor\":\"\""); + coverage = StringUtils.remove(coverage, ",\"scmRevision\":\"\""); + coverage = StringUtils.remove(coverage, ",\"duplicated\":false"); + return coverage; + } + + @Test + public void coverage_no_condition() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-ut-coverage-no-condition"))); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "sample-ut-coverage", ALL_COVERAGE_METRICS); + assertThat(measures.get("line_coverage")).isEqualTo(50.0); + assertThat(measures.get("lines_to_cover")).isEqualTo(4d); + assertThat(measures.get("uncovered_lines")).isEqualTo(2d); + assertThat(measures.get("branch_coverage")).isNull(); + assertThat(measures.get("conditions_to_cover")).isNull(); + assertThat(measures.get("uncovered_conditions")).isNull(); + assertThat(measures.get("coverage")).isEqualTo(50.0); + + assertThat(measures.get("it_coverage")).isNull(); + + assertThat(measures.get("overall_coverage")).isNull(); + + String coverage = cleanupScmAndDuplication(orchestrator.getServer().adminWsClient().get("api/sources/lines", "key", "sample-ut-coverage:src/main/xoo/sample/Sample.xoo")); + // Use strict checking to be sure IT coverage is not present + JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/CoverageTest/unit_test_coverage_no_condition-expected.json"), "UTF-8"), coverage, + true); + + verifyComputeEngineTempDirIsEmpty(); + } + + @Test + public void it_coverage_imported_as_coverage() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-it-coverage"))); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "sample-it-coverage", ALL_COVERAGE_METRICS); + + // Since SQ 6.2 all coverage reports are merged as coverage + + assertThat(measures.get("line_coverage")).isEqualTo(50.0); + assertThat(measures.get("lines_to_cover")).isEqualTo(4d); + assertThat(measures.get("uncovered_lines")).isEqualTo(2d); + assertThat(measures.get("branch_coverage")).isEqualTo(50.0); + assertThat(measures.get("conditions_to_cover")).isEqualTo(2d); + assertThat(measures.get("uncovered_conditions")).isEqualTo(1d); + assertThat(measures.get("coverage")).isEqualTo(50.0); + + assertThat(measures.get("it_coverage")).isNull(); + + assertThat(measures.get("overall_coverage")).isNull(); + + String coverage = cleanupScmAndDuplication(orchestrator.getServer().adminWsClient().get("api/sources/lines", "key", "sample-it-coverage:src/main/xoo/sample/Sample.xoo")); + // Use strict checking to be sure UT coverage is not present + JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/CoverageTest/it_coverage-expected.json"), "UTF-8"), coverage, true); + + verifyComputeEngineTempDirIsEmpty(); + } + + @Test + public void ut_and_it_coverage_merged_in_coverage() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-overall-coverage"))); + + // Since SQ 6.2 all coverage reports are merged as coverage + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "sample-overall-coverage", ALL_COVERAGE_METRICS); + assertThat(measures.get("line_coverage")).isEqualTo(75.0); + assertThat(measures.get("lines_to_cover")).isEqualTo(4); + assertThat(measures.get("uncovered_lines")).isEqualTo(1); + assertThat(measures.get("branch_coverage")).isEqualTo(50.0); + assertThat(measures.get("conditions_to_cover")).isEqualTo(4); + assertThat(measures.get("uncovered_conditions")).isEqualTo(2); + assertThat(measures.get("coverage")).isEqualTo(62.5); + + assertThat(measures.get("it_coverage")).isNull(); + + assertThat(measures.get("overall_coverage")).isNull(); + + String coverage = cleanupScmAndDuplication(orchestrator.getServer().adminWsClient().get("api/sources/lines", "key", "sample-overall-coverage:src/main/xoo/sample/Sample.xoo")); + // Use strict checking to be sure no extra coverage is present + JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/CoverageTest/ut_and_it_coverage-expected.json"), "UTF-8"), coverage, true); + + verifyComputeEngineTempDirIsEmpty(); + } + + /** + * SONAR-766 + */ + @Test + public void should_compute_coverage_on_project() { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-half-covered"))); + + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, "xoo-half-covered", ALL_COVERAGE_METRICS).get("coverage")).isEqualTo(50.0); + + verifyComputeEngineTempDirIsEmpty(); + } + + /** + * SONAR-766 + */ + @Test + public void should_ignore_coverage_on_full_path() { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-half-covered")) + .setProperty("sonar.coverage.exclusions", "src/main/xoo/org/sonar/tests/halfcovered/UnCovered.xoo")); + + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, "xoo-half-covered", ALL_COVERAGE_METRICS).get("coverage")).isEqualTo(100.0); + + verifyComputeEngineTempDirIsEmpty(); + } + + /** + * SONAR-766 + */ + @Test + public void should_ignore_coverage_on_pattern() { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-half-covered")) + .setProperty("sonar.coverage.exclusions", "**/UnCovered*")); + + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, "xoo-half-covered", ALL_COVERAGE_METRICS).get("coverage")).isEqualTo(100.0); + + verifyComputeEngineTempDirIsEmpty(); + } + + /** + * SONAR-766 + */ + @Test + public void should_not_have_coverage_at_all() { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-half-covered")) + .setProperty("sonar.coverage.exclusions", "**/*")); + + assertThat(getMeasuresAsDoubleByMetricKey(orchestrator, "xoo-half-covered", ALL_COVERAGE_METRICS).get("coverage")).isNull(); + + verifyComputeEngineTempDirIsEmpty(); + } + + private void verifyComputeEngineTempDirIsEmpty() { + File ceTempDirectory = new File(new File(orchestrator.getServer().getHome(), "temp"), "ce"); + assertThat(FileUtils.listFiles(ceTempDirectory, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)).isEmpty(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/test/CoverageTrackingTest.java b/tests/src/test/java/org/sonarqube/tests/test/CoverageTrackingTest.java new file mode 100644 index 00000000000..f6dd9b95355 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/test/CoverageTrackingTest.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.test; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.IOUtils; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.sonarqube.tests.Tester; + +import static util.ItUtils.projectDir; + +public class CoverageTrackingTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void test_coverage_per_test() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-with-coverage-per-test"))); + + String tests = orchestrator.getServer().adminWsClient().get("api/tests/list", "testFileKey", "sample-with-tests:src/test/xoo/sample/SampleTest.xoo"); + JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/CoverageTrackingTest/tests-expected.json"), "UTF-8"), tests, false); + + String covered_files = orchestrator.getServer().adminWsClient() + .get("api/tests/covered_files", "testId", extractSuccessfulTestId(tests)); + JSONAssert + .assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/CoverageTrackingTest/covered_files-expected.json"), "UTF-8"), covered_files, false); + } + + private String extractSuccessfulTestId(String json) { + Matcher jsonObjectMatcher = Pattern.compile(".*\\{((.*?)success(.*?))\\}.*", Pattern.MULTILINE).matcher(json); + jsonObjectMatcher.find(); + + Matcher idMatcher = Pattern.compile(".*\"id\"\\s*?:\\s*?\"(\\S*?)\".*", Pattern.MULTILINE).matcher(jsonObjectMatcher.group(1)); + return idMatcher.find() ? idMatcher.group(1) : ""; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/test/NewCoverageTest.java b/tests/src/test/java/org/sonarqube/tests/test/NewCoverageTest.java new file mode 100644 index 00000000000..6a331207e32 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/test/NewCoverageTest.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.test; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.Map; +import org.assertj.core.data.Offset; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.WsMeasures; + +import static java.lang.Double.parseDouble; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresWithVariationsByMetricKey; +import static util.ItUtils.projectDir; + +public class NewCoverageTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + private static final String PROJECT_KEY = "sample-new-coverage"; + + private static final Offset<Double> DEFAULT_OFFSET = Offset.offset(0.1d); + + private static final String[] ALL_NEW_COVERAGE_METRICS = new String[] { + "new_coverage", "new_line_coverage", "new_branch_coverage" + }; + + @BeforeClass + public static void analyze_project() { + orchestrator.resetData(); + + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-new-coverage-v1")) + .setProperty("sonar.projectDate", "2015-02-01") + .setProperty("sonar.scm.disabled", "false")); + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-new-coverage-v2")) + .setProperty("sonar.scm.disabled", "false")); + } + + @Test + public void new_coverage() throws Exception { + Map<String, WsMeasures.Measure> measures = getMeasuresWithVariationsByMetricKey(orchestrator, PROJECT_KEY, ALL_NEW_COVERAGE_METRICS); + assertThat(parseDouble(measures.get("new_coverage").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(66.6d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_line_coverage").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(100d, DEFAULT_OFFSET); + assertThat(parseDouble(measures.get("new_branch_coverage").getPeriods().getPeriodsValue(0).getValue())).isEqualTo(42.8, DEFAULT_OFFSET); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/test/TestExecutionTest.java b/tests/src/test/java/org/sonarqube/tests/test/TestExecutionTest.java new file mode 100644 index 00000000000..bb5edbb55f3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/test/TestExecutionTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.test; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category2Suite; +import java.util.Map; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.getMeasuresAsDoubleByMetricKey; +import static util.ItUtils.projectDir; + +public class TestExecutionTest { + + @ClassRule + public static Orchestrator orchestrator = Category2Suite.ORCHESTRATOR; + + @Before + public void delete_data() { + orchestrator.resetData(); + } + + @Test + public void test_execution_details() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-with-tests-execution-details"))); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "sample-with-tests", + "test_success_density", "test_failures", "test_errors", "tests", "skipped_tests", "test_execution_time"); + assertThat(measures.get("test_success_density")).isEqualTo(33.3); + assertThat(measures.get("test_failures")).isEqualTo(1); + assertThat(measures.get("test_errors")).isEqualTo(1); + assertThat(measures.get("tests")).isEqualTo(3); + assertThat(measures.get("skipped_tests")).isEqualTo(1); + assertThat(measures.get("test_execution_time")).isEqualTo(8); + + String json = orchestrator.getServer().adminWsClient().get("api/tests/list", "testFileKey", "sample-with-tests:src/test/xoo/sample/SampleTest.xoo"); + JSONAssert.assertEquals(IOUtils.toString(this.getClass().getResourceAsStream("/test/TestExecutionTest/expected.json"), "UTF-8"), json, false); + } + + @Test + public void test_execution_measures() throws Exception { + orchestrator.executeBuilds(SonarScanner.create(projectDir("testing/xoo-sample-with-tests-execution-measures"))); + + Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(orchestrator, "sample-with-tests", + "test_success_density", "test_failures", "test_errors", "tests", "skipped_tests", "test_execution_time"); + assertThat(measures.get("test_success_density")).isEqualTo(33.3); + assertThat(measures.get("test_failures")).isEqualTo(1); + assertThat(measures.get("test_errors")).isEqualTo(1); + assertThat(measures.get("tests")).isEqualTo(3); + assertThat(measures.get("skipped_tests")).isEqualTo(1); + assertThat(measures.get("test_execution_time")).isEqualTo(8); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/ui/OrganizationUiExtensionsTest.java b/tests/src/test/java/org/sonarqube/tests/ui/OrganizationUiExtensionsTest.java new file mode 100644 index 00000000000..5198fc89062 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ui/OrganizationUiExtensionsTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ui; + +import com.codeborne.selenide.Condition; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.openqa.selenium.By; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Organizations.Organization; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.WebDriverRunner.url; +import static org.assertj.core.api.Assertions.assertThat; + +public class OrganizationUiExtensionsTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + @Test + public void organization_page() { + Organization organization = tester.organizations().generate(); + tester.openBrowser().open("/organizations/" + organization.getKey() + "/projects"); + + $("#organization-navigation-more").click(); + $(By.linkText("Organization Page")).shouldBe(Condition.visible).click(); + + assertThat(url()).contains("uiextensionsplugin/organization_page"); + $("body").shouldHave(text("uiextensionsplugin/organization_page")); + } + + @Test + public void organization_admin_page() { + Organization organization = tester.organizations().generate(); + User administrator = tester.users().generateAdministrator(organization); + tester.openBrowser() + .logIn().submitCredentials(administrator.getLogin()) + .open("/organizations/" + organization.getKey() + "/projects"); + + $("#context-navigation a.navbar-admin-link").click(); + $(By.linkText("Organization Admin Page")).shouldBe(Condition.visible).click(); + + assertThat(url()).contains("uiextensionsplugin/organization_admin_page"); + $("body").shouldHave(text("uiextensionsplugin/organization_admin_page")); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/ui/SourceViewerTest.java b/tests/src/test/java/org/sonarqube/tests/ui/SourceViewerTest.java new file mode 100644 index 00000000000..64ff1451740 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ui/SourceViewerTest.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ui; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.pageobjects.Navigation; + +import static com.codeborne.selenide.Condition.exist; +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.projectDir; + +public class SourceViewerTest { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Category4Suite.ORCHESTRATOR; + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + @BeforeClass + public static void beforeClass() { + ORCHESTRATOR.resetData(); + analyzeSampleProject(); + } + + @Test + public void line_permalink() { + nav.open("/component?id=sample%3Asrc%2Fmain%2Fxoo%2Fsample%2FSample.xoo&line=6"); + $(".source-line").should(exist); + $(".source-line-highlighted[data-line-number=\"6\"]").should(exist); + } + + private static void analyzeSampleProject() { + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/ui/UiExtensionsTest.java b/tests/src/test/java/org/sonarqube/tests/ui/UiExtensionsTest.java new file mode 100644 index 00000000000..db79e355e35 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ui/UiExtensionsTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ui; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.openqa.selenium.By; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsProjects; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.project.CreateRequest; +import util.ItUtils; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.WebDriverRunner.url; +import static org.assertj.core.api.Assertions.assertThat; + +public class UiExtensionsTest { + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void test_static_files() { + tester.runHtmlTests("/ui/UiExtensionsTest/static-files.html"); + } + + @Test + public void test_global_page() { + tester.openBrowser().open("/about"); + + // on about page + $("#global-navigation-more").click(); + $(By.linkText("Global Page")).click(); + + assertThat(url()).contains("/uiextensionsplugin/global_page"); + $("body").shouldHave(text("uiextensionsplugin/global_page")); + } + + @Test + public void test_global_administration_page() { + User administrator = tester.users().generateAdministrator(); + tester.openBrowser() + .logIn().submitCredentials(administrator.getLogin()) + .open("/about"); + + $(".navbar-admin-link").click(); + $("#settings-navigation-configuration").click(); + $(By.linkText("Global Admin Page")).click(); + + assertThat(url()).contains("uiextensionsplugin/global_admin_page"); + $("body").shouldHave(text("uiextensionsplugin/global_admin_page")); + } + + @Test + public void test_project_page() { + WsProjects.CreateWsResponse.Project project = createSampleProject(); + + tester.openBrowser().open("/dashboard?id=" + project.getKey()); + + $("#component-navigation-more").click(); + $(By.linkText("Project Page")).click(); + + assertThat(url()).contains("uiextensionsplugin/project_page"); + $("body").shouldHave(text("uiextensionsplugin/project_page")); + } + + @Test + public void test_project_administration_page() { + WsProjects.CreateWsResponse.Project project = createSampleProject(); + User administrator = tester.users().generateAdministrator(); + + tester.openBrowser() + .logIn().submitCredentials(administrator.getLogin()) + .open("/dashboard?id=" + project.getKey()); + + $("#component-navigation-admin").click(); + $(By.linkText("Project Admin Page")).click(); + + assertThat(url()).contains("uiextensionsplugin/project_admin_page"); + $("body").shouldHave(text("uiextensionsplugin/project_admin_page")); + } + + private WsProjects.CreateWsResponse.Project createSampleProject() { + String projectKey = ItUtils.newProjectKey(); + return tester.wsClient().projects().create(CreateRequest.builder() + .setKey(projectKey) + .setName("Name of " + projectKey) + .build()).getProject(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/ui/UiTest.java b/tests/src/test/java/org/sonarqube/tests/ui/UiTest.java new file mode 100644 index 00000000000..ff4b65c0f14 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ui/UiTest.java @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ui; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.pageobjects.Navigation; +import util.ItUtils; + +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.WebDriverRunner.url; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.projectDir; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class UiTest { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = Category4Suite.ORCHESTRATOR; + + private Navigation nav = Navigation.create(ORCHESTRATOR); + + @Before + @After + public void resetData() throws Exception { + resetSettings(ORCHESTRATOR, null, "sonar.forceAuthentication"); + } + + @Test + public void footer_contains_information() { + nav.getFooter() + .should(hasText("Documentation")) + .should(hasText("SonarSource SA")); + } + + @Test + public void footer_contains_version() { + WsResponse status = ItUtils.newAdminWsClient(ORCHESTRATOR).wsConnector().call(new GetRequest("api/navigation/global")); + Map<String, Object> statusMap = ItUtils.jsonToMap(status.content()); + + nav.getFooter().should(hasText((String) statusMap.get("version"))); + } + + @Test + public void footer_doesnt_contains_version_on_login_page() { + WsResponse status = ItUtils.newAdminWsClient(ORCHESTRATOR).wsConnector().call(new GetRequest("api/navigation/global")); + Map<String, Object> statusMap = ItUtils.jsonToMap(status.content()); + + nav.openLogin(); + nav.getFooter().shouldNot(hasText((String) statusMap.get("version"))); + } + + @Test + public void footer_doesnt_contains_about_when_not_logged_in() { + setServerProperty(ORCHESTRATOR, "sonar.forceAuthentication", "true"); + nav.openLogin(); + nav.getFooter() + .shouldNot(hasText("About")) + .shouldNot(hasText("Web API")); + } + + @Test + public void many_page_transitions() { + analyzeSampleProject(); + + nav.open("/about"); + + // on about page + $(".about-page-projects-link") + .shouldBe(visible) + .shouldHave(text("1")) + .click(); + + // on projects page + assertThat(url()).contains("/projects"); + $(".project-card-name") + .shouldBe(visible) + .shouldHave(text("Sample")) + .find("a") + .click(); + + // on project dashboard + assertThat(url()).contains("/dashboard?id=sample"); + $(".overview-quality-gate") + .shouldBe(visible) + .shouldHave(text("Passed")); + $("a[href=\"/project/issues?id=sample&resolved=false&types=CODE_SMELL\"]") + .shouldBe(visible) + .shouldHave(text("0")) + .click(); + + // on project issues page + assertThat(url()).contains("/project/issues?id=sample&resolved=false&types=CODE_SMELL"); + $("[data-property=\"resolutions\"] .facet.active").shouldBe(visible); + + $("#global-navigation").find("a[href=\"/profiles\"]").click(); + + // on quality profiles page + assertThat(url()).contains("/profiles"); + $("table[data-language=xoo]").find("tr[data-name=Basic]").find(".quality-profiles-table-name") + .shouldBe(visible) + .shouldHave(text("Basic")) + .find("a") + .click(); + + // on profile page + assertThat(url()).contains("/profiles/show"); + $(".quality-profile-inheritance") + .shouldBe(visible) + .shouldHave(text("active rules")); + } + + @Test + public void markdown_help() { + String tags[] = {"strong", "a", "ul", "ol", "h1", "code", "pre", "blockquote"}; + + nav.open("/markdown/help"); + for (String tag : tags) { + $(tag).shouldBe(visible); + } + } + + private static void analyzeSampleProject() { + ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/updateCenter/UpdateCenterTest.java b/tests/src/test/java/org/sonarqube/tests/updateCenter/UpdateCenterTest.java new file mode 100644 index 00000000000..a8f4d95b825 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/updateCenter/UpdateCenterTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.updateCenter; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import util.user.UserRule; + +import static util.ItUtils.pluginArtifact; +import static util.selenium.Selenese.runSelenese; + +/** + * This class start its own orchestrator + */ +public class UpdateCenterTest { + + @ClassRule + public static final Orchestrator orchestrator = Orchestrator.builderEnv() + .setServerProperty("sonar.updatecenter.url", UpdateCenterTest.class.getResource("/updateCenter/UpdateCenterTest/update-center.properties").toString()) + .addPlugin(pluginArtifact("sonar-fake-plugin")) + .build(); + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + @After + public void tearDown() throws Exception { + userRule.resetUsers(); + } + + @Test + public void test_console() { + runSelenese(orchestrator, "/updateCenter/installed-plugins.html"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/MssqlConfig.java b/tests/src/test/java/org/sonarqube/tests/upgrade/MssqlConfig.java new file mode 100644 index 00000000000..2d22485ef43 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/MssqlConfig.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import com.sonar.orchestrator.config.Configuration; +import com.sonar.orchestrator.version.Version; +import org.apache.commons.lang.StringUtils; + +import static java.util.Objects.requireNonNull; + +public class MssqlConfig { + + /** + * Versions prior to 5.2 support only jTDS driver. Versions greater than or equal to 5.2 + * support only MS driver. The problem is that the test is configured with only + * the MS URL, so it must be changed at runtime for versions < 5.2. + */ + public static String fixUrl(Configuration conf, Version sqVersion) { + String jdbcUrl = requireNonNull(conf.getString("sonar.jdbc.url"), "No JDBC url configured"); + if (jdbcUrl.startsWith("jdbc:sqlserver:") && !sqVersion.isGreaterThanOrEquals("5.2")) { + // Job is configured with the new Microsoft driver, which is not supported by old versions of SQ + String host = StringUtils.substringBetween(jdbcUrl, "jdbc:sqlserver://", ";databaseName="); + String db = StringUtils.substringAfter(jdbcUrl, "databaseName="); + jdbcUrl = "jdbc:jtds:sqlserver://" + host + "/" + db; + System.out.println("Replaced JDBC url to: " + jdbcUrl); + return jdbcUrl; + } + return jdbcUrl; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/SelenideConfig.java b/tests/src/test/java/org/sonarqube/tests/upgrade/SelenideConfig.java new file mode 100644 index 00000000000..b67e99b5735 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/SelenideConfig.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import com.codeborne.selenide.Configuration; +import com.google.common.collect.ImmutableSet; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +enum SelenideConfig { + INSTANCE; + + private static final Set<String> SUPPORTED_BROWSERS = ImmutableSet.of("firefox"); + + SelenideConfig() { + Configuration.reportsFolder = "target/screenshots"; + } + + public SelenideConfig setBrowser(String browser) { + checkArgument(SUPPORTED_BROWSERS.contains(requireNonNull(browser)), "Browser is not supported: %s", browser); + Configuration.browser = browser; + return this; + } + + public SelenideConfig setBaseUrl(String s) { + Configuration.baseUrl = requireNonNull(s); + return this; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/ServerMigrationCall.java b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerMigrationCall.java new file mode 100644 index 00000000000..8b55198410e --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerMigrationCall.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import com.sonar.orchestrator.Orchestrator; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.wsclient.jsonsimple.JSONObject; + +public class ServerMigrationCall extends WsCallAndWait<ServerMigrationResponse> { + + public ServerMigrationCall(Orchestrator orchestrator) { + super(orchestrator, "/api/system/migrate_db"); + } + + @Override + @Nonnull + protected ServerMigrationResponse parse(JSONObject jsonObject) { + try { + return new ServerMigrationResponse( + ServerMigrationResponse.Status.valueOf((String) jsonObject.get("state")), + (String) jsonObject.get("message"), + parseDate((String) jsonObject.get("createdAt"))); + } catch (Exception e) { + throw new IllegalStateException("Failed to parse JSON response", e); + } + } + + @CheckForNull + private Date parseDate(@Nullable String createdAt) throws ParseException { + if (createdAt == null || createdAt.isEmpty()) { + return null; + } + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(createdAt); + } + + @Override + protected boolean shouldWait(ServerMigrationResponse response) { + return response.getStatus() == ServerMigrationResponse.Status.MIGRATION_NEEDED + || response.getStatus() == ServerMigrationResponse.Status.MIGRATION_RUNNING; + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/ServerMigrationResponse.java b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerMigrationResponse.java new file mode 100644 index 00000000000..1a24b0c5445 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerMigrationResponse.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import java.util.Date; +import javax.annotation.CheckForNull; + +class ServerMigrationResponse { + private final Status status; + private final String message; + private final Date date; + + ServerMigrationResponse(Status status, String message, Date date) { + this.status = status; + this.message = message; + this.date = date; + } + + public Status getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + @CheckForNull + public Date getDate() { + return date; + } + + public enum Status { + NO_MIGRATION, MIGRATION_NEEDED, MIGRATION_RUNNING, MIGRATION_SUCCEEDED, MIGRATION_FAILED + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/ServerStatusCall.java b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerStatusCall.java new file mode 100644 index 00000000000..d764cfd0bac --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerStatusCall.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import com.sonar.orchestrator.Orchestrator; +import javax.annotation.Nonnull; +import org.sonar.wsclient.jsonsimple.JSONObject; + +public class ServerStatusCall extends WsCallAndWait<ServerStatusResponse> { + protected ServerStatusCall(Orchestrator orchestrator) { + super(orchestrator, "/api/system/status"); + } + + @Nonnull + @Override + protected ServerStatusResponse parse(JSONObject jsonObject) { + return new ServerStatusResponse( + (String) jsonObject.get("id"), + (String) jsonObject.get("version"), + ServerStatusResponse.Status.valueOf((String) jsonObject.get("status")) + ); + } + + @Override + protected boolean shouldWait(ServerStatusResponse serverStatusResponse) { + ServerStatusResponse.Status status = serverStatusResponse.getStatus(); + return status == ServerStatusResponse.Status.STARTING || status == ServerStatusResponse.Status.DB_MIGRATION_RUNNING; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/ServerStatusResponse.java b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerStatusResponse.java new file mode 100644 index 00000000000..0ffcc3cfb4a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/ServerStatusResponse.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +public class ServerStatusResponse { + private final String id; + private final String version; + private final Status status; + + public ServerStatusResponse(String id, String version, Status status) { + this.id = id; + this.version = version; + this.status = status; + } + + public String getId() { + return id; + } + + public String getVersion() { + return version; + } + + public Status getStatus() { + return status; + } + + public enum Status { + UP, DOWN, STARTING, DB_MIGRATION_NEEDED, DB_MIGRATION_RUNNING + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/UpgradeSuite.java b/tests/src/test/java/org/sonarqube/tests/upgrade/UpgradeSuite.java new file mode 100644 index 00000000000..efb4694299f --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/UpgradeSuite.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + UpgradeTest.class +}) +public class UpgradeSuite { +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/UpgradeTest.java b/tests/src/test/java/org/sonarqube/tests/upgrade/UpgradeTest.java new file mode 100644 index 00000000000..83cf0c1ac91 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/UpgradeTest.java @@ -0,0 +1,281 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.WebDriverRunner; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.OrchestratorBuilder; +import com.sonar.orchestrator.build.MavenBuild; +import com.sonar.orchestrator.container.Server; +import com.sonar.orchestrator.locator.FileLocation; +import com.sonar.orchestrator.version.Version; +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Test; +import org.sonar.wsclient.services.ResourceQuery; +import org.sonarqube.ws.WsMeasures.Measure; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsClientFactories; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.measure.ComponentWsRequest; + +import static com.codeborne.selenide.Condition.hasText; +import static com.codeborne.selenide.Selenide.$; +import static java.lang.Integer.parseInt; +import static org.assertj.core.api.Assertions.assertThat; + +public class UpgradeTest { + + private static final String PROJECT_KEY = "org.apache.struts:struts-parent"; + private static final String LATEST_JAVA_RELEASE = "LATEST_RELEASE"; + private static final Version VERSION_5_2 = Version.create("5.2"); + private static final Version VERSION_5_6_1 = Version.create("5.6.1"); + private static final Version VERSION_CURRENT = Version.create("DEV"); + + private Orchestrator orchestrator; + + @After + public void stop() { + if (orchestrator != null) { + orchestrator.stop(); + orchestrator = null; + } + } + + @Test + public void test_upgrade_from_5_6_1() { + testDatabaseUpgrade(VERSION_5_6_1); + } + + @Test + public void test_upgrade_from_5_2_via_5_6_1() { + testDatabaseUpgrade(VERSION_5_2, VERSION_5_6_1); + } + + private void testDatabaseUpgrade(Version fromVersion, Version... intermediaryVersions) { + startOldVersionServer(fromVersion, false); + scanProject(); + int files = countFiles(PROJECT_KEY); + assertThat(files).isGreaterThan(0); + stopServer(); + + Arrays.stream(intermediaryVersions).forEach((sqVersion) -> { + startOldVersionServer(sqVersion, true); + upgrade(sqVersion); + verifyAnalysis(files); + stopServer(); + }); + + startDevServer(); + upgrade(VERSION_CURRENT); + verifyAnalysis(files); + stopServer(); + } + + private void verifyAnalysis(int expectedNumberOfFiles) { + assertThat(countFiles(PROJECT_KEY)).isEqualTo(expectedNumberOfFiles); + scanProject(); + assertThat(countFiles(PROJECT_KEY)).isEqualTo(expectedNumberOfFiles); + browseWebapp(); + } + + private void upgrade(Version sqVersion) { + checkSystemStatus(sqVersion, ServerStatusResponse.Status.DB_MIGRATION_NEEDED); + if (sqVersion.equals(VERSION_CURRENT)) { + checkUrlsBeforeUpgrade(); + } + ServerMigrationResponse serverMigrationResponse = new ServerMigrationCall(orchestrator).callAndWait(); + assertThat(serverMigrationResponse.getStatus()) + .describedAs("Migration status of version " + sqVersion + " should be MIGRATION_SUCCEEDED") + .isEqualTo(ServerMigrationResponse.Status.MIGRATION_SUCCEEDED); + checkSystemStatus(sqVersion, ServerStatusResponse.Status.UP); + checkUrlsAfterUpgrade(); + } + + private void checkSystemStatus(Version sqVersion, ServerStatusResponse.Status serverStatus) { + ServerStatusResponse serverStatusResponse = new ServerStatusCall(orchestrator).callAndWait(); + + assertThat(serverStatusResponse.getStatus()) + .describedAs("Server status of version " + sqVersion + " should be " + serverStatus) + .isEqualTo(serverStatus); + } + + private void checkUrlsBeforeUpgrade() { + // These urls should be available when system requires a migration + checkUrlIsReturningOk("/api/system/status"); + checkUrlIsReturningOk("/api/system/db_migration_status"); + checkUrlIsReturningOk("/api/webservices/list"); + + // These urls should not be available when system requires a migration + checkUrlIsReturningNotFound("/api/issues/search?projectKeys=org.apache.struts%3Astruts-core"); + checkUrlIsReturningNotFound("/api/components/tree?baseComponentKey=org.apache.struts%3Astruts-core"); + checkUrlIsReturningNotFound("/api/measures/component_tree?baseComponentKey=org.apache.struts%3Astruts-core&metricKeys=ncloc,files,violations"); + checkUrlIsReturningNotFound("/api/qualityprofiles/search"); + + // These page should all redirect to maintenance page + checkUrlIsRedirectedToMaintenancePage("/"); + checkUrlIsRedirectedToMaintenancePage("/issues/index"); + checkUrlIsRedirectedToMaintenancePage("/dashboard/index/org.apache.struts:struts-parent"); + checkUrlIsRedirectedToMaintenancePage("/issues"); + checkUrlIsRedirectedToMaintenancePage( + "/component/index?id=org.apache.struts%3Astruts-core%3Asrc%2Fmain%2Fjava%2Forg%2Fapache%2Fstruts%2Fchain%2Fcommands%2Fgeneric%2FWrappingLookupCommand.java"); + checkUrlIsRedirectedToMaintenancePage("/profiles"); + } + + private void checkUrlsAfterUpgrade() { + checkUrlIsReturningOk("/api/system/status"); + checkUrlIsReturningOk("/api/system/db_migration_status"); + checkUrlIsReturningOk("/api/webservices/list"); + checkUrlIsReturningOk("/api/l10n/index"); + + checkUrlIsReturningOk("/api/issues/search?projectKeys=org.apache.struts%3Astruts-core"); + checkUrlIsReturningOk("/api/components/tree?baseComponentKey=org.apache.struts%3Astruts-core"); + checkUrlIsReturningOk("/api/measures/component_tree?baseComponentKey=org.apache.struts%3Astruts-core&metricKeys=ncloc,files,violations"); + checkUrlIsReturningOk("/api/qualityprofiles/search"); + } + + private void browseWebapp() { + testUrl("/"); + testUrl("/api/issues/search?projectKeys=org.apache.struts%3Astruts-core"); + testUrl("/api/components/tree?baseComponentKey=org.apache.struts%3Astruts-core"); + testUrl("/api/measures/component_tree?baseComponentKey=org.apache.struts%3Astruts-core&metricKeys=ncloc,files,violations"); + testUrl("/api/qualityprofiles/search"); + testUrl("/issues/index"); + testUrl("/dashboard/index/org.apache.struts:struts-parent"); + testUrl("/issues"); + testUrl("/component/index?id=org.apache.struts%3Astruts-core%3Asrc%2Fmain%2Fjava%2Forg%2Fapache%2Fstruts%2Fchain%2Fcommands%2Fgeneric%2FWrappingLookupCommand.java"); + testUrl("/profiles"); + } + + private void startOldVersionServer(Version sqVersion, boolean keepDatabase) { + OrchestratorBuilder builder = Orchestrator.builderEnv() + .setSonarVersion(sqVersion.toString()) + .setOrchestratorProperty("orchestrator.keepDatabase", String.valueOf(keepDatabase)) + .setOrchestratorProperty("javaVersion", "3.14") + .addPlugin("java") + .setStartupLogWatcher(log -> log.contains("Process[web] is up")); + orchestrator = builder.build(); + orchestrator.start(); + initSelenide(orchestrator); + } + + private void startDevServer() { + OrchestratorBuilder builder = Orchestrator.builderEnv() + .setZipFile(FileLocation.byWildcardMavenFilename(new File("../sonar-application/target"), "sonar*.zip").getFile()) + .setOrchestratorProperty("orchestrator.keepDatabase", "true") + .setOrchestratorProperty("javaVersion", LATEST_JAVA_RELEASE) + .addPlugin("java") + .setStartupLogWatcher(log -> log.contains("Database must be upgraded")); + orchestrator = builder.build(); + orchestrator.start(); + initSelenide(orchestrator); + } + + private void stopServer() { + if (orchestrator != null) { + orchestrator.stop(); + } + } + + private void scanProject() { + MavenBuild build = MavenBuild.create(new File("projects/struts-1.3.9-diet/pom.xml")) + .setCleanSonarGoals() + // exclude pom.xml, otherwise it will be published in SQ 6.3+ and not in previous versions, resulting in a different number of components + .setProperty("sonar.exclusions", "**/pom.xml") + .setProperty("sonar.dynamicAnalysis", "false") + .setProperty("sonar.scm.disabled", "true") + .setProperty("sonar.cpd.cross_project", "true"); + orchestrator.executeBuild(build); + } + + private int countFiles(String key) { + if (orchestrator.getConfiguration().getSonarVersion().isGreaterThanOrEquals("5.4")) { + Measure measure = newWsClient(orchestrator).measures().component(new ComponentWsRequest().setComponentKey(key).setMetricKeys(Collections.singletonList("files"))) + .getComponent().getMeasures(0); + return parseInt(measure.getValue()); + } + return orchestrator.getServer().getWsClient().find(ResourceQuery.createForMetrics(key, "files")).getMeasureIntValue("files"); + } + + private void testUrl(String path) { + HttpURLConnection connection = null; + try { + URL url = new URL(orchestrator.getServer().getUrl() + path); + connection = (HttpURLConnection) url.openConnection(); + connection.connect(); + assertThat(connection.getResponseCode()).as("Fail to load " + path).isEqualTo(HttpURLConnection.HTTP_OK); + + String content = IOUtils.toString(connection.getInputStream()); + assertThat(content).as("Fail to load " + path).doesNotContain("something went wrong"); + assertThat(content).as("Fail to load " + path).doesNotContain("The page you were looking for doesn't exist"); + assertThat(content).as("Fail to load " + path).doesNotContain("Unauthorized access"); + + } catch (IOException e) { + throw new IllegalStateException("Error with " + path, e); + + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + private void checkUrlIsReturningOk(String url) { + newWsClient(orchestrator).wsConnector().call(new GetRequest(url)).failIfNotSuccessful(); + } + + private void checkUrlIsReturningNotFound(String url) { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest(url)); + assertThat(response.code()).isEqualTo(HttpURLConnection.HTTP_NOT_FOUND); + } + + private void checkUrlIsRedirectedToMaintenancePage(String url) { + shouldBeRedirectToMaintenance(url); + } + + private static WsClient newWsClient(Orchestrator orchestrator) { + Server server = orchestrator.getServer(); + return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() + .url(server.getUrl()) + .build()); + } + + private static void initSelenide(Orchestrator orchestrator) { + String browser = orchestrator.getConfiguration().getString("orchestrator.browser", "firefox"); + SelenideConfig.INSTANCE + .setBrowser(browser) + .setBaseUrl(orchestrator.getServer().getUrl()); + WebDriverRunner.getWebDriver().manage().deleteAllCookies(); + } + + private void shouldBeRedirectToMaintenance(String relativeUrl) { + Selenide.open(relativeUrl); + $("#content").should(hasText("SonarQube is under maintenance")); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/upgrade/WsCallAndWait.java b/tests/src/test/java/org/sonarqube/tests/upgrade/WsCallAndWait.java new file mode 100644 index 00000000000..4e4fa645ea2 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/upgrade/WsCallAndWait.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.upgrade; + +import com.sonar.orchestrator.Orchestrator; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import org.sonar.wsclient.jsonsimple.JSONObject; +import org.sonar.wsclient.jsonsimple.parser.JSONParser; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.propagate; + +public abstract class WsCallAndWait<RESPONSE> { + private static final long TIMEOUT_5_MINUTES = 5L * 60 * 1000; + private static final long DELAY_3_SECONDS = 3L * 1000; + + private final Orchestrator orchestrator; + private final String targetRelativeUrl; + private final long timeout; + private final long delay; + + protected WsCallAndWait(Orchestrator orchestrator, String targetRelativeUrl, long timeout, long delay) { + this.orchestrator = orchestrator; + this.targetRelativeUrl = checkNotNull(targetRelativeUrl); + this.timeout = timeout; + this.delay = delay; + } + + protected WsCallAndWait(Orchestrator orchestrator, String targetRelativeUrl) { + this(orchestrator, targetRelativeUrl, TIMEOUT_5_MINUTES, DELAY_3_SECONDS); + } + + @Nonnull + public RESPONSE call() { + String response = orchestrator.getServer().wsClient().post(targetRelativeUrl); + JSONObject jsonObject = toJsonObject(response); + try { + return parse(jsonObject); + } catch (Exception e) { + throw new IllegalStateException("Failed to parse JSON response", e); + } + } + + @CheckForNull + public RESPONSE callAndWait() { + long endAt = System.currentTimeMillis() + timeout; + + while (System.currentTimeMillis() < endAt) { + RESPONSE response = call(); + if (shouldWait(response)) { + sleepQuietly(delay); + } else { + return response; + } + } + return null; + } + + private void sleepQuietly(long rateInMs) { + try { + Thread.sleep(rateInMs); + } catch (InterruptedException e) { + propagate(e); + } + } + + private JSONObject toJsonObject(String s) { + try { + JSONParser parser = new JSONParser(); + Object o = parser.parse(s); + if (o instanceof JSONObject) { + return (JSONObject) o; + } + throw new RuntimeException("Can not parse response from server migration WS (not a JSON object)"); + } catch (Exception e) { + throw new IllegalStateException("Invalid JSON: " + s, e); + } + } + + @Nonnull + protected abstract RESPONSE parse(JSONObject jsonObject); + + protected abstract boolean shouldWait(RESPONSE response); +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java new file mode 100644 index 00000000000..65cd095d285 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java @@ -0,0 +1,324 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.io.File; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.user.CreateRequest; +import org.sonarqube.pageobjects.Navigation; +import util.user.UserRule; +import util.user.Users; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; +import static util.selenium.Selenese.runSelenese; + +/** + * TODO : Add missing ITs + * - display multiple identity provider plugins (probably in another class) + */ +public class BaseIdentityProviderTest { + + @ClassRule + public static Orchestrator ORCHESTRATOR = Category4Suite.ORCHESTRATOR; + + @ClassRule + public static UserRule userRule = UserRule.from(ORCHESTRATOR); + + static String FAKE_PROVIDER_KEY = "fake-base-id-provider"; + + static String USER_LOGIN = "john"; + static String USER_PROVIDER_ID = "fake-john"; + static String USER_NAME = "John"; + static String USER_EMAIL = "john@email.com"; + + static String USER_NAME_UPDATED = "John Doe"; + static String USER_EMAIL_UPDATED = "john.doe@email.com"; + + static String GROUP1 = "group1"; + static String GROUP2 = "group2"; + static String GROUP3 = "group3"; + + static WsClient adminWsClient; + + @BeforeClass + public static void setUp() { + ORCHESTRATOR.resetData(); + adminWsClient = newAdminWsClient(ORCHESTRATOR); + } + + @Before + @After + public void resetData() throws Exception { + userRule.resetUsers(); + userRule.removeGroups(GROUP1, GROUP2, GROUP3); + resetSettings(ORCHESTRATOR, null, + "sonar.auth.fake-base-id-provider.enabled", + "sonar.auth.fake-base-id-provider.user", + "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", + "sonar.auth.fake-base-id-provider.enabledGroupsSync", + "sonar.auth.fake-base-id-provider.groups", + "sonar.auth.fake-base-id-provider.allowsUsersToSignUp"); + } + + @Test + public void create_new_user_when_authenticate() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + userRule.verifyUserDoesNotExist(USER_LOGIN); + + // First connection, user is created + authenticateWithFakeAuthProvider(); + + userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL, false); + } + + @Test + public void authenticate_user_through_ui() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + Navigation.create(ORCHESTRATOR).openLogin().useOAuth2().shouldBeLoggedIn(); + + userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL); + } + + @Test + public void display_unauthorized_page_when_authentication_failed() throws Exception { + enablePlugin(); + // As this property is null, the plugin will throw an exception + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.user", null); + + runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); + + userRule.verifyUserDoesNotExist(USER_LOGIN); + } + + @Test + public void fail_when_email_already_exists() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + userRule.createUser("another", "Another", USER_EMAIL, "another"); + + runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_when_email_already_exists.html"); + + File logFile = ORCHESTRATOR.getServer().getWebLogs(); + assertThat(FileUtils.readFileToString(logFile)) + .doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); + } + + @Test + public void fail_to_authenticate_when_not_allowed_to_sign_up() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.allowsUsersToSignUp", "false"); + + runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); + + userRule.verifyUserDoesNotExist(USER_LOGIN); + } + + @Test + public void update_existing_user_when_authenticate() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + // First connection, user is created + authenticateWithFakeAuthProvider(); + + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME_UPDATED, USER_EMAIL_UPDATED); + + // Second connection, user should be updated + authenticateWithFakeAuthProvider(); + + userRule.verifyUserExists(USER_LOGIN, USER_NAME_UPDATED, USER_EMAIL_UPDATED); + } + + @Test + public void reactivate_disabled_user() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + userRule.verifyUserDoesNotExist(USER_LOGIN); + + // First connection, user is created + authenticateWithFakeAuthProvider(); + + Optional<Users.User> user = userRule.getUserByLogin(USER_LOGIN); + assertThat(user).isPresent(); + + // Disable user + userRule.deactivateUsers(USER_LOGIN); + + // Second connection, user is reactivated + authenticateWithFakeAuthProvider(); + userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL); + } + + @Test + public void not_authenticate_when_plugin_is_disabled() throws Exception { + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabled", "false"); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + authenticateWithFakeAuthProvider(); + + // User is not created as nothing plugin is disabled + userRule.verifyUserDoesNotExist(USER_LOGIN); + + // TODO Add Selenium test to check login form + } + + @Test + public void display_message_in_ui_but_not_in_log_when_unauthorized_exception() throws Exception { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true"); + + runSelenese(ORCHESTRATOR, + "/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); + + File logFile = ORCHESTRATOR.getServer().getWebLogs(); + assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); + assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); + + userRule.verifyUserDoesNotExist(USER_LOGIN); + } + + @Test + public void synchronize_groups_for_new_user() throws Exception { + enablePlugin(); + userRule.createGroup(GROUP1); + userRule.createGroup(GROUP2); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + // Group3 doesn't exist in DB, user won't belong to this group + setGroupsReturnedByAuthPlugin(GROUP1, GROUP2, GROUP3); + + authenticateWithFakeAuthProvider(); + + userRule.verifyUserGroupMembership(USER_LOGIN, GROUP1, GROUP2, "sonar-users"); + } + + @Test + public void synchronize_groups_for_existing_user() throws Exception { + enablePlugin(); + userRule.createGroup(GROUP1); + userRule.createGroup(GROUP2); + userRule.createGroup(GROUP3); + userRule.createUser(USER_LOGIN, "password"); + userRule.associateGroupsToUser(USER_LOGIN, GROUP1, GROUP2); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + // Group1 is not returned by the plugin, user won't belong anymore to this group + setGroupsReturnedByAuthPlugin(GROUP2, GROUP3); + + authenticateWithFakeAuthProvider(); + + userRule.verifyUserGroupMembership(USER_LOGIN, GROUP2, GROUP3, "sonar-users"); + } + + @Test + public void remove_user_groups_when_groups_provided_by_plugin_are_empty() throws Exception { + enablePlugin(); + userRule.createGroup(GROUP1); + userRule.createUser(USER_LOGIN, "password"); + userRule.associateGroupsToUser(USER_LOGIN, GROUP1); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + // No group is returned by the plugin + setGroupsReturnedByAuthPlugin(); + + authenticateWithFakeAuthProvider(); + + // User is not member to any group + userRule.verifyUserGroupMembership(USER_LOGIN, "sonar-users"); + } + + @Test + public void allow_user_login_with_2_characters() throws Exception { + enablePlugin(); + String login = "jo"; + setUserCreatedByAuthPlugin(login, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + userRule.verifyUserDoesNotExist(login); + + // First connection, user is created + authenticateWithFakeAuthProvider(); + + userRule.verifyUserExists(login, USER_NAME, USER_EMAIL, false); + } + + @Test + public void provision_user_before_authentication() { + enablePlugin(); + setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL); + + // Provision none local user in database + newAdminWsClient(ORCHESTRATOR).users().create(CreateRequest.builder() + .setLogin(USER_LOGIN) + .setName(USER_NAME) + .setEmail(USER_EMAIL) + .setLocal(false) + .build()); + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_LOGIN, "sonarqube"); + + // Authenticate with external system -> It will update external provider info + authenticateWithFakeAuthProvider(); + + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_PROVIDER_ID, FAKE_PROVIDER_KEY); + } + + private static void enablePlugin() { + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabled", "true"); + } + + private static void setUserCreatedByAuthPlugin(String login, String providerId, String name, String email) { + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.user", login + "," + providerId + "," + name + "," + email); + } + + private static void setGroupsReturnedByAuthPlugin(String... groups) { + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabledGroupsSync", "true"); + if (groups.length > 0) { + setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.groups", Joiner.on(",").join(groups)); + } + } + + private static void authenticateWithFakeAuthProvider() { + adminWsClient.wsConnector().call( + new GetRequest("/sessions/init/" + FAKE_PROVIDER_KEY)) + .failIfNotSuccessful(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/FavoritesWsTest.java b/tests/src/test/java/org/sonarqube/tests/user/FavoritesWsTest.java new file mode 100644 index 00000000000..fbcd3181ad3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/FavoritesWsTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import java.util.List; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.Favorites.Favorite; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.favorite.SearchRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +/** + * TODO This test should not require an analysis, only provisioning the project should be enough + */ +public class FavoritesWsTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + private static WsClient adminClient; + + @Before + public void inspectProject() { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"))); + adminClient = newAdminWsClient(orchestrator); + } + + @Test + public void favorites_web_service() { + // GET (nothing) + List<Favorite> favorites = adminClient.favorites().search(new SearchRequest()).getFavoritesList(); + assertThat(favorites).isEmpty(); + + // POST (create favorites) + adminClient.favorites().add("sample"); + adminClient.favorites().add("sample:src/main/xoo/sample/Sample.xoo"); + + // GET (created favorites) + favorites = adminClient.favorites().search(new SearchRequest()).getFavoritesList(); + assertThat(favorites.stream().map(Favorite::getKey)).containsOnly("sample", "sample:src/main/xoo/sample/Sample.xoo"); + + // DELETE (a favorite) + adminClient.favorites().remove("sample"); + favorites = adminClient.favorites().search(new SearchRequest()).getFavoritesList(); + assertThat(favorites.stream().map(Favorite::getKey)).containsOnly("sample:src/main/xoo/sample/Sample.xoo"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/ForceAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/ForceAuthenticationTest.java new file mode 100644 index 00000000000..1f0ae8f71ee --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/ForceAuthenticationTest.java @@ -0,0 +1,132 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsConnector; +import org.sonarqube.ws.client.WsRequest; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.pageobjects.Navigation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.client.WsRequest.Method.GET; +import static org.sonarqube.ws.client.WsRequest.Method.POST; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class ForceAuthenticationTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + private User user; + + @Before + public void setUp() { + setServerProperty(orchestrator, "sonar.forceAuthentication", "true"); + user = tester.users().generate(); + } + + @After + public void tearDown() { + resetSettings(orchestrator, null, "sonar.forceAuthentication"); + } + + @Test + public void batch_ws_does_not_require_authentication() { + WsConnector anonymousConnector = tester.asAnonymous().wsClient().wsConnector(); + WsResponse batchIndex = anonymousConnector.call(new GetRequest("/batch/index")).failIfNotSuccessful(); + String batchIndexContent = batchIndex.content(); + + assertThat(batchIndexContent).isNotEmpty(); + String jar = batchIndexContent.split("\\|")[0]; + + assertThat(anonymousConnector.call( + new GetRequest("/batch/file").setParam("name", jar)).failIfNotSuccessful().contentStream()).isNotNull(); + + // As sonar-runner is still using deprecated /batch/key, we have to also verify it + assertThat(anonymousConnector.call(new GetRequest("/batch/" + jar)).failIfNotSuccessful().contentStream()).isNotNull(); + } + + @Test + public void authentication_ws_does_not_require_authentication() { + WsConnector anonymousConnector = tester.asAnonymous().wsClient().wsConnector(); + assertThat(anonymousConnector.call(new PostRequest("/api/authentication/login") + .setParam("login", user.getLogin()) + .setParam("password", user.getLogin())).isSuccessful()).isTrue(); + verifyPathDoesNotRequiresAuthentication("/api/authentication/logout", POST); + } + + @Test + public void check_ws_not_requiring_authentication() { + verifyPathDoesNotRequiresAuthentication("/api/system/db_migration_status", GET); + verifyPathDoesNotRequiresAuthentication("/api/system/status", GET); + verifyPathDoesNotRequiresAuthentication("/api/system/migrate_db", POST); + verifyPathDoesNotRequiresAuthentication("/api/users/identity_providers", GET); + verifyPathDoesNotRequiresAuthentication("/api/l10n/index", GET); + } + + @Test + public void check_ws_requiring_authentication() { + verifyPathRequiresAuthentication("/api/issues/search", GET); + verifyPathRequiresAuthentication("/api/rules/search", GET); + } + + @Test + public void redirect_to_login_page() { + User administrator = tester.users().generateAdministrator(); + Navigation page = tester.openBrowser().openHome(); + page.shouldBeRedirectedToLogin(); + page.openLogin().submitCredentials(administrator.getLogin()).shouldBeLoggedIn(); + page.logOut().shouldBeRedirectedToLogin(); + } + + private void verifyPathRequiresAuthentication(String path, WsRequest.Method method) { + assertThat(call(tester.asAnonymous().wsClient(), path, method).code()).isEqualTo(401); + WsResponse wsResponse = call(tester.wsClient(), path, method); + assertThat(wsResponse.isSuccessful()).as("code is %s on path %s", wsResponse.code(), path).isTrue(); + } + + private void verifyPathDoesNotRequiresAuthentication(String path, WsRequest.Method method) { + WsResponse wsResponse = call(tester.asAnonymous().wsClient(), path, method); + assertThat(wsResponse.isSuccessful()).as("code is %s on path %s", wsResponse.code(), path).isTrue(); + wsResponse = call(tester.wsClient(), path, method); + assertThat(wsResponse.isSuccessful()).as("code is %s on path %s", wsResponse.code(), path).isTrue(); + } + + private WsResponse call(WsClient client, String path, WsRequest.Method method) { + WsRequest request = method.equals(GET) ? new GetRequest(path) : new PostRequest(path); + return client.wsConnector().call(request); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java new file mode 100644 index 00000000000..3f53ba7145b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/LocalAuthenticationTest.java @@ -0,0 +1,234 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.codeborne.selenide.Condition; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUserTokens; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsClientFactories; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.usertoken.GenerateWsRequest; +import org.sonarqube.ws.client.usertoken.RevokeWsRequest; +import org.sonarqube.ws.client.usertoken.SearchWsRequest; +import org.sonarqube.ws.client.usertoken.UserTokensService; +import org.sonarqube.pageobjects.LoginPage; +import org.sonarqube.pageobjects.Navigation; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; +import static util.selenium.Selenese.runSelenese; + +public class LocalAuthenticationTest { + + private static final String ADMIN_USER_LOGIN = "admin-user"; + + private static final String LOGIN = "george.orwell"; + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Before + public void setUp() { + tester.users().generate(u -> u.setLogin(LOGIN).setPassword("123456")); + addUserPermission(LOGIN, "admin"); + + tester.users().generate(u -> u.setLogin("simple-user").setPassword("password")); + tester.users().generateAdministrator(u -> u.setLogin(ADMIN_USER_LOGIN).setPassword(ADMIN_USER_LOGIN)); + } + + @After + public void resetProperties() throws Exception { + resetSettings(orchestrator, null, "sonar.forceAuthentication"); + } + + @Test + public void log_in_with_correct_credentials_then_log_out() { + Navigation nav = tester.openBrowser(); + nav.shouldNotBeLoggedIn(); + nav.logIn().submitCredentials(LOGIN, "123456").shouldBeLoggedIn(); + nav.logOut().shouldNotBeLoggedIn(); + } + + @Test + public void log_in_with_wrong_credentials() { + Navigation nav = tester.openBrowser(); + LoginPage page = nav + .logIn() + .submitWrongCredentials(LOGIN, "wrong"); + page.getErrorMessage().shouldHave(Condition.text("Authentication failed")); + + nav.openHome(); + nav.shouldNotBeLoggedIn(); + } + + @Test + public void basic_authentication_based_on_login_and_password() { + String userId = UUID.randomUUID().toString(); + String login = format("login-%s", userId); + String name = format("name-%s", userId); + String password = "!ascii-only:-)@"; + tester.users().generate(u -> u.setLogin(login).setName(name).setPassword(password)); + + // authenticate + WsClient wsClient = tester.as(login, password).wsClient(); + WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate")); + assertThat(response.content()).isEqualTo("{\"valid\":true}"); + } + + @Test + public void basic_authentication_based_on_token() { + String tokenName = "Validate token based authentication"; + UserTokensService tokensService = tester.wsClient().userTokens(); + WsUserTokens.GenerateWsResponse generateWsResponse = tokensService.generate(new GenerateWsRequest() + .setLogin(LOGIN) + .setName(tokenName)); + WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() + .url(orchestrator.getServer().getUrl()) + .token(generateWsResponse.getToken()).build()); + + WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate")); + + assertThat(response.content()).isEqualTo("{\"valid\":true}"); + + WsUserTokens.SearchWsResponse searchResponse = tokensService.search(new SearchWsRequest().setLogin(LOGIN)); + assertThat(searchResponse.getUserTokensCount()).isEqualTo(1); + tokensService.revoke(new RevokeWsRequest().setLogin(LOGIN).setName(tokenName)); + searchResponse = tokensService.search(new SearchWsRequest().setLogin(LOGIN)); + assertThat(searchResponse.getUserTokensCount()).isEqualTo(0); + } + + @Test + @Ignore + public void web_login_form_should_support_utf8_passwords() { + // TODO selenium + } + + @Test + public void basic_authentication_does_not_support_utf8_passwords() { + String login = "user_with_utf8_password"; + // see http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + String password = "κόσμε"; + + // create user with a UTF-8 password + tester.users().generate(u -> u.setLogin(login).setPassword(password)); + + // authenticate + assertThat(checkAuthenticationWithAuthenticateWebService(login, password)).isFalse(); + } + + @Test + public void allow_user_login_with_2_characters() throws Exception { + tester.users().generate(u -> u.setLogin("jo").setPassword("password")); + + assertThat(checkAuthenticationWithAuthenticateWebService("jo", "password")).isTrue(); + } + + @Test + public void authentication_through_ui() { + runSelenese(orchestrator, + "/user/LocalAuthenticationTest/login_successful.html", + "/user/LocalAuthenticationTest/login_wrong_password.html", + "/user/LocalAuthenticationTest/should_not_be_unlogged_when_going_to_login_page.html", + "/user/LocalAuthenticationTest/redirect_to_login_when_not_enough_privilege.html", + // SONAR-2132 + "/user/LocalAuthenticationTest/redirect_to_original_url_after_direct_login.html", + "/user/LocalAuthenticationTest/redirect_to_original_url_with_parameters_after_direct_login.html", + // SONAR-2009 + "/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html"); + + setServerProperty(orchestrator, "sonar.forceAuthentication", "true"); + + runSelenese(orchestrator, + // SONAR-3473 + "/user/LocalAuthenticationTest/force-authentication.html"); + } + + @Test + public void authentication_with_authentication_ws() { + assertThat(checkAuthenticationWithAuthenticateWebService("admin", "admin")).isTrue(); + assertThat(checkAuthenticationWithAuthenticateWebService("wrong", "admin")).isFalse(); + assertThat(checkAuthenticationWithAuthenticateWebService("admin", "wrong")).isFalse(); + assertThat(checkAuthenticationWithAuthenticateWebService(null, null)).isTrue(); + + setServerProperty(orchestrator, "sonar.forceAuthentication", "true"); + + assertThat(checkAuthenticationWithAuthenticateWebService("admin", "admin")).isTrue(); + assertThat(checkAuthenticationWithAuthenticateWebService("wrong", "admin")).isFalse(); + assertThat(checkAuthenticationWithAuthenticateWebService("admin", "wrong")).isFalse(); + assertThat(checkAuthenticationWithAuthenticateWebService(null, null)).isFalse(); + } + + /** + * SONAR-7640 + */ + @Test + public void authentication_with_any_ws() throws Exception { + assertThat(checkAuthenticationWithAnyWS("admin", "admin").code()).isEqualTo(200); + assertThat(checkAuthenticationWithAnyWS("wrong", "admin").code()).isEqualTo(401); + assertThat(checkAuthenticationWithAnyWS("admin", "wrong").code()).isEqualTo(401); + assertThat(checkAuthenticationWithAnyWS("admin", null).code()).isEqualTo(401); + assertThat(checkAuthenticationWithAnyWS(null, null).code()).isEqualTo(200); + + setServerProperty(orchestrator, "sonar.forceAuthentication", "true"); + + assertThat(checkAuthenticationWithAnyWS("admin", "admin").code()).isEqualTo(200); + assertThat(checkAuthenticationWithAnyWS("wrong", "admin").code()).isEqualTo(401); + assertThat(checkAuthenticationWithAnyWS("admin", "wrong").code()).isEqualTo(401); + assertThat(checkAuthenticationWithAnyWS("admin", null).code()).isEqualTo(401); + assertThat(checkAuthenticationWithAnyWS(null, null).code()).isEqualTo(401); + } + + private boolean checkAuthenticationWithAuthenticateWebService(String login, String password) { + String result = tester.as(login, password).wsClient().wsConnector().call(new PostRequest("/api/authentication/validate")).content(); + return result.contains("{\"valid\":true}"); + } + + private WsResponse checkAuthenticationWithAnyWS(String login, String password) { + WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder().url(orchestrator.getServer().getUrl()).credentials(login, password).build()); + // Call any WS + return wsClient.wsConnector().call(new GetRequest("api/rules/search")); + } + + private void addUserPermission(String login, String permission) { + tester.wsClient().permissions().addUser(new AddUserWsRequest() + .setLogin(login) + .setPermission(permission)); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/MyAccountPageTest.java b/tests/src/test/java/org/sonarqube/tests/user/MyAccountPageTest.java new file mode 100644 index 00000000000..de2c4a992d3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/MyAccountPageTest.java @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.sonarqube.tests.Category4Suite; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.pageobjects.Navigation; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.projectDir; +import static util.selenium.Selenese.runSelenese; + +public class MyAccountPageTest { + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + private User administrator; + + @Before + public void initUser() { + administrator = tester.users().generateAdministrator(); + createUser("account-user", "User With Account", "user@example.com"); + } + + @Test + public void should_display_user_details() throws Exception { + Navigation nav = tester.openBrowser(); + nav.openLogin().submitCredentials("account-user", "password").shouldBeLoggedIn(); + nav.open("/account"); + $("#name").shouldHave(text("User With Account")); + $("#login").shouldHave(text("account-user")); + $("#email").shouldHave(text("user@example.com")); + $("#groups").shouldHave(text("sonar-users")); + $("#scm-accounts").shouldHave(text("user@example.com")); + $("#avatar").shouldBe(visible); + } + + @Test + public void should_change_password() throws Exception { + Navigation nav = tester.openBrowser(); + nav.openLogin().submitCredentials("account-user", "password").shouldBeLoggedIn(); + nav.open("/account/security"); + $("#old_password").val("password"); + $("#password").val("new_password"); + $("#password_confirmation").val("new_password"); + $("#change-password").click(); + $(".alert-success").shouldBe(visible); + nav.logOut().logIn().submitCredentials("account-user", "new_password").shouldBeLoggedIn(); + } + + @Test + public void should_display_projects() throws Exception { + // first, try on empty instance + runSelenese(orchestrator, "/user/MyAccountPageTest/should_display_no_projects.html"); + + // then, analyze a project + analyzeProject("sample"); + grantAdminPermission("account-user", "sample"); + + runSelenese(orchestrator, "/user/MyAccountPageTest/should_display_projects.html"); + } + + @Test + public void notifications() { + Navigation nav = tester.openBrowser(); + nav.logIn().submitCredentials(administrator.getLogin()).openNotifications() + .addGlobalNotification("ChangesOnMyIssue") + .addGlobalNotification("NewIssues") + .removeGlobalNotification("ChangesOnMyIssue"); + + nav.openNotifications() + .shouldHaveGlobalNotification("NewIssues") + .shouldNotHaveGlobalNotification("ChangesOnMyIssue"); + } + + private void createUser(String login, String name, String email) { + tester.wsClient().wsConnector().call( + new PostRequest("api/users/create") + .setParam("login", login) + .setParam("name", name) + .setParam("email", email) + .setParam("password", "password")); + } + + private static void analyzeProject(String projectKey) { + SonarScanner build = SonarScanner.create(projectDir("qualitygate/xoo-sample")) + .setProjectKey(projectKey) + .setProperty("sonar.projectDescription", "Description of a project") + .setProperty("sonar.links.homepage", "http://example.com"); + orchestrator.executeBuild(build); + } + + private void grantAdminPermission(String login, String projectKey) { + tester.wsClient().wsConnector().call( + new PostRequest("api/permissions/add_user") + .setParam("login", login) + .setParam("projectKey", projectKey) + .setParam("permission", "admin")); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java new file mode 100644 index 00000000000..642a7351ccd --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java @@ -0,0 +1,220 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import java.io.File; +import java.net.HttpURLConnection; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUsers.SearchWsResponse.User; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.user.CreateRequest; +import org.sonarqube.pageobjects.Navigation; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; +import static util.selenium.Selenese.runSelenese; + +/** + * There's only tests specific to OAuth2 in this class + */ +public class OAuth2IdentityProviderTest { + + @ClassRule + public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + private static String FAKE_PROVIDER_KEY = "fake-oauth2-id-provider"; + + private static String USER_LOGIN = "john"; + private static String USER_PROVIDER_ID = "fake-john"; + private static String USER_NAME = "John"; + private static String USER_EMAIL = "john@email.com"; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + private MockWebServer fakeServerAuthProvider; + private String fakeServerAuthProviderUrl; + + @Before + public void setUp() throws Exception { + fakeServerAuthProvider = new MockWebServer(); + fakeServerAuthProvider.start(); + fakeServerAuthProviderUrl = fakeServerAuthProvider.url("").url().toString(); + resetData(); + } + + @After + public void tearDown() throws Exception { + resetData(); + fakeServerAuthProvider.shutdown(); + } + + private void resetData() { + resetSettings(orchestrator, null, + "sonar.auth.fake-oauth2-id-provider.enabled", + "sonar.auth.fake-oauth2-id-provider.url", + "sonar.auth.fake-oauth2-id-provider.user", + "sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", + "sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp"); + } + + @Test + public void create_user_when_authenticating_for_the_first_time() { + simulateRedirectionToCallback(); + enablePlugin(); + + authenticateWithFakeAuthProvider(); + + verifyUser(USER_LOGIN, USER_NAME, USER_EMAIL); + } + + private void verifyUser(String login, String name, String email) { + User user = tester.users().getByLogin(login).orElseThrow(IllegalStateException::new); + assertThat(user.getLogin()).isEqualTo(login); + assertThat(user.getName()).isEqualTo(name); + assertThat(user.getEmail()).isEqualTo(email); + assertThat(user.getActive()).isTrue(); + } + + @Test + public void authenticate_user_through_ui() throws Exception { + simulateRedirectionToCallback(); + enablePlugin(); + + Navigation nav = tester.openBrowser(); + nav.openLogin().useOAuth2().shouldBeLoggedIn(); + + verifyUser(USER_LOGIN, USER_NAME, USER_EMAIL); + } + + @Test + public void display_unauthorized_page_when_authentication_failed_in_callback() throws Exception { + simulateRedirectionToCallback(); + enablePlugin(); + + // As this property is null, the plugin will throw an exception + setServerProperty(orchestrator, "sonar.auth.fake-oauth2-id-provider.user", null); + + runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html"); + + assertThatUserDoesNotExist(USER_LOGIN); + } + + @Test + public void fail_to_authenticate_when_not_allowed_to_sign_up() throws Exception { + simulateRedirectionToCallback(); + enablePlugin(); + setServerProperty(orchestrator, "sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp", "false"); + + runSelenese(orchestrator, "/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html"); + + assertThatUserDoesNotExist(USER_LOGIN); + } + + @Test + public void display_message_in_ui_but_not_in_log_when_unauthorized_exception_in_callback() throws Exception { + simulateRedirectionToCallback(); + enablePlugin(); + setServerProperty(orchestrator, "sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", "true"); + + tester.runHtmlTests("/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html"); + + File logFile = orchestrator.getServer().getWebLogs(); + assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened"); + assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException"); + + assertThatUserDoesNotExist(USER_LOGIN); + } + + @Test + public void fail_when_email_already_exists() throws Exception { + simulateRedirectionToCallback(); + enablePlugin(); + tester.users().generate(u -> u.setLogin("another").setName("Another").setEmail(USER_EMAIL).setPassword("another")); + + tester.runHtmlTests("/user/OAuth2IdentityProviderTest/fail_when_email_already_exists.html"); + + File logFile = orchestrator.getServer().getWebLogs(); + assertThat(FileUtils.readFileToString(logFile)) + .doesNotContain("You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account"); + } + + @Test + public void provision_user_before_authentication() { + simulateRedirectionToCallback(); + enablePlugin(); + + // Provision none local user in database + tester.wsClient().users().create(CreateRequest.builder() + .setLogin(USER_LOGIN) + .setName(USER_NAME) + .setEmail(USER_EMAIL) + .setLocal(false) + .build()); + User user = tester.users().getByLogin(USER_LOGIN).get(); + assertThat(user.getLocal()).isFalse(); + assertThat(user.getExternalIdentity()).isEqualTo(USER_LOGIN); + assertThat(user.getExternalProvider()).isEqualTo("sonarqube"); + + // Authenticate with external system -> It will update external provider info + authenticateWithFakeAuthProvider(); + + user = tester.users().getByLogin(USER_LOGIN).get(); + assertThat(user.getLocal()).isFalse(); + assertThat(user.getExternalIdentity()).isEqualTo(USER_PROVIDER_ID); + assertThat(user.getExternalProvider()).isEqualTo(FAKE_PROVIDER_KEY); + } + + private void authenticateWithFakeAuthProvider() { + WsResponse response = tester.wsClient().wsConnector().call( + new GetRequest(("/sessions/init/" + FAKE_PROVIDER_KEY))); + assertThat(response.code()).isEqualTo(200); + } + + private void simulateRedirectionToCallback() { + fakeServerAuthProvider.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .addHeader("Location: " + orchestrator.getServer().getUrl() + "/oauth2/callback/" + FAKE_PROVIDER_KEY) + .setBody("Redirect to SonarQube")); + } + + private void enablePlugin() { + setServerProperty(orchestrator, "sonar.auth.fake-oauth2-id-provider.enabled", "true"); + setServerProperty(orchestrator, "sonar.auth.fake-oauth2-id-provider.url", fakeServerAuthProviderUrl); + setServerProperty(orchestrator, "sonar.auth.fake-oauth2-id-provider.user", USER_LOGIN + "," + USER_PROVIDER_ID + "," + USER_NAME + "," + USER_EMAIL); + } + + private void assertThatUserDoesNotExist(String login) { + assertThat(tester.users().getByLogin(login)).isEmpty(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/OnboardingTest.java b/tests/src/test/java/org/sonarqube/tests/user/OnboardingTest.java new file mode 100644 index 00000000000..205e17d10ab --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/OnboardingTest.java @@ -0,0 +1,161 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.WsClient; +import util.ItUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class OnboardingTest { + + private static final String ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS = "sonar.onboardingTutorial.showToNewUsers"; + + @ClassRule + public static final Orchestrator orchestrator = Orchestrator.builderEnv().build(); + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Before + public void setUp() { + resetSettings(orchestrator, null, ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS); + } + + @After + public void reset() { + resetSettings(orchestrator, null, ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS); + } + + @Test + public void by_default_new_user_does_not_see_onboarding_tutorial() { + User user = tester.users().generate(); + + verifyTutorial(user, false); + } + + @Test + public void new_user_see_onboarding_tutorial_when_show_onboarding_setting_is_enabled() { + setShownOnboardingSetting(true); + User user = tester.users().generate(); + + verifyTutorial(user, true); + } + + @Test + public void new_user_does_not_see_onboarding_tutorial_when_show_onboarding_setting_is_disabled() { + setShownOnboardingSetting(false); + User user = tester.users().generate(); + + verifyTutorial(user, false); + } + + @Test + public void new_user_does_not_see_onboarding_tutorial_when_show_onboarding_setting_is_enabled_after_user_creation() { + setShownOnboardingSetting(false); + // User is created when show onboading is disabled + User user = tester.users().generate(); + setShownOnboardingSetting(true); + + // The user doesn't see the tutorial as he was created when the show onboading setting was disabled + verifyTutorial(user, false); + } + + @Test + public void skip_onboarding_tutorial() { + setShownOnboardingSetting(true); + User user = tester.users().generate(); + + tester.as(user.getLogin()).wsClient().users().skipOnboardingTutorial(); + + verifyTutorial(user, false); + } + + @Test + public void skip_onboarding_tutorial_when_show_onboarding_setting_is_disabled() { + setShownOnboardingSetting(true); + User user = tester.users().generate(); + + tester.as(user.getLogin()).wsClient().users().skipOnboardingTutorial(); + + verifyTutorial(user, false); + } + + @Test + public void anonymous_user_does_not_see_onboarding_tutorial() { + setShownOnboardingSetting(true); + + // anonymous should not see the onboarding tutorial + verifyTutorialForAnonymous(false); + + // anonymous should not be able to skip the tutorial + ItUtils.expectHttpError(401, () -> tester.asAnonymous().wsClient().users().skipOnboardingTutorial()); + } + + @Test + public void admin_user_see_onboarding_tutorial() { + + assertThat(tester.wsClient().users().current().getShowOnboardingTutorial()).isEqualTo(true); + + // Onboarding setting has no effect as admin is created at startup + setShownOnboardingSetting(false); + assertThat(tester.wsClient().users().current().getShowOnboardingTutorial()).isEqualTo(true); + + setShownOnboardingSetting(true); + assertThat(tester.wsClient().users().current().getShowOnboardingTutorial()).isEqualTo(true); + } + + @Test + public void reactivated_user_should_see_the_onboarding_tutorial() { + setShownOnboardingSetting(true); + User user = tester.users().generate(); + tester.as(user.getLogin()).wsClient().users().skipOnboardingTutorial(); + verifyTutorial(user, false); + + tester.wsClient().users().deactivate(user.getLogin()); + User reactivatedUser = tester.users().generate(u -> u.setLogin(user.getLogin()).setName(user.getName()).setPassword(user.getLogin())); + + verifyTutorial(reactivatedUser, true); + } + + private static void setShownOnboardingSetting(boolean showTutorial) { + setServerProperty(orchestrator, ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, String.valueOf(showTutorial)); + } + + private void verifyTutorial(User user, boolean expectedTutorial) { + WsClient wsClient = tester.as(user.getLogin()).wsClient(); + assertThat(wsClient.users().current().getShowOnboardingTutorial()).isEqualTo(expectedTutorial); + } + + private void verifyTutorialForAnonymous(boolean expectedTutorial) { + WsClient wsClient = tester.asAnonymous().wsClient(); + assertThat(wsClient.users().current().getShowOnboardingTutorial()).isEqualTo(expectedTutorial); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/OrganizationIdentityProviderTest.java b/tests/src/test/java/org/sonarqube/tests/user/OrganizationIdentityProviderTest.java new file mode 100644 index 00000000000..767c76f70db --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/OrganizationIdentityProviderTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.google.common.base.Joiner; +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category6Suite; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsUserGroups.Group; +import org.sonarqube.ws.WsUsers.CreateWsResponse.User; +import org.sonarqube.ws.client.GetRequest; + +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; + +public class OrganizationIdentityProviderTest { + + @ClassRule + public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator); + + + @Before + public void setUp() { + // enable the fake authentication plugin + setServerProperty(orchestrator, "sonar.auth.fake-base-id-provider.enabled", "true"); + } + + @After + public void tearDown() { + resetSettings(orchestrator, null, "sonar.auth.fake-base-id-provider.enabled", "sonar.auth.fake-base-id-provider.user", + "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "sonar.auth.fake-base-id-provider.enabledGroupsSync", "sonar.auth.fake-base-id-provider.groups", + "sonar.auth.fake-base-id-provider.allowsUsersToSignUp"); + } + + @Test + public void default_group_is_not_added_for_new_user_when_organizations_are_enabled() { + Group group = tester.groups().generate(null); + enableUserCreationByAuthPlugin("aLogin"); + setGroupsReturnedByAuthPlugin(group.getName()); + + authenticateWithFakeAuthProvider(); + + // No default group membership + tester.groups().assertThatUserIsOnlyMemberOf(null, "aLogin", group.getName()); + } + + @Test + public void default_group_is_not_sync_for_existing_user_when_organizations_are_enabled() { + Group group = tester.groups().generate(null); + User user = tester.users().generate(); + enableUserCreationByAuthPlugin(user.getLogin()); + setGroupsReturnedByAuthPlugin(group.getName()); + + authenticateWithFakeAuthProvider(); + + // No default group membership + tester.groups().assertThatUserIsOnlyMemberOf(null, user.getLogin(), group.getName()); + } + + @Test + public void remove_default_group_when_organizations_are_enabled() { + Group group = tester.groups().generate(null); + User user = tester.users().generate(); + // Add user as member of default organization + tester.wsClient().organizations().addMember("default-organization", user.getLogin()); + tester.groups().assertThatUserIsMemberOf(null, user.getLogin(), "Members"); + enableUserCreationByAuthPlugin(user.getLogin()); + // No group is returned by the plugin + setGroupsReturnedByAuthPlugin(); + + authenticateWithFakeAuthProvider(); + + // No default group membership + tester.groups().assertThatUserIsOnlyMemberOf(null, user.getLogin()); + } + + private static void enableUserCreationByAuthPlugin(String login) { + setServerProperty(orchestrator, "sonar.auth.fake-base-id-provider.user", login + ",fake-john,John,john@email.com"); + } + + private static void setGroupsReturnedByAuthPlugin(String... groups) { + setServerProperty(orchestrator, "sonar.auth.fake-base-id-provider.enabledGroupsSync", "true"); + if (groups.length > 0) { + setServerProperty(orchestrator, "sonar.auth.fake-base-id-provider.groups", Joiner.on(",").join(groups)); + } + } + + private void authenticateWithFakeAuthProvider() { + tester.wsClient().wsConnector().call( + new GetRequest("/sessions/init/fake-base-id-provider")) + .failIfNotSuccessful(); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java new file mode 100644 index 00000000000..5e4170fceb1 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java @@ -0,0 +1,389 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.sonar.orchestrator.Orchestrator; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.wsclient.Host; +import org.sonar.wsclient.Sonar; +import org.sonar.wsclient.base.HttpException; +import org.sonar.wsclient.connectors.HttpClient4Connector; +import org.sonar.wsclient.services.AuthenticationQuery; +import org.sonar.wsclient.user.UserParameters; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.user.CreateRequest; +import util.user.UserRule; +import util.user.Users; + +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.pluginArtifact; +import static util.ItUtils.resetSettings; +import static util.ItUtils.setServerProperty; +import static util.selenium.Selenese.runSelenese; + +/** + * Test REALM authentication. + * + * It starts its own server as it's using a different authentication system + */ +public class RealmAuthenticationTest { + + private static final String TECH_USER = "techUser"; + private static final String USER_LOGIN = "tester"; + private static final String ADMIN_USER_LOGIN = "admin-user"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + /** + * Property from security-plugin for user management. + */ + private static final String USERS_PROPERTY = "sonar.fakeauthenticator.users"; + + @ClassRule + public static final Orchestrator orchestrator = Orchestrator.builderEnv() + .addPlugin(pluginArtifact("security-plugin")) + .setServerProperty("sonar.security.realm", "FakeRealm") + .build(); + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + @Before + @After + public void resetData() throws Exception { + resetSettings(orchestrator, null, USERS_PROPERTY, "sonar.security.updateUserAttributes"); + } + + @Before + public void initAdminUser() throws Exception { + userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); + } + + @After + public void deleteAdminUser() { + userRule.resetUsers(); + } + + /** + * SONAR-3137, SONAR-2292 + * Restriction on password length (minimum 4 characters) should be disabled, when external system enabled. + */ + @Test + public void shouldSynchronizeDetailsAndGroups() { + // Given clean Sonar installation and no users in external system + String username = USER_LOGIN; + String password = "123"; + Map<String, String> users = Maps.newHashMap(); + + // When user created in external system + users.put(username + ".password", password); + users.put(username + ".name", "Tester Testerovich"); + users.put(username + ".email", "tester@example.org"); + users.put(username + ".groups", "sonar-user"); + updateUsersInExtAuth(users); + // Then + verifyAuthenticationIsOk(username, password); + + // with external details and groups + runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html"); + + // SONAR-4462 + runSelenese(orchestrator, "/user/ExternalAuthenticationTest/system-info.html"); + } + + /** + * SONAR-4034 + */ + @Test + public void shouldUpdateDetailsByDefault() { + // Given clean Sonar installation and no users in external system + String username = USER_LOGIN; + String password = "123"; + Map<String, String> users = Maps.newHashMap(); + + // When user created in external system + users.put(username + ".password", password); + users.put(username + ".name", "Tester Testerovich"); + users.put(username + ".email", "tester@example.org"); + users.put(username + ".groups", "sonar-user"); + updateUsersInExtAuth(users); + // Then + verifyAuthenticationIsOk(username, password); + + // with external details and groups + // TODO replace by WS ? Or with new Selenese utils + runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html"); + + // Now update user details + users.put(username + ".name", "Tester2 Testerovich"); + users.put(username + ".email", "tester2@example.org"); + updateUsersInExtAuth(users); + // Then + verifyAuthenticationIsOk(username, password); + + // with external details and groups updated + runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details2.html"); + } + + /** + * SONAR-3138 + */ + @Test + public void shouldNotFallback() { + // Given clean Sonar installation and no users in external system + String login = USER_LOGIN; + String password = "1234567"; + Map<String, String> users = Maps.newHashMap(); + + // When user created in external system + users.put(login + ".password", password); + updateUsersInExtAuth(users); + // Then + verifyAuthenticationIsOk(login, password); + + // When external system does not work + users.remove(login + ".password"); + updateUsersInExtAuth(users); + // Then + verifyAuthenticationIsNotOk(login, password); + } + + /** + * SONAR-4543 + */ + @Test + public void adminIsLocalAccountByDefault() { + // Given clean Sonar installation and no users in external system + String login = "admin"; + String localPassword = "admin"; + String remotePassword = "nimda"; + Map<String, String> users = Maps.newHashMap(); + + // When admin created in external system with a different password + users.put(login + ".password", remotePassword); + updateUsersInExtAuth(users); + + // Then this is local DB that should be used + verifyAuthenticationIsNotOk(login, remotePassword); + verifyAuthenticationIsOk(login, localPassword); + } + + /** + * SONAR-1334, SONAR-3185 (createUsers=true is default) + */ + @Test + public void shouldCreateNewUsers() { + // Given clean Sonar installation and no users in external system + String username = USER_LOGIN; + String password = "1234567"; + Map<String, String> users = Maps.newHashMap(); + + // When user not exists in external system + // Then + verifyAuthenticationIsNotOk(username, password); + + // When user created in external system + users.put(username + ".password", password); + updateUsersInExtAuth(users); + // Then + verifyAuthenticationIsOk(username, password); + verifyAuthenticationIsNotOk(username, "wrong"); + } + + // SONAR-3258 + @Test + public void shouldAutomaticallyReactivateDeletedUser() throws Exception { + // Given clean Sonar installation and no users in external system + + // Let's create and delete the user "tester" in Sonar DB + runSelenese(orchestrator, "/user/ExternalAuthenticationTest/create-and-delete-user.html"); + + // And now update the security with the user that was deleted + String login = USER_LOGIN; + String password = "1234567"; + Map<String, String> users = Maps.newHashMap(); + users.put(login + ".password", password); + updateUsersInExtAuth(users); + // check that the deleted/deactivated user "tester" has been reactivated and can now log in + verifyAuthenticationIsOk(login, password); + } + + /** + * SONAR-7036 + */ + @Test + public void update_password_of_technical_user() throws Exception { + // Create user in external authentication + updateUsersInExtAuth(ImmutableMap.of(USER_LOGIN + ".password", USER_LOGIN)); + verifyAuthenticationIsOk(USER_LOGIN, USER_LOGIN); + + // Create technical user in db + createUserInDb(TECH_USER, "old_password"); + assertThat(checkAuthenticationThroughWebService(TECH_USER, "old_password")).isTrue(); + + // Updating password of technical user is allowed + updateUserPasswordInDb(TECH_USER, "new_password"); + assertThat(checkAuthenticationThroughWebService(TECH_USER, "new_password")).isTrue(); + + // But updating password of none local user is not allowed + try { + updateUserPasswordInDb(USER_LOGIN, "new_password"); + fail(); + } catch (HttpException e) { + verifyHttpException(e, 400); + } + } + + /** + * SONAR-7640 + */ + @Test + public void authentication_with_ws() throws Exception { + // Given clean Sonar installation and no users in external system + String login = USER_LOGIN; + String password = "1234567"; + Map<String, String> users = Maps.newHashMap(); + + // When user created in external system + users.put(login + ".password", password); + updateUsersInExtAuth(users); + + verifyAuthenticationIsOk(login, password); + verifyAuthenticationIsNotOk("wrong", password); + verifyAuthenticationIsNotOk(login, "wrong"); + verifyAuthenticationIsNotOk(login, null); + verifyAuthenticationIsOk(null, null); + + setServerProperty(orchestrator, "sonar.forceAuthentication", "true"); + + verifyAuthenticationIsOk(login, password); + verifyAuthenticationIsNotOk("wrong", password); + verifyAuthenticationIsNotOk(login, "wrong"); + verifyAuthenticationIsNotOk(login, null); + verifyAuthenticationIsNotOk(null, null); + } + + @Test + public void allow_user_login_with_2_characters() { + String username = "jo"; + String password = "1234567"; + updateUsersInExtAuth(ImmutableMap.of(username + ".password", password)); + + verifyAuthenticationIsOk(username, password); + } + + @Test + public void provision_user_before_authentication() { + newAdminWsClient(orchestrator).users().create(CreateRequest.builder() + .setLogin(USER_LOGIN) + .setName("Tester Testerovich") + .setEmail("tester@example.org") + .setLocal(false) + .build()); + // The user is created in SonarQube but doesn't exist yet in external authentication system + verifyAuthenticationIsNotOk(USER_LOGIN, "123"); + + updateUsersInExtAuth(ImmutableMap.of( + USER_LOGIN + ".password", "123", + USER_LOGIN + ".name", "Tester Testerovich", + USER_LOGIN + ".email", "tester@example.org")); + + verifyAuthenticationIsOk(USER_LOGIN, "123"); + assertThat(userRule.getUserByLogin(USER_LOGIN).get()) + .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider) + .containsOnly(false, USER_LOGIN, "sonarqube"); + } + + private void verifyHttpException(Exception e, int expectedCode) { + assertThat(e).isInstanceOf(HttpException.class); + HttpException exception = (HttpException) e; + assertThat(exception.status()).isEqualTo(expectedCode); + } + + private boolean checkAuthenticationThroughWebService(String login, String password) { + return createWsClient(login, password).find(new AuthenticationQuery()).isValid(); + } + + /** + * Updates information about users in security-plugin. + */ + private static void updateUsersInExtAuth(Map<String, String> users) { + setServerProperty(orchestrator, USERS_PROPERTY, format(users)); + } + + private void createUserInDb(String login, String password) { + orchestrator.getServer().adminWsClient().userClient().create(UserParameters.create().login(login).name(login) + .password(password).passwordConfirmation(password)); + } + + private void updateUserPasswordInDb(String login, String newPassword) { + orchestrator.getServer().adminWsClient().post("/api/users/change_password", "login", login, "password", newPassword); + } + + /** + * Utility method to create {@link Sonar} with specified {@code username} and {@code password}. + * Orchestrator does not provide such method. + */ + private Sonar createWsClient(String username, String password) { + return new Sonar(new HttpClient4Connector(new Host(orchestrator.getServer().getUrl(), username, password))); + } + + @CheckForNull + private static String format(Map<String, String> map) { + if (map.isEmpty()) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry<String, String> entry : map.entrySet()) { + sb.append(entry.getKey()).append('=').append(entry.getValue()).append('\n'); + } + return sb.toString(); + } + + private void verifyAuthenticationIsOk(String login, String password) { + assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_OK); + } + + private void verifyAuthenticationIsNotOk(String login, String password) { + assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_UNAUTHORIZED); + } + + private WsResponse checkAuthenticationWithWebService(String login, String password) { + // Call any WS + return newUserWsClient(orchestrator, login, password).wsConnector().call(new GetRequest("api/rules/search")); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java new file mode 100644 index 00000000000..2860b09dfb7 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/SsoAuthenticationTest.java @@ -0,0 +1,162 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import java.net.URLEncoder; +import java.util.List; +import javax.annotation.Nullable; +import okhttp3.Response; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import util.user.UserRule; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.call; + +/** + * Test SSO authentication (using HTTP headers). + * <p> + * It starts its own server as it's using a different authentication system + */ +public class SsoAuthenticationTest { + + private static final String LOGIN_HEADER = "H-Login"; + private static final String NAME_HEADER = "H-Name"; + private static final String EMAIL_HEADER = "H-Email"; + private static final String GROUPS_HEADER = "H-Groups"; + + static final String USER_LOGIN = "tester"; + static final String USER_NAME = "Tester"; + static final String USER_EMAIL = "tester@email.com"; + + static final String GROUP_1 = "group-1"; + static final String GROUP_2 = "group-2"; + static final String GROUP_3 = "group-3"; + + @ClassRule + public static final Orchestrator orchestrator = Orchestrator.builderEnv() + .setServerProperty("sonar.web.sso.enable", "true") + .setServerProperty("sonar.web.sso.loginHeader", LOGIN_HEADER) + .setServerProperty("sonar.web.sso.nameHeader", NAME_HEADER) + .setServerProperty("sonar.web.sso.emailHeader", EMAIL_HEADER) + .setServerProperty("sonar.web.sso.groupsHeader", GROUPS_HEADER) + .build(); + + @ClassRule + public static UserRule USER_RULE = UserRule.from(orchestrator); + + @Before + public void resetData() throws Exception { + USER_RULE.resetUsers(); + } + + @Test + public void authenticate() { + doCall(USER_LOGIN, USER_NAME, USER_EMAIL, null); + + USER_RULE.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL); + } + + @Test + public void authenticate_with_only_login() throws Exception { + doCall(USER_LOGIN, null, null, null); + + USER_RULE.verifyUserExists(USER_LOGIN, USER_LOGIN, null); + } + + @Test + public void update_user_when_headers_are_updated() { + doCall(USER_LOGIN, USER_NAME, USER_EMAIL, null); + USER_RULE.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL); + + // As we don't keep the JWT token is the test, the user is updated + doCall(USER_LOGIN, "new name", "new email", null); + USER_RULE.verifyUserExists(USER_LOGIN, "new name", "new email"); + } + + @Test + public void authenticate_with_groups() { + doCall(USER_LOGIN, null, null, GROUP_1); + + USER_RULE.verifyUserGroupMembership(USER_LOGIN, GROUP_1, "sonar-users"); + } + + @Test + public void synchronize_groups_when_authenticating_existing_user() throws Exception { + USER_RULE.createGroup(GROUP_1); + USER_RULE.createGroup(GROUP_2); + USER_RULE.createGroup(GROUP_3); + USER_RULE.createUser(USER_LOGIN, "password"); + USER_RULE.associateGroupsToUser(USER_LOGIN, GROUP_1, GROUP_2); + + doCall(USER_LOGIN, null, null, GROUP_2 + "," + GROUP_3); + + USER_RULE.verifyUserGroupMembership(USER_LOGIN, GROUP_2, GROUP_3, "sonar-users"); + } + + @Test + public void authentication_with_local_user_is_possible_when_no_header() throws Exception { + USER_RULE.createUser(USER_LOGIN, "password"); + + checkLocalAuthentication(USER_LOGIN, "password"); + } + + @Test + public void display_message_in_ui_but_not_in_log_when_unauthorized_exception() throws Exception { + Response response = doCall("invalid login $", null, null, null); + + assertThat(response.code()).isEqualTo(200); + assertThat(response.request().url().toString()).contains("sessions/unauthorized"); + + List<String> logsLines = FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8); + assertThat(logsLines).doesNotContain("org.sonar.server.exceptions.BadRequestException: Use only letters, numbers, and .-_@ please."); + USER_RULE.verifyUserDoesNotExist(USER_LOGIN); + } + + @Test + public void fail_when_email_already_exists() throws Exception { + USER_RULE.createUser("another", "Another", USER_EMAIL, "another"); + + Response response = doCall(USER_LOGIN, USER_NAME, USER_EMAIL, null); + + String expectedError = "You can't sign up because email 'tester@email.com' is already used by an existing user. This means that you probably already registered with another account"; + assertThat(response.code()).isEqualTo(200); + assertThat(response.request().url().toString()).contains(URLEncoder.encode(expectedError, UTF_8.name())); + assertThat(FileUtils.readLines(orchestrator.getServer().getWebLogs(), UTF_8)).doesNotContain(expectedError); + } + + private static Response doCall(String login, @Nullable String name, @Nullable String email, @Nullable String groups) { + return call(orchestrator.getServer().getUrl(), + LOGIN_HEADER, login, + NAME_HEADER, name, + EMAIL_HEADER, email, + GROUPS_HEADER, groups); + } + + private boolean checkLocalAuthentication(String login, String password) { + String result = orchestrator.getServer().wsClient(login, password).get("/api/authentication/validate"); + return result.contains("{\"valid\":true}"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/user/UsersPageTest.java b/tests/src/test/java/org/sonarqube/tests/user/UsersPageTest.java new file mode 100644 index 00000000000..b983b0e69e0 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/UsersPageTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category1Suite; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.user.GroupsRequest; +import util.selenium.Selenese; +import util.user.UserRule; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; + +public class UsersPageTest { + + private static final String ADMIN_USER_LOGIN = "admin-user"; + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public UserRule userRule = UserRule.from(orchestrator); + + private WsClient adminClient = newAdminWsClient(orchestrator); + + @Before + public void initAdminUser() throws Exception { + userRule.createAdminUser(ADMIN_USER_LOGIN, ADMIN_USER_LOGIN); + } + + @After + public void deleteAdminUser() { + userRule.resetUsers(); + } + + @Test + public void generate_and_revoke_user_token() { + Selenese.runSelenese(orchestrator, "/user/UsersPageTest/generate_and_revoke_user_token.html"); + } + + @Test + public void admin_should_change_his_own_password() { + Selenese.runSelenese(orchestrator, "/user/UsersPageTest/admin_should_change_its_own_password.html"); + } + + @Test + public void return_groups_belonging_to_a_user() { + String login = randomAlphabetic(10); + String group = randomAlphabetic(10); + userRule.createUser(login, login); + userRule.createGroup(group); + userRule.associateGroupsToUser(login, group); + + List<WsUsers.GroupsWsResponse.Group> result = adminClient.users().groups(GroupsRequest.builder().setLogin(login).build()).getGroupsList(); + + assertThat(result).extracting(WsUsers.GroupsWsResponse.Group::getName).contains(group); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/webhook/ExternalServer.java b/tests/src/test/java/org/sonarqube/tests/webhook/ExternalServer.java new file mode 100644 index 00000000000..33301c85e3b --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/webhook/ExternalServer.java @@ -0,0 +1,97 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.webhook; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.rules.ExternalResource; + +/** + * This web server listens to requests sent by webhooks + */ +class ExternalServer extends ExternalResource { + private final Server jetty; + private final List<PayloadRequest> payloads = new ArrayList<>(); + + ExternalServer() { + jetty = new Server(0); + jetty.setHandler(new AbstractHandler() { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + if ("POST".equalsIgnoreCase(request.getMethod())) { + String json = request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); + Map<String, String> httpHeaders = new HashMap<>(); + Enumeration<String> headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String key = headerNames.nextElement(); + httpHeaders.put(key, request.getHeader(key)); + } + payloads.add(new PayloadRequest(target, httpHeaders, json)); + } + + response.setStatus(target.equals("/fail") ? 500 : 200); + baseRequest.setHandled(true); + } + }); + } + + @Override + protected void before() throws Throwable { + jetty.start(); + } + + @Override + protected void after() { + try { + jetty.stop(); + } catch (Exception e) { + throw new IllegalStateException("Cannot stop Jetty"); + } + } + + List<PayloadRequest> getPayloadRequests() { + return payloads; + } + + List<PayloadRequest> getPayloadRequestsOnPath(String path) { + return payloads.stream().filter(p -> p.getPath().equals(path)).collect(Collectors.toList()); + } + + String urlFor(String path) { + return jetty.getURI().resolve(path).toString(); + } + + void clear() { + payloads.clear(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/webhook/PayloadRequest.java b/tests/src/test/java/org/sonarqube/tests/webhook/PayloadRequest.java new file mode 100644 index 00000000000..e641e71648d --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/webhook/PayloadRequest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.webhook; + +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +/** + * Request received by {@link ExternalServer} + */ +class PayloadRequest { + private final String path; + private final Map<String, String> httpHeaders; + private final String json; + + PayloadRequest(String path, Map<String, String> httpHeaders, String json) { + this.path = requireNonNull(path); + this.httpHeaders = requireNonNull(httpHeaders); + this.json = requireNonNull(json); + } + + Map<String, String> getHttpHeaders() { + return httpHeaders; + } + + String getJson() { + return json; + } + + String getPath() { + return path; + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java b/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java new file mode 100644 index 00000000000..975a5dc9366 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java @@ -0,0 +1,290 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.webhook; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category3Suite; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.Webhooks; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.project.DeleteRequest; +import org.sonarqube.ws.client.setting.ResetRequest; +import org.sonarqube.ws.client.setting.SetRequest; +import org.sonarqube.ws.client.webhook.DeliveriesRequest; +import util.ItUtils; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.IntStream.range; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.jsonToMap; +import static util.ItUtils.runProjectAnalysis; + +public class WebhooksTest { + + private static final String PROJECT_KEY = "my-project"; + private static final String PROJECT_NAME = "My Project"; + private static final String GLOBAL_WEBHOOK_PROPERTY = "sonar.webhooks.global"; + private static final String PROJECT_WEBHOOK_PROPERTY = "sonar.webhooks.project"; + + @ClassRule + public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR; + + @ClassRule + public static ExternalServer externalServer = new ExternalServer(); + + private WsClient adminWs = ItUtils.newAdminWsClient(orchestrator); + + @Before + public void setUp() throws Exception { + externalServer.clear(); + } + + @Before + @After + public void reset() throws Exception { + disableGlobalWebhooks(); + try { + // delete project and related properties/webhook deliveries + adminWs.projects().delete(DeleteRequest.builder().setKey(PROJECT_KEY).build()); + } catch (HttpException e) { + // ignore because project may not exist + } + } + + @Test + public void call_multiple_global_and_project_webhooks_when_analysis_is_done() { + orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_NAME); + enableGlobalWebhooks( + new Webhook("Jenkins", externalServer.urlFor("/jenkins")), + new Webhook("HipChat", externalServer.urlFor("/hipchat"))); + enableProjectWebhooks(PROJECT_KEY, + new Webhook("Burgr", externalServer.urlFor("/burgr"))); + + analyseProject(); + + // the same payload has been sent to three servers + assertThat(externalServer.getPayloadRequests()).hasSize(3); + PayloadRequest request = externalServer.getPayloadRequests().get(0); + for (int i = 1; i < 3; i++) { + PayloadRequest r = externalServer.getPayloadRequests().get(i); + assertThat(request.getJson()).isEqualTo(r.getJson()); + } + + // verify HTTP headers + assertThat(request.getHttpHeaders().get("X-SonarQube-Project")).isEqualTo(PROJECT_KEY); + + // verify content of payload + Map<String, Object> payload = jsonToMap(request.getJson()); + assertThat(payload.get("status")).isEqualTo("SUCCESS"); + assertThat(payload.get("analysedAt")).isNotNull(); + Map<String, String> project = (Map<String, String>) payload.get("project"); + assertThat(project.get("key")).isEqualTo(PROJECT_KEY); + assertThat(project.get("name")).isEqualTo(PROJECT_NAME); + Map<String, Object> gate = (Map<String, Object>) payload.get("qualityGate"); + assertThat(gate.get("name")).isEqualTo("SonarQube way"); + assertThat(gate.get("status")).isEqualTo("OK"); + assertThat(gate.get("conditions")).isNotNull(); + + // verify list of persisted deliveries (api/webhooks/deliveries) + List<Webhooks.Delivery> deliveries = getPersistedDeliveries(); + assertThat(deliveries).hasSize(3); + for (Webhooks.Delivery delivery : deliveries) { + assertThatPersistedDeliveryIsValid(delivery); + assertThat(delivery.getSuccess()).isTrue(); + assertThat(delivery.getHttpStatus()).isEqualTo(200); + assertThat(delivery.getName()).isIn("Jenkins", "HipChat", "Burgr"); + assertThat(delivery.hasErrorStacktrace()).isFalse(); + // payload is available only in api/webhooks/delivery to avoid loading multiple DB CLOBs + assertThat(delivery.hasPayload()).isFalse(); + } + + // verify detail of persisted delivery (api/webhooks/delivery) + Webhooks.Delivery detail = getDetailOfPersistedDelivery(deliveries.get(0)); + assertThatPersistedDeliveryIsValid(detail); + assertThat(detail.getPayload()).isEqualTo(request.getJson()); + } + + @Test + public void persist_delivery_as_failed_if_external_server_returns_an_error_code() { + enableGlobalWebhooks( + new Webhook("Fail", externalServer.urlFor("/fail")), + new Webhook("HipChat", externalServer.urlFor("/hipchat"))); + + analyseProject(); + + // all webhooks are called, even if one returns an error code + assertThat(externalServer.getPayloadRequests()).hasSize(2); + + // verify persisted deliveries + Webhooks.Delivery failedDelivery = getPersistedDeliveryByName("Fail"); + assertThatPersistedDeliveryIsValid(failedDelivery); + assertThat(failedDelivery.getSuccess()).isFalse(); + assertThat(failedDelivery.getHttpStatus()).isEqualTo(500); + + Webhooks.Delivery successfulDelivery = getPersistedDeliveryByName("HipChat"); + assertThatPersistedDeliveryIsValid(successfulDelivery); + assertThat(successfulDelivery.getSuccess()).isTrue(); + assertThat(successfulDelivery.getHttpStatus()).isEqualTo(200); + } + + /** + * Restrict calls to ten webhooks per type (global or project) + */ + @Test + public void do_not_become_a_denial_of_service_attacker() { + orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_NAME); + + List<Webhook> globalWebhooks = range(0, 15).mapToObj(i -> new Webhook("G" + i, externalServer.urlFor("/global"))).collect(Collectors.toList()); + enableGlobalWebhooks(globalWebhooks.toArray(new Webhook[globalWebhooks.size()])); + List<Webhook> projectWebhooks = range(0, 15).mapToObj(i -> new Webhook("P" + i, externalServer.urlFor("/project"))).collect(Collectors.toList()); + enableProjectWebhooks(PROJECT_KEY, projectWebhooks.toArray(new Webhook[projectWebhooks.size()])); + + analyseProject(); + + // only the first ten global webhooks and ten project webhooks are called + assertThat(externalServer.getPayloadRequests()).hasSize(10 + 10); + assertThat(externalServer.getPayloadRequestsOnPath("/global")).hasSize(10); + assertThat(externalServer.getPayloadRequestsOnPath("/project")).hasSize(10); + + // verify persisted deliveries + assertThat(getPersistedDeliveries()).hasSize(10 + 10); + } + + @Test + public void persist_delivery_as_failed_if_webhook_url_is_malformed() { + enableGlobalWebhooks(new Webhook("Jenkins", "this_is_not_an_url")); + + analyseProject(); + + assertThat(externalServer.getPayloadRequests()).isEmpty(); + + // verify persisted deliveries + Webhooks.Delivery delivery = getPersistedDeliveryByName("Jenkins"); + Webhooks.Delivery detail = getDetailOfPersistedDelivery(delivery); + + assertThat(detail.getSuccess()).isFalse(); + assertThat(detail.hasHttpStatus()).isFalse(); + assertThat(detail.hasDurationMs()).isFalse(); + assertThat(detail.getPayload()).isNotEmpty(); + assertThat(detail.getErrorStacktrace()) + .contains("java.lang.IllegalArgumentException") + .contains("unexpected url") + .contains("this_is_not_an_url"); + } + + @Test + public void ignore_webhook_if_url_is_missing() { + // property sets, as used to define webhooks, do + // not allow to validate values yet + enableGlobalWebhooks(new Webhook("Jenkins", null)); + + analyseProject(); + + assertThat(externalServer.getPayloadRequests()).isEmpty(); + assertThat(getPersistedDeliveries()).isEmpty(); + } + + private void analyseProject() { + runProjectAnalysis(orchestrator, "shared/xoo-sample", + "sonar.projectKey", PROJECT_KEY, + "sonar.projectName", PROJECT_NAME); + } + + private List<Webhooks.Delivery> getPersistedDeliveries() { + DeliveriesRequest deliveriesReq = DeliveriesRequest.builder().setComponentKey(PROJECT_KEY).build(); + return adminWs.webhooks().deliveries(deliveriesReq).getDeliveriesList(); + } + + private Webhooks.Delivery getPersistedDeliveryByName(String webhookName) { + List<Webhooks.Delivery> deliveries = getPersistedDeliveries(); + return deliveries.stream().filter(d -> d.getName().equals(webhookName)).findFirst().get(); + } + + private Webhooks.Delivery getDetailOfPersistedDelivery(Webhooks.Delivery delivery) { + Webhooks.Delivery detail = adminWs.webhooks().delivery(delivery.getId()).getDelivery(); + return requireNonNull(detail); + } + + private void assertThatPersistedDeliveryIsValid(Webhooks.Delivery delivery) { + assertThat(delivery.getId()).isNotEmpty(); + assertThat(delivery.getName()).isNotEmpty(); + assertThat(delivery.hasSuccess()).isTrue(); + assertThat(delivery.getHttpStatus()).isGreaterThanOrEqualTo(200); + assertThat(delivery.getDurationMs()).isGreaterThanOrEqualTo(0); + assertThat(delivery.getAt()).isNotEmpty(); + assertThat(delivery.getComponentKey()).isEqualTo(PROJECT_KEY); + assertThat(delivery.getUrl()).startsWith(externalServer.urlFor("/")); + } + + private void enableGlobalWebhooks(Webhook... webhooks) { + enableWebhooks(null, GLOBAL_WEBHOOK_PROPERTY, webhooks); + } + + private void enableProjectWebhooks(String projectKey, Webhook... webhooks) { + enableWebhooks(projectKey, PROJECT_WEBHOOK_PROPERTY, webhooks); + } + + private void enableWebhooks(@Nullable String projectKey, String property, Webhook... webhooks) { + List<String> webhookIds = new ArrayList<>(); + for (int i = 0; i < webhooks.length; i++) { + Webhook webhook = webhooks[i]; + String id = String.valueOf(i + 1); + webhookIds.add(id); + setProperty(projectKey, property + "." + id + ".name", webhook.name); + setProperty(projectKey, property + "." + id + ".url", webhook.url); + } + setProperty(projectKey, property, StringUtils.join(webhookIds, ",")); + } + + private void disableGlobalWebhooks() { + setProperty(null, GLOBAL_WEBHOOK_PROPERTY, null); + } + + private void setProperty(@Nullable String componentKey, String key, @Nullable String value) { + if (value == null) { + ResetRequest req = ResetRequest.builder().setKeys(key).setComponent(componentKey).build(); + adminWs.settings().reset(req); + } else { + SetRequest req = SetRequest.builder().setKey(key).setValue(value).setComponent(componentKey).build(); + adminWs.settings().set(req); + } + } + + private static class Webhook { + private final String name; + private final String url; + + Webhook(@Nullable String name, @Nullable String url) { + this.name = name; + this.url = url; + } + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/ws/RoutesTest.java b/tests/src/test/java/org/sonarqube/tests/ws/RoutesTest.java new file mode 100644 index 00000000000..6db3a4d30b6 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ws/RoutesTest.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ws; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newWsClient; + +public class RoutesTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Test + public void redirect_profiles_export_to_api_qualityprofiles_export() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("profiles/export?language=xoo&format=XooFakeExporter")); + assertThat(response.isSuccessful()).isTrue(); + assertThat(response.requestUrl()).endsWith("/api/qualityprofiles/export?language=xoo&format=XooFakeExporter"); + assertThat(response.content()).isEqualTo("xoo -> Basic -> 1"); + + // Check 'name' parameter is taken into account + assertThat(newWsClient(orchestrator).wsConnector().call(new GetRequest("profiles/export?language=xoo&name=empty&format=XooFakeExporter")).content()).isEqualTo("xoo -> empty -> 0"); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/ws/WsLocalCallTest.java b/tests/src/test/java/org/sonarqube/tests/ws/WsLocalCallTest.java new file mode 100644 index 00000000000..8e822e286dd --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ws/WsLocalCallTest.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ws; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsClientFactories; +import org.sonarqube.ws.client.WsResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newWsClient; + +/** + * Tests the ability for a web service to call another web services. + */ +public class WsLocalCallTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Test + public void gets_protobuf() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("local_ws_call/protobuf_data")); + assertThat(response.isSuccessful()).isTrue(); + } + + @Test + public void gets_json() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("local_ws_call/json_data")); + assertThat(response.isSuccessful()).isTrue(); + } + + @Test + public void propagates_authorization_rights() { + WsClient wsClient = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() + .url(orchestrator.getServer().getUrl()) + .credentials("admin", "admin") + .build()); + WsResponse response = wsClient.wsConnector().call(new GetRequest("local_ws_call/require_permission")); + assertThat(response.isSuccessful()).isTrue(); + } + + @Test + public void fails_if_requires_permissions() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("local_ws_call/require_permission")); + + // this is not the unauthorized code as plugin forces it to 500 + assertThat(response.code()).isEqualTo(500); + } + +} diff --git a/tests/src/test/java/org/sonarqube/tests/ws/WsTest.java b/tests/src/test/java/org/sonarqube/tests/ws/WsTest.java new file mode 100644 index 00000000000..50fdd7758c5 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ws/WsTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ws; + +import com.sonar.orchestrator.Orchestrator; +import org.sonarqube.tests.Category4Suite; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newWsClient; + +public class WsTest { + + @ClassRule + public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR; + + @Test + public void gets_protobuf() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/issues/search.protobuf")); + assertThat(response.isSuccessful()).isTrue(); + assertThat(response.contentType()).contains("application/x-protobuf"); + } + + @Test + public void gets_json() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/issues/search.json")); + assertThat(response.isSuccessful()).isTrue(); + assertThat(response.contentType()).contains("application/json"); + } + + /** + * SONAR-7484 + */ + @Test + public void fail_on_unknown_extension() { + WsResponse response = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/issues/search.unknown")); + assertThat(response.isSuccessful()).isFalse(); + assertThat(response.code()).isEqualTo(HTTP_BAD_REQUEST); + } + +} diff --git a/tests/src/test/java/util/ItUtils.java b/tests/src/test/java/util/ItUtils.java new file mode 100644 index 00000000000..3ef0d275549 --- /dev/null +++ b/tests/src/test/java/util/ItUtils.java @@ -0,0 +1,534 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +import com.google.common.base.Splitter; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.BuildResult; +import com.sonar.orchestrator.build.SonarRunner; +import com.sonar.orchestrator.container.Server; +import com.sonar.orchestrator.locator.FileLocation; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.io.FileUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.junit.Assert; +import org.sonar.wsclient.issue.Issue; +import org.sonar.wsclient.issue.IssueClient; +import org.sonar.wsclient.issue.IssueQuery; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsComponents.Component; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.WsMeasures.Measure; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsClientFactories; +import org.sonarqube.ws.client.component.ShowWsRequest; +import org.sonarqube.ws.client.measure.ComponentWsRequest; +import org.sonarqube.ws.client.qualityprofile.RestoreWsRequest; +import org.sonarqube.ws.client.setting.ResetRequest; +import org.sonarqube.ws.client.setting.SetRequest; + +import static com.google.common.base.Preconditions.checkState; +import static com.sonar.orchestrator.container.Server.ADMIN_LOGIN; +import static com.sonar.orchestrator.container.Server.ADMIN_PASSWORD; +import static java.lang.Double.parseDouble; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Locale.ENGLISH; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class ItUtils { + public static final Splitter LINE_SPLITTER = Splitter.on(System.getProperty("line.separator")); + + private ItUtils() { + } + + public static FileLocation xooPlugin() { + return FileLocation.byWildcardMavenFilename(new File("../plugins/sonar-xoo-plugin/target"), "sonar-xoo-plugin-*.jar"); + } + + public static List<Issue> getAllServerIssues(Orchestrator orchestrator) { + IssueClient issueClient = orchestrator.getServer().wsClient().issueClient(); + return issueClient.find(IssueQuery.create()).list(); + } + + /** + * @deprecated replaced by {@link Tester#wsClient()} + */ + @Deprecated + public static WsClient newAdminWsClient(Orchestrator orchestrator) { + return newUserWsClient(orchestrator, ADMIN_LOGIN, ADMIN_PASSWORD); + } + + /** + * @deprecated replaced by {@link Tester#wsClient()} + */ + @Deprecated + public static WsClient newWsClient(Orchestrator orchestrator) { + return newUserWsClient(orchestrator, null, null); + } + + /** + * @deprecated replaced by {@link Tester#wsClient()} + */ + @Deprecated + public static WsClient newUserWsClient(Orchestrator orchestrator, @Nullable String login, @Nullable String password) { + Server server = orchestrator.getServer(); + return WsClientFactories.getDefault().newClient(HttpConnector.newBuilder() + .url(server.getUrl()) + .credentials(login, password) + .build()); + } + + /** + * Locate the directory of sample project + * + * @param relativePath path related to the directory it/projects, for example "qualitygate/xoo-sample" + */ + public static File projectDir(String relativePath) { + File dir = new File("projects/" + relativePath); + if (!dir.exists() || !dir.isDirectory()) { + throw new IllegalStateException("Directory does not exist: " + dir.getAbsolutePath()); + } + return dir; + } + + /** + * Locate the artifact of a fake plugin stored in it/plugins. + * + * @param dirName the directory of it/plugins, for example "sonar-fake-plugin". + * It assumes that version is 1.0-SNAPSHOT + */ + public static FileLocation pluginArtifact(String dirName) { + return FileLocation.byWildcardMavenFilename(new File("plugins/" + dirName + "/target"), dirName + "-*.jar"); + } + + /** + * Locate the pom file of a sample project + * + * @param projectName project path related to the directory it/projects, for example "qualitygate/xoo-sample" + */ + public static File projectPom(String projectName) { + File pom = new File(projectDir(projectName), "pom.xml"); + if (!pom.exists() || !pom.isFile()) { + throw new IllegalStateException("pom file does not exist: " + pom.getAbsolutePath()); + } + return pom; + } + + public static String sanitizeTimezones(String s) { + return s.replaceAll("[\\+\\-]\\d\\d\\d\\d", "+0000"); + } + + public static JSONObject getJSONReport(BuildResult result) { + Pattern pattern = Pattern.compile("Export issues to (.*?).json"); + Matcher m = pattern.matcher(result.getLogs()); + if (m.find()) { + String s = m.group(1); + File path = new File(s + ".json"); + assertThat(path).exists(); + try { + return (JSONObject) JSONValue.parse(FileUtils.readFileToString(path)); + } catch (IOException e) { + throw new RuntimeException("Unable to read JSON report", e); + } + } + fail("Unable to locate json report"); + return null; + } + + public static int countIssuesInJsonReport(BuildResult result, boolean onlyNews) { + JSONObject obj = getJSONReport(result); + JSONArray issues = (JSONArray) obj.get("issues"); + int count = 0; + for (Object issue : issues) { + JSONObject jsonIssue = (JSONObject) issue; + if (!onlyNews || (Boolean) jsonIssue.get("isNew")) { + count++; + } + } + return count; + } + + public static void assertIssuesInJsonReport(BuildResult result, int newIssues, int resolvedIssues, int existingIssues) { + JSONObject obj = getJSONReport(result); + JSONArray issues = (JSONArray) obj.get("issues"); + int countNew = 0; + int countResolved = 0; + int countExisting = 0; + + for (Object issue : issues) { + JSONObject jsonIssue = (JSONObject) issue; + + if ((Boolean) jsonIssue.get("isNew")) { + countNew++; + } else if (jsonIssue.get("resolution") != null) { + countResolved++; + } else { + countExisting++; + } + } + assertThat(countNew).isEqualTo(newIssues); + assertThat(countResolved).isEqualTo(resolvedIssues); + assertThat(countExisting).isEqualTo(existingIssues); + } + + public static SonarRunner runVerboseProjectAnalysis(Orchestrator orchestrator, String projectRelativePath, String... properties) { + return runProjectAnalysis(orchestrator, projectRelativePath, true, properties); + } + + public static SonarRunner runProjectAnalysis(Orchestrator orchestrator, String projectRelativePath, String... properties) { + return runProjectAnalysis(orchestrator, projectRelativePath, false, properties); + } + + private static SonarRunner runProjectAnalysis(Orchestrator orchestrator, String projectRelativePath, boolean enableDebugLogs, String... properties) { + SonarRunner sonarRunner = SonarRunner.create(projectDir(projectRelativePath)); + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + for (int i = 0; i < properties.length; i += 2) { + builder.put(properties[i], properties[i + 1]); + } + SonarRunner scan = sonarRunner.setDebugLogs(enableDebugLogs).setProperties(builder.build()); + orchestrator.executeBuild(scan); + return scan; + } + + public static void setServerProperty(Orchestrator orchestrator, String key, @Nullable String value) { + setServerProperty(orchestrator, null, key, value); + } + + public static void setServerProperty(Orchestrator orchestrator, @Nullable String componentKey, String key, @Nullable String value) { + if (value == null) { + newAdminWsClient(orchestrator).settings().reset(ResetRequest.builder().setKeys(key).setComponent(componentKey).build()); + } else { + newAdminWsClient(orchestrator).settings().set(SetRequest.builder().setKey(key).setValue(value).setComponent(componentKey).build()); + } + } + + public static void setServerProperties(Orchestrator orchestrator, @Nullable String componentKey, String... properties) { + for (int i = 0; i < properties.length; i += 2) { + setServerProperty(orchestrator, componentKey, properties[i], properties[i + 1]); + } + } + + public static void resetSettings(Orchestrator orchestrator, @Nullable String componentKey, String... keys) { + if (keys.length > 0) { + newAdminWsClient(orchestrator).settings().reset(ResetRequest.builder().setKeys(keys).setComponent(componentKey).build()); + } + } + + public static void resetEmailSettings(Orchestrator orchestrator) { + resetSettings(orchestrator, null, "email.smtp_host.secured", "email.smtp_port.secured", "email.smtp_secure_connection.secured", "email.smtp_username.secured", + "email.smtp_password.secured", "email.from", "email.prefix"); + } + + public static void resetPeriod(Orchestrator orchestrator) { + resetSettings(orchestrator, null, "sonar.leak.period"); + } + + @CheckForNull + public static Measure getMeasure(Orchestrator orchestrator, String componentKey, String metricKey) { + return getMeasuresByMetricKey(orchestrator, componentKey, metricKey).get(metricKey); + } + + @CheckForNull + public static Double getMeasureAsDouble(Orchestrator orchestrator, String componentKey, String metricKey) { + Measure measure = getMeasure(orchestrator, componentKey, metricKey); + return (measure == null) ? null : Double.parseDouble(measure.getValue()); + } + + public static Map<String, Measure> getMeasuresByMetricKey(Orchestrator orchestrator, String componentKey, String... metricKeys) { + return getStreamMeasures(orchestrator, componentKey, metricKeys) + .filter(Measure::hasValue) + .collect(Collectors.toMap(Measure::getMetric, Function.identity())); + } + + public static Map<String, Double> getMeasuresAsDoubleByMetricKey(Orchestrator orchestrator, String componentKey, String... metricKeys) { + return getStreamMeasures(orchestrator, componentKey, metricKeys) + .filter(Measure::hasValue) + .collect(Collectors.toMap(Measure::getMetric, measure -> parseDouble(measure.getValue()))); + } + + private static Stream<Measure> getStreamMeasures(Orchestrator orchestrator, String componentKey, String... metricKeys) { + return newWsClient(orchestrator).measures().component(new ComponentWsRequest() + .setComponentKey(componentKey) + .setMetricKeys(asList(metricKeys))) + .getComponent().getMeasuresList() + .stream(); + } + + @CheckForNull + public static Measure getMeasureWithVariation(Orchestrator orchestrator, String componentKey, String metricKey) { + WsMeasures.ComponentWsResponse response = newWsClient(orchestrator).measures().component(new ComponentWsRequest() + .setComponentKey(componentKey) + .setMetricKeys(singletonList(metricKey)) + .setAdditionalFields(singletonList("periods"))); + List<Measure> measures = response.getComponent().getMeasuresList(); + return measures.size() == 1 ? measures.get(0) : null; + } + + @CheckForNull + public static Map<String, Measure> getMeasuresWithVariationsByMetricKey(Orchestrator orchestrator, String componentKey, String... metricKeys) { + return newWsClient(orchestrator).measures().component(new ComponentWsRequest() + .setComponentKey(componentKey) + .setMetricKeys(asList(metricKeys)) + .setAdditionalFields(singletonList("periods"))).getComponent().getMeasuresList() + .stream() + .collect(Collectors.toMap(Measure::getMetric, Function.identity())); + } + + /** + * Return leak period value + */ + @CheckForNull + public static Double getLeakPeriodValue(Orchestrator orchestrator, String componentKey, String metricKey) { + List<WsMeasures.PeriodValue> periodsValueList = getMeasureWithVariation(orchestrator, componentKey, metricKey).getPeriods().getPeriodsValueList(); + return periodsValueList.size() > 0 ? Double.parseDouble(periodsValueList.get(0).getValue()) : null; + } + + @CheckForNull + public static Component getComponent(Orchestrator orchestrator, String componentKey) { + try { + return newWsClient(orchestrator).components().show(new ShowWsRequest().setKey((componentKey))).getComponent(); + } catch (org.sonarqube.ws.client.HttpException e) { + if (e.code() == 404) { + return null; + } + throw new IllegalStateException(e); + } + } + + @CheckForNull + public static ComponentNavigation getComponentNavigation(Orchestrator orchestrator, String componentKey) { + // Waiting for SONAR-7745 to have version in api/components/show, we use internal api/navigation/component WS to get the component + // version + String content = newWsClient(orchestrator).wsConnector().call(new GetRequest("api/navigation/component").setParam("componentKey", componentKey)).failIfNotSuccessful() + .content(); + return ComponentNavigation.parse(content); + } + + public static void restoreProfile(Orchestrator orchestrator, URL resource) { + restoreProfile(orchestrator, resource, null); + } + + public static void restoreProfile(Orchestrator orchestrator, URL resource, String organization) { + URI uri; + try { + uri = resource.toURI(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot find quality profile xml file '" + resource + "' in classpath"); + } + newAdminWsClient(orchestrator) + .qualityProfiles() + .restoreProfile( + RestoreWsRequest.builder() + .setBackup(new File(uri)) + .setOrganization(organization) + .build()); + } + + public static String newOrganizationKey() { + return randomAlphabetic(32).toLowerCase(ENGLISH); + } + + public static String newProjectKey() { + return "key-" + randomAlphabetic(100); + } + + public static class ComponentNavigation { + private String version; + private String analysisDate; + + public String getVersion() { + return version; + } + + public Date getDate() { + return toDatetime(analysisDate); + } + + public static ComponentNavigation parse(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, ComponentNavigation.class); + } + } + + /** + * Concatenates a vararg to a String array. + * + * Useful when using {@link #runVerboseProjectAnalysis(Orchestrator, String, String...)}, eg.: + * <pre> + * ItUtils.runProjectAnalysis(orchestrator, "project_name", + * ItUtils.concat(properties, "sonar.scm.disabled", "false") + * ); + * </pre> + */ + public static String[] concat(String[] properties, String... str) { + if (properties == null || properties.length == 0) { + return str; + } + return Stream.concat(Arrays.stream(properties), Arrays.stream(str)) + .toArray(String[]::new); + } + + public static Date toDate(String sDate) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.parse(sDate); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + public static Date toDatetime(String sDate) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + return sdf.parse(sDate); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + public static String formatDate(Date d) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + return sdf.format(d); + } + + public static String extractCeTaskId(BuildResult buildResult) { + List<String> taskIds = extractCeTaskIds(buildResult); + checkState(taskIds.size() == 1, "More than one task id retrieved from logs"); + return taskIds.iterator().next(); + } + + private static List<String> extractCeTaskIds(BuildResult buildResult) { + String logs = buildResult.getLogs(); + return StreamSupport.stream(LINE_SPLITTER.split(logs).spliterator(), false) + .filter(s -> s.contains("More about the report processing at")) + .map(s -> s.substring(s.length() - 20, s.length())) + .collect(Collectors.toList()); + } + + public static Map<String, Object> jsonToMap(String json) { + Gson gson = new Gson(); + Type type = new TypeToken<Map<String, Object>>() { + }.getType(); + return gson.fromJson(json, type); + } + + /** + * @deprecated replaced by {@code orchestrator.getServer().newHttpCall()} + */ + @Deprecated + public static Response call(String url, String... headers) { + Request.Builder requestBuilder = new Request.Builder().get().url(url); + for (int i = 0; i < headers.length; i += 2) { + String headerName = headers[i]; + String headerValue = headers[i + 1]; + if (headerValue != null) { + requestBuilder.addHeader(headerName, headerValue); + } + } + try { + return new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + .newCall(requestBuilder.build()) + .execute(); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public static void expectBadRequestError(Runnable runnable) { + expectHttpError(400, runnable); + } + + public static void expectMissingError(Runnable runnable) { + expectHttpError(404, runnable); + } + /** + * Missing permissions + */ + public static void expectForbiddenError(Runnable runnable) { + expectHttpError(403, runnable); + } + + /** + * Not authenticated + */ + public static void expectUnauthorizedError(Runnable runnable) { + expectHttpError(401, runnable); + } + + public static void expectNotFoundError(Runnable runnable) { + expectHttpError(404, runnable); + } + + public static void expectHttpError(int expectedCode, Runnable runnable) { + try { + runnable.run(); + Assert.fail("Ws call should have failed"); + } catch (org.sonarqube.ws.client.HttpException e) { + assertThat(e.code()).isEqualTo(expectedCode); + } + } + + public static void expectHttpError(int expectedCode, String expectedMessage, Runnable runnable) { + try { + runnable.run(); + Assert.fail("Ws call should have failed"); + } catch (org.sonarqube.ws.client.HttpException e) { + assertThat(e.code()).isEqualTo(expectedCode); + assertThat(e.getMessage()).contains(expectedMessage); + } + } +} diff --git a/tests/src/test/java/util/LoadedProfiles.java b/tests/src/test/java/util/LoadedProfiles.java new file mode 100644 index 00000000000..71c76803901 --- /dev/null +++ b/tests/src/test/java/util/LoadedProfiles.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import static com.google.common.base.Preconditions.checkArgument; + +final class LoadedProfiles { + private final Map<String, Profile> profileStatesPerProfileKey = new HashMap<>(); + + public LoadedProfiles() { + init(); + } + + public String loadProfile(String relativePathToProfile) { + try { + URL resource = getClass().getResource(relativePathToProfile); + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(resource.openStream()); + + String profileKey = null; + String languageKey = null; + + Element documentElement = document.getDocumentElement(); + checkArgument("profile".equals(documentElement.getNodeName()), "%s is not a quality profile file. Root node is not %s", resource.toURI().toString()); + NodeList childNodes = documentElement.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node childNode = childNodes.item(i); + if ("name".equals(childNode.getNodeName())) { + profileKey = childNode.getChildNodes().item(0).getNodeValue(); + } else if ("language".equals(childNode.getNodeName())) { + languageKey = childNode.getChildNodes().item(0).getNodeValue(); + } + } + checkArgument(profileKey != null, "Quality profile file %s is missing profile key", resource.toURI().toString()); + checkArgument(languageKey != null, "Quality profile file %s is missing language key", resource.toURI().toString()); + this.profileStatesPerProfileKey.put(profileKey, new Profile(profileKey, languageKey, relativePathToProfile)); + + return profileKey; + } catch (URISyntaxException | SAXException | IOException | ParserConfigurationException e) { + throw new RuntimeException("Can not load quality profile " + relativePathToProfile, e); + } + } + + public Profile getState(String qualityProfileKey) { + Profile profile = this.profileStatesPerProfileKey.get(qualityProfileKey); + checkArgument(profile != null, "Quality Profile with key %s is unknown to %s", qualityProfileKey, ProjectAnalysisRule.class.getSimpleName()); + return profile; + } + + public void reset() { + this.profileStatesPerProfileKey.clear(); + init(); + } + + private void init() { + this.profileStatesPerProfileKey.put(Profile.XOO_EMPTY_PROFILE.getProfileKey(), Profile.XOO_EMPTY_PROFILE); + } +} diff --git a/tests/src/test/java/util/LoadedProjects.java b/tests/src/test/java/util/LoadedProjects.java new file mode 100644 index 00000000000..eb3565f5f84 --- /dev/null +++ b/tests/src/test/java/util/LoadedProjects.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +import com.google.common.base.Throwables; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +final class LoadedProjects { + + static final String SONAR_PROJECT_PROPERTIES_FILE_NAME = "sonar-project.properties"; + + private final Map<String, ProjectState> projectStatePerProjectKey = new HashMap<>(); + private final Set<String> knownProjects = new HashSet<>(); + + public void reset() { + this.projectStatePerProjectKey.clear(); + this.knownProjects.clear(); + } + + public String load(String projectRelativePath) { + checkState(!knownProjects.contains(projectRelativePath), "Project at location %s already loaded", projectRelativePath); + + File projectDir = ItUtils.projectDir(projectRelativePath); + Properties sonarProjectProperties = loadProjectProperties(projectDir); + ProjectState projectState = new ProjectState(projectDir, sonarProjectProperties); + + register(projectRelativePath, projectState); + + return projectState.getProjectKey(); + } + + public ProjectState getProjectState(String projectKey) { + ProjectState projectState = this.projectStatePerProjectKey.get(projectKey); + checkArgument(projectState != null, "Project with key %s is unknown to %s", projectKey, ProjectAnalysisRule.class.getSimpleName()); + return projectState; + } + + private void register(String projectRelativePath, ProjectState projectState) { + this.projectStatePerProjectKey.put(projectState.getProjectKey(), projectState); + this.knownProjects.add(projectRelativePath); + } + + private static Properties loadProjectProperties(File projectDir) { + File sonarPropertiesFile = new File(projectDir, SONAR_PROJECT_PROPERTIES_FILE_NAME); + checkArgument(sonarPropertiesFile.exists(), "Can not locate %s in project %s", SONAR_PROJECT_PROPERTIES_FILE_NAME, projectDir.getAbsolutePath()); + + Properties properties = new Properties(); + try { + properties.load(new FileReader(sonarPropertiesFile)); + } catch (IOException e) { + Throwables.propagate(e); + } + return properties; + } +} diff --git a/tests/src/test/java/util/Profile.java b/tests/src/test/java/util/Profile.java new file mode 100644 index 00000000000..f9f6a78c52a --- /dev/null +++ b/tests/src/test/java/util/Profile.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +import javax.annotation.concurrent.Immutable; + +@Immutable +final class Profile { + static final Profile XOO_EMPTY_PROFILE = new Profile("empty", "xoo", "n/a"); + + private final String profileKey; + private final String languageKey; + private final String relativePath; + + Profile(String profileKey, String languageKey, String relativePath) { + this.profileKey = profileKey; + this.languageKey = languageKey; + this.relativePath = relativePath; + } + + public String getProfileKey() { + return profileKey; + } + + public String getLanguageKey() { + return languageKey; + } + + public String getRelativePath() { + return relativePath; + } +} diff --git a/tests/src/test/java/util/ProjectAnalysis.java b/tests/src/test/java/util/ProjectAnalysis.java new file mode 100644 index 00000000000..e60f4c37704 --- /dev/null +++ b/tests/src/test/java/util/ProjectAnalysis.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +public interface ProjectAnalysis { + /** + * Creates a new ProjectAnalysis which will use the specified quality profile. + * + * @throws IllegalArgumentException if the quality profile with the specified key has not been loaded into the Rule + * @see {@link ProjectAnalysisRule#registerProfile(String)} + */ + ProjectAnalysis withQualityProfile(String qualityProfileKey); + + /** + * Creates a new ProjectAnalysis which will use the built-in Xoo empty profile. + */ + ProjectAnalysis withXooEmptyProfile(); + + /** + * Creates a new ProjectAnalysis which will have debug logs enabled (or not). + */ + ProjectAnalysis withDebugLogs(boolean enabled); + + /** + * Creates a new ProjectAnalysis which will have the specified properties. + */ + ProjectAnalysis withProperties(String... properties); + + /** + * Execute the current ProjectAnalysis. + * This method can be called any number of time and will run the same analysis again and again. + */ + void run(); +} diff --git a/tests/src/test/java/util/ProjectAnalysisRule.java b/tests/src/test/java/util/ProjectAnalysisRule.java new file mode 100644 index 00000000000..89debd0c066 --- /dev/null +++ b/tests/src/test/java/util/ProjectAnalysisRule.java @@ -0,0 +1,220 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarRunner; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.junit.rules.ExternalResource; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; +import static util.ItUtils.resetSettings; + +/** + * Rule wrapping around an {@link Orchestrator} instance which handle: + * <ul> + * <li>automatic reset of Orchestrator data after each method when used as a {@link org.junit.Rule}, + * after each class when used as a {@link org.junit.ClassRule}</li> + * <li>automatic reset of server properties after each method when used as a {@link org.junit.Rule}, + * after each class when used as a {@link org.junit.ClassRule}</li> + * <li>associating project with a specific Quality Profile before running an analysis</li> + * <li>provisioning a project before its first analysis so that a Quality Profile can be associated to it</li> + * <li>"restoring" a Quality Profile before an analysis with a specific Quality Profile</li> + * </ul> + * + * This Rule has preparatory methods ({@link #registerProfile(String)} and {@link #registerProject(String)}) which + * will allow consequent calls to the rule methods to be based solely on Quality Profile and Project keys. In addition, + * these methods returns the Quality Profile and Project key to avoid information duplication (the only magic string + * the IT developer has to know is the relative path of the Project or the Quality Profile). + * + * To run an analysis, use method {@link #newProjectAnalysis(String)} to create a {@link ProjectAnalysis} + * object. This object has a {@link ProjectAnalysis#run()} method which will start the analysis. + * {@link ProjectAnalysis} can safely be reused to run the same analysis multiple times. In addition, these objects are + * immutable. Any call to one of their method which would modify their state will create a new instance which can also + * be reused at will. + */ +public class ProjectAnalysisRule extends ExternalResource { + + private final Orchestrator orchestrator; + private final LoadedProfiles loadedProfiles = new LoadedProfiles(); + private final LoadedProjects loadedProjects = new LoadedProjects(); + private final Set<String> serverProperties = new HashSet<>(); + + private ProjectAnalysisRule(Orchestrator orchestrator) { + this.orchestrator = orchestrator; + } + + public static ProjectAnalysisRule from(Orchestrator orchestrator) { + return new ProjectAnalysisRule(requireNonNull(orchestrator, "Orchestrator instance can not be null")); + } + + /** + * @param relativePathToProfile eg.: "/issue/suite/IssueFilterExtensionTest/xoo-with-many-rules.xml" + * + * @return the quality profile key + */ + public String registerProfile(String relativePathToProfile) { + return this.loadedProfiles.loadProfile(relativePathToProfile); + } + + /** + * @param projectRelativePath path relative to it/projects, eg. "shared/xoo-multi-modules-sample" + * + * @return the project key + */ + public String registerProject(String projectRelativePath) { + return this.loadedProjects.load(projectRelativePath); + } + + public ProjectAnalysis newProjectAnalysis(String projectKey) { + ProjectState projectState = this.loadedProjects.getProjectState(projectKey); + + return new ProjectAnalysisImpl(projectState, null, false); + } + + @Override + protected void before() throws Throwable { + orchestrator.resetData(); + } + + @Override + protected void after() { + resetServerProperties(); + resetRuleState(); + } + + private void resetServerProperties() { + resetSettings(orchestrator, null, serverProperties.toArray(new String[] {})); + } + + public void setServerPropertyImpl(String key, @Nullable String value) { + ItUtils.setServerProperty(orchestrator, key, value); + } + + public ProjectAnalysisRule setServerProperty(String key, String value) { + setServerPropertyImpl(key, value); + this.serverProperties.add(key); + return this; + } + + @Immutable + private final class ProjectAnalysisImpl implements ProjectAnalysis { + private final ProjectState projectState; + @CheckForNull + private final Profile qualityProfile; + private final boolean debugLogs; + @CheckForNull + private final String[] properties; + + private ProjectAnalysisImpl(ProjectState projectState, @Nullable Profile qualityProfile, boolean debugLogs, String... properties) { + this.projectState = projectState; + this.qualityProfile = qualityProfile; + this.debugLogs = debugLogs; + this.properties = properties; + } + + @Override + public ProjectAnalysis withQualityProfile(String qualityProfileKey) { + checkNotNull(qualityProfileKey, "Specified Quality Profile Key can not be null"); + if (this.qualityProfile != null && this.qualityProfile.getProfileKey().equals(qualityProfileKey)) { + return this; + } + + return new ProjectAnalysisImpl(this.projectState, loadedProfiles.getState(qualityProfileKey), this.debugLogs, this.properties); + } + + @Override + public ProjectAnalysis withXooEmptyProfile() { + if (this.qualityProfile == Profile.XOO_EMPTY_PROFILE) { + return this; + } + return new ProjectAnalysisImpl(this.projectState, Profile.XOO_EMPTY_PROFILE, this.debugLogs, this.properties); + } + + @Override + public ProjectAnalysis withDebugLogs(boolean enabled) { + if (this.debugLogs == enabled) { + return this; + } + return new ProjectAnalysisImpl(this.projectState, this.qualityProfile, enabled, this.properties); + } + + @Override + public ProjectAnalysis withProperties(String... properties) { + checkArgument( + properties == null || properties.length % 2 == 0, + "there must be an even number of String parameters (got %s): key/value pairs must be complete", properties == null ? 0 : properties.length); + return new ProjectAnalysisImpl(this.projectState, this.qualityProfile, this.debugLogs, properties); + } + + @Override + public void run() { + provisionIfNecessary(); + setQualityProfileIfNecessary(); + runAnalysis(); + } + + private void setQualityProfileIfNecessary() { + if (this.qualityProfile != null) { + if (this.qualityProfile != Profile.XOO_EMPTY_PROFILE) { + ItUtils.restoreProfile(orchestrator, getClass().getResource(this.qualityProfile.getRelativePath())); + } + orchestrator.getServer().associateProjectToQualityProfile( + this.projectState.getProjectKey(), + this.qualityProfile.getLanguageKey(), + this.qualityProfile.getProfileKey()); + } + } + + private void provisionIfNecessary() { + if (this.qualityProfile != null && !projectState.isProvisioned()) { + String projectKey = projectState.getProjectKey(); + orchestrator.getServer().provisionProject(projectKey, MoreObjects.firstNonNull(projectState.getProjectName(), projectKey)); + projectState.setProvisioned(true); + } + } + + private SonarRunner runAnalysis() { + SonarRunner sonarRunner = SonarRunner.create(projectState.getProjectDir()); + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); + for (int i = 0; i < this.properties.length; i += 2) { + builder.put(this.properties[i], this.properties[i + 1]); + } + SonarRunner scan = sonarRunner.setDebugLogs(this.debugLogs).setProperties(builder.build()); + orchestrator.executeBuild(scan); + return scan; + } + } + + private void resetRuleState() { + this.loadedProjects.reset(); + this.loadedProfiles.reset(); + this.serverProperties.clear(); + } + +} diff --git a/tests/src/test/java/util/ProjectState.java b/tests/src/test/java/util/ProjectState.java new file mode 100644 index 00000000000..cfa310326b4 --- /dev/null +++ b/tests/src/test/java/util/ProjectState.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util; + +import java.io.File; +import java.util.Properties; + +import static com.google.common.base.Preconditions.checkState; +import static util.LoadedProjects.SONAR_PROJECT_PROPERTIES_FILE_NAME; + +final class ProjectState { + private static final String SONAR_PROJECT_KEY_PROPERTY_NAME = "sonar.projectKey"; + private static final String SONAR_PROJECT_NAME_PROPERTY_NAME = "sonar.projectName"; + + private final File projectDir; + private final Properties properties; + private boolean provisioned = false; + + ProjectState(File projectDir, Properties properties) { + this.projectDir = projectDir; + this.properties = properties; + } + + public File getProjectDir() { + return projectDir; + } + + public Properties getProperties() { + return properties; + } + + public String getProjectKey() { + return getProperty(SONAR_PROJECT_KEY_PROPERTY_NAME); + } + + public String getProjectName() { + return getProperty(SONAR_PROJECT_NAME_PROPERTY_NAME); + } + + private String getProperty(String propertyName) { + String value = this.properties.getProperty(propertyName); + checkState(value != null, "Property %s is missing in %s file in project directory %s", + propertyName, SONAR_PROJECT_PROPERTIES_FILE_NAME, projectDir.getAbsolutePath()); + return value; + } + + public boolean isProvisioned() { + return provisioned; + } + + public void setProvisioned(boolean provisioned) { + this.provisioned = provisioned; + } +} diff --git a/tests/src/test/java/util/issue/IssueRule.java b/tests/src/test/java/util/issue/IssueRule.java new file mode 100644 index 00000000000..7857d3cabdc --- /dev/null +++ b/tests/src/test/java/util/issue/IssueRule.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.issue; + +import com.sonar.orchestrator.Orchestrator; +import java.util.List; +import org.junit.rules.ExternalResource; +import org.sonarqube.ws.Issues.Issue; +import org.sonarqube.ws.Issues.SearchWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.issue.SearchWsRequest; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; + +public class IssueRule extends ExternalResource { + + private final Orchestrator orchestrator; + + private WsClient adminWsClient; + + private IssueRule(Orchestrator orchestrator) { + this.orchestrator = orchestrator; + } + + public static IssueRule from(Orchestrator orchestrator) { + return new IssueRule(requireNonNull(orchestrator, "Orchestrator instance can not be null")); + } + + public SearchWsResponse search(SearchWsRequest request) { + return adminWsClient().issues().search(request); + } + + public Issue getRandomIssue() { + List<Issue> issues = search(new SearchWsRequest()).getIssuesList(); + assertThat(issues).isNotEmpty(); + return issues.get(0); + } + + public Issue getByKey(String issueKey) { + List<Issue> issues = search(new SearchWsRequest().setIssues(singletonList(issueKey)).setAdditionalFields(singletonList("_all"))).getIssuesList(); + assertThat(issues).hasSize(1); + return issues.iterator().next(); + } + + public List<Issue> getByKeys(String... issueKeys) { + List<Issue> issues = search(new SearchWsRequest().setIssues(asList(issueKeys)).setAdditionalFields(singletonList("_all"))).getIssuesList(); + assertThat(issues).hasSize(issueKeys.length); + return issues; + } + + private WsClient adminWsClient() { + if (adminWsClient == null) { + adminWsClient = newAdminWsClient(orchestrator); + } + return adminWsClient; + } + +} diff --git a/tests/src/test/java/util/selenium/ByCssSelectorOrByNameOrById.java b/tests/src/test/java/util/selenium/ByCssSelectorOrByNameOrById.java new file mode 100644 index 00000000000..364ba779d6e --- /dev/null +++ b/tests/src/test/java/util/selenium/ByCssSelectorOrByNameOrById.java @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import org.openqa.selenium.By; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.FindsByCssSelector; +import org.openqa.selenium.internal.FindsById; +import org.openqa.selenium.internal.FindsByName; + +public class ByCssSelectorOrByNameOrById extends By implements Serializable { + private static final long serialVersionUID = -3910258723099459239L; + + private final String selector; + + public ByCssSelectorOrByNameOrById(String selector) { + this.selector = selector; + } + + @Override + public WebElement findElement(SearchContext context) { + WebElement element; + + if (validCssSelector(selector)) { + element = ((FindsByCssSelector) context).findElementByCssSelector(quoteCss(selector)); + if (element != null) { + return element; + } + } + + element = ((FindsByName) context).findElementByName(selector); + if (element != null) { + return element; + } + + element = ((FindsById) context).findElementById(selector); + if (element != null) { + return element; + } + + return null; + } + + @Override + public List<WebElement> findElements(SearchContext context) { + List<WebElement> elements; + + if (validCssSelector(selector)) { + elements = ((FindsByCssSelector) context).findElementsByCssSelector(quoteCss(selector)); + if ((elements != null) && (!elements.isEmpty())) { + return elements; + } + } + + elements = ((FindsByName) context).findElementsByName(selector); + if ((elements != null) && (!elements.isEmpty())) { + return elements; + } + + elements = ((FindsById) context).findElementsById(selector); + if ((elements != null) && (!elements.isEmpty())) { + return elements; + } + + return Collections.emptyList(); + } + + protected boolean validCssSelector(String selector) { + return !selector.endsWith("[]"); + } + + protected String quoteCss(String selector) { + if (selector.startsWith(".")) { + return selector; + } + if (selector.startsWith("#")) { + return selector.replaceAll("(\\w)[.]", "$1\\\\."); + } + return selector; + } + + @Override + public String toString() { + return selector; + } +} diff --git a/tests/src/test/java/util/selenium/Consumer.java b/tests/src/test/java/util/selenium/Consumer.java new file mode 100644 index 00000000000..69009893a97 --- /dev/null +++ b/tests/src/test/java/util/selenium/Consumer.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +public interface Consumer<T> { + void accept(T t); +} diff --git a/tests/src/test/java/util/selenium/ElementFilter.java b/tests/src/test/java/util/selenium/ElementFilter.java new file mode 100644 index 00000000000..86b42cfa826 --- /dev/null +++ b/tests/src/test/java/util/selenium/ElementFilter.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.google.common.base.Function; +import java.util.Collection; +import org.openqa.selenium.WebElement; + +class ElementFilter { + private static final ElementFilter ANY = new ElementFilter("", new Function<Collection<WebElement>, Collection<WebElement>>() { + @Override + public Collection<WebElement> apply(Collection<WebElement> input) { + return input; + } + }); + + private final String description; + private final Function<Collection<WebElement>, Collection<WebElement>> filter; + + ElementFilter(String description, Function<Collection<WebElement>, Collection<WebElement>> filter) { + this.description = description; + this.filter = filter; + } + + public String getDescription() { + return description; + } + + public Function<Collection<WebElement>, Collection<WebElement>> getFilter() { + return filter; + } + + public static ElementFilter any() { + return ANY; + } + + public ElementFilter and(final ElementFilter second) { + if (ANY == this) { + return second; + } + if (ANY == second) { + return this; + } + return new ElementFilter(description + ',' + second.description, new Function<Collection<WebElement>, Collection<WebElement>>() { + @Override + public Collection<WebElement> apply(Collection<WebElement> stream) { + return second.filter.apply(filter.apply(stream)); + } + }); + } +} diff --git a/tests/src/test/java/util/selenium/Failure.java b/tests/src/test/java/util/selenium/Failure.java new file mode 100644 index 00000000000..8ba36543820 --- /dev/null +++ b/tests/src/test/java/util/selenium/Failure.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import java.util.ArrayList; +import java.util.List; + +class Failure { + private static final String PREFIX = Failure.class.getPackage().getName() + "."; + + private Failure() { + // Static class + } + + public static AssertionError create(String message) { + AssertionError error = new AssertionError(message); + removeSimpleleniumFromStackTrace(error); + return error; + } + + private static void removeSimpleleniumFromStackTrace(Throwable throwable) { + List<StackTraceElement> filtered = new ArrayList<>(); + + for (StackTraceElement element : throwable.getStackTrace()) { + if (!element.getClassName().contains(PREFIX)) { + filtered.add(element); + } + } + + throwable.setStackTrace(filtered.toArray(new StackTraceElement[filtered.size()])); + } +} diff --git a/tests/src/test/java/util/selenium/LazyDomElement.java b/tests/src/test/java/util/selenium/LazyDomElement.java new file mode 100644 index 00000000000..27c5199f820 --- /dev/null +++ b/tests/src/test/java/util/selenium/LazyDomElement.java @@ -0,0 +1,174 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.FluentIterable; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import javax.annotation.Nullable; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.Select; + +class LazyDomElement { + private final WebDriver driver; + private final By selector; + private final ElementFilter filter; + private final Retry retry; + + LazyDomElement(WebDriver driver, By selector) { + this(driver, selector, Retry._30_SECONDS); + } + + LazyDomElement(WebDriver driver, By selector, Retry retry) { + this(driver, selector, ElementFilter.any(), retry); + } + + private LazyDomElement(WebDriver driver, By selector, ElementFilter filter, Retry retry) { + this.driver = driver; + this.selector = selector; + this.filter = filter; + this.retry = retry; + } + + public LazyDomElement withText(final String text) { + String fullDescription = " with text [" + text + "]"; + + return with(new ElementFilter(fullDescription, new Function<Collection<WebElement>, Collection<WebElement>>() { + @Override + public Collection<WebElement> apply(Collection<WebElement> stream) { + return FluentIterable.from(stream).filter(new Predicate<WebElement>() { + @Override + public boolean apply(@Nullable WebElement element) { +// return Objects.equals(element.getText(), text); + return element.getText().contains(text); + } + }).toList(); + } + })); + } + + public LazyShould should() { + return new LazyShould(this, Retry._30_SECONDS, true); + } + + public void fill(final CharSequence text) { + execute("fill(" + text + ")", new Consumer<WebElement>() { + @Override + public void accept(WebElement element) { + element.clear(); + element.sendKeys(text); + } + }); + } + + public void pressEnter() { + execute("pressEnter", new Consumer<WebElement>() { + @Override + public void accept(WebElement element) { + element.sendKeys(Keys.ENTER); + } + }); + } + + public void select(final String text) { + executeSelect("select(" + text + ")", new Consumer<Select>() { + @Override + public void accept(Select select) { + select.selectByVisibleText(text); + } + }); + } + + public void executeSelect(String description, final Consumer<Select> selectOnElement) { + execute(description, new Consumer<WebElement>() { + @Override + public void accept(WebElement element) { + selectOnElement.accept(new Select(element)); + } + }); + } + + public void click() { + execute("click", new Consumer<WebElement>() { + @Override + public void accept(WebElement element) { + new Actions(driver).moveToElement(element); + element.click(); + } + }); + } + + public void check() { + execute("check", new Consumer<WebElement>() { + @Override + public void accept(WebElement element) { + if (!element.isSelected()) { + element.click(); + } + } + }); + } + + public void execute(Consumer<WebElement> action) { + execute("execute(" + action + ")", action); + } + + private LazyDomElement with(ElementFilter filter) { + return new LazyDomElement(driver, selector, this.filter.and(filter), retry); + } + + private void execute(String message, Consumer<WebElement> action) { + System.out.println(" - " + Text.toString(selector) + filter.getDescription() + "." + message); + + Supplier<Optional<WebElement>> findOne = new Supplier<Optional<WebElement>>() { + @Override + public Optional<WebElement> get() { + List<WebElement> elements = stream(); + if (elements.isEmpty()) { + return Optional.empty(); + } + return Optional.of(elements.get(0)); + } + }; + + try { + retry.execute(findOne, action); + } catch (NoSuchElementException e) { + throw new AssertionError("Element not found: " + Text.toString(selector)); + } + } + + List<WebElement> stream() { + return FluentIterable.from(filter.getFilter().apply(driver.findElements(selector))).toList(); + } + + @Override + public String toString() { + return Text.toString(selector) + filter.getDescription(); + } +} diff --git a/tests/src/test/java/util/selenium/LazyShould.java b/tests/src/test/java/util/selenium/LazyShould.java new file mode 100644 index 00000000000..c880a7d944b --- /dev/null +++ b/tests/src/test/java/util/selenium/LazyShould.java @@ -0,0 +1,190 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.collect.FluentIterable; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import org.openqa.selenium.WebElement; + +class LazyShould { + private final LazyDomElement element; + private final Retry retry; + private final boolean ok; + + LazyShould(LazyDomElement element, Retry retry, boolean ok) { + this.element = element; + this.retry = retry; + this.ok = ok; + } + + public LazyShould beDisplayed() { + return verify( + isOrNot("displayed"), + new Predicate<List<WebElement>>() { + @Override + public boolean apply(List<WebElement> elements) { + return !elements.isEmpty() && FluentIterable.from(elements).allMatch(new Predicate<WebElement>() { + @Override + public boolean apply(WebElement element) { + return element.isDisplayed(); + } + }); + } + }, + new Function<List<WebElement>, String>() { + @Override + public String apply(List<WebElement> elements) { + return "It is " + statuses(elements, new Function<WebElement, String>() { + @Override + public String apply(WebElement element) { + return displayedStatus(element); + } + }); + } + }); + } + + public LazyShould match(final Pattern regexp) { + return verify( + doesOrNot("match") + " (" + regexp.pattern() + ")", + new Predicate<List<WebElement>>() { + @Override + public boolean apply(List<WebElement> elements) { + return !elements.isEmpty() && FluentIterable.from(elements).anyMatch(new Predicate<WebElement>() { + @Override + public boolean apply(WebElement element) { + return regexp.matcher(WebElementHelper.text(element)).matches(); + } + }); + } + }, + new Function<List<WebElement>, String>() { + @Override + public String apply(List<WebElement> elements) { + return "It contains " + statuses(elements, new Function<WebElement, String>() { + @Nullable + @Override + public String apply(@Nullable WebElement element) { + return WebElementHelper.text(element); + } + }); + } + }); + } + + public LazyShould contain(final String text) { + return verify( + doesOrNot("contain") + "(" + text + ")", + new Predicate<List<WebElement>>() { + @Override + public boolean apply(List<WebElement> elements) { + return FluentIterable.from(elements).anyMatch(new Predicate<WebElement>() { + @Override + public boolean apply(@Nullable WebElement element) { + if (text.startsWith("exact:")) { + return WebElementHelper.text(element).equals(text.substring(6)); + } + return WebElementHelper.text(element).contains(text); + } + }); + } + }, + new Function<List<WebElement>, String>() { + @Override + public String apply(List<WebElement> elements) { + return "It contains " + statuses(elements, new Function<WebElement, String>() { + @Override + public String apply(WebElement element) { + return WebElementHelper.text(element); + } + }); + } + }); + } + + public LazyShould exist() { + return verify( + doesOrNot("exist"), + new Predicate<List<WebElement>>() { + @Override + public boolean apply(List<WebElement> elements) { + return !elements.isEmpty(); + } + }, + new Function<List<WebElement>, String>() { + @Override + public String apply(List<WebElement> elements) { + return "It contains " + Text.plural(elements.size(), "element"); + } + }); + } + + private static String displayedStatus(WebElement element) { + return element.isDisplayed() ? "displayed" : "not displayed"; + } + + private LazyShould verify(String message, Predicate<List<WebElement>> predicate, Function<List<WebElement>, String> toErrorMessage) { + String verification = "verify that " + element + " " + message; + System.out.println(" -> " + verification); + + try { + if (!retry.verify(new Supplier<List<WebElement>>() { + @Override + public List<WebElement> get() { + return LazyShould.this.findElements(); + } + }, ok ? predicate : Predicates.not(predicate))) { + throw Failure.create("Failed to " + verification + ". " + toErrorMessage.apply(findElements())); + } + } catch (NoSuchElementException e) { + throw Failure.create("Element not found. Failed to " + verification); + } + + return ok ? this : not(); + } + + private List<WebElement> findElements() { + return element.stream(); + } + + private static String statuses(List<WebElement> elements, Function<WebElement, String> toStatus) { + return "(" + FluentIterable.from(elements).transform(toStatus).join(Joiner.on(";")) + ")"; + } + + public LazyShould not() { + return new LazyShould(element, retry, !ok); + } + + private String doesOrNot(String verb) { + return Text.doesOrNot(!ok, verb); + } + + private String isOrNot(String state) { + return Text.isOrNot(!ok, state); + } +} diff --git a/tests/src/test/java/util/selenium/Optional.java b/tests/src/test/java/util/selenium/Optional.java new file mode 100644 index 00000000000..918d488a85f --- /dev/null +++ b/tests/src/test/java/util/selenium/Optional.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import java.util.NoSuchElementException; + +public final class Optional<T> { + private static final Optional<?> EMPTY = new Optional<>(); + + private final T value; + + private Optional() { + this.value = null; + } + + public static <T> Optional<T> empty() { + @SuppressWarnings("unchecked") + Optional<T> t = (Optional<T>) EMPTY; + return t; + } + + private Optional(T value) { + this.value = value; + } + + public static <T> Optional<T> of(T value) { + return new Optional<>(value); + } + + public T get() { + if (value == null) { + throw new NoSuchElementException("No value present"); + } + return value; + } + + public boolean isPresent() { + return value != null; + } +} diff --git a/tests/src/test/java/util/selenium/Retry.java b/tests/src/test/java/util/selenium/Retry.java new file mode 100644 index 00000000000..7cc8a20897c --- /dev/null +++ b/tests/src/test/java/util/selenium/Retry.java @@ -0,0 +1,152 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import java.util.NoSuchElementException; +import java.util.concurrent.TimeUnit; +import org.openqa.selenium.InvalidElementStateException; +import org.openqa.selenium.NotFoundException; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.WebDriverException; + +import static java.util.concurrent.TimeUnit.SECONDS; + +class Retry { + public static final Retry _30_SECONDS = new Retry(30, SECONDS); + + private final long timeoutInMs; + + Retry(long duration, TimeUnit timeUnit) { + this.timeoutInMs = timeUnit.toMillis(duration); + } + + <T> void execute(Supplier<Optional<T>> target, Consumer<T> action) { + WebDriverException lastError = null; + + boolean retried = false; + + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < timeoutInMs) { + try { + Optional<T> targetElement = target.get(); + if (targetElement.isPresent()) { + action.accept(targetElement.get()); + if (retried) { + System.out.println(); + } + return; + } + } catch (StaleElementReferenceException e) { + // ignore + } catch (WebDriverException e) { + lastError = e; + } + + retried = true; + System.out.print("."); + } + + if (retried) { + System.out.println(); + } + + if (lastError != null) { + throw lastError; + } + throw new NoSuchElementException("Not found"); + } + + <T> void execute(Runnable action) { + WebDriverException lastError = null; + + boolean retried = false; + + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < timeoutInMs) { + try { + action.run(); + if (retried) { + System.out.println(); + } + return; + } catch (StaleElementReferenceException e) { + // ignore + } catch (WebDriverException e) { + lastError = e; + } + + retried = true; + System.out.print("."); + } + + if (retried) { + System.out.println(); + } + + if (lastError != null) { + throw lastError; + } + throw new NoSuchElementException("Not found"); + } + + <T> boolean verify(Supplier<T> targetSupplier, Predicate<T> predicate) throws NoSuchElementException { + Error error = Error.KO; + + boolean retried = false; + + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < timeoutInMs) { + try { + if (predicate.apply(targetSupplier.get())) { + if (retried) { + System.out.println(); + } + return true; + } + + error = Error.KO; + } catch (InvalidElementStateException e) { + error = Error.KO; + } catch (NotFoundException e) { + error = Error.NOT_FOUND; + } catch (StaleElementReferenceException e) { + // ignore + } + + retried = true; + System.out.print("."); + } + + if (retried) { + System.out.println(); + } + + if (error == Error.NOT_FOUND) { + throw new NoSuchElementException("Not found"); + } + return false; + } + + enum Error { + NOT_FOUND, KO + } +} diff --git a/tests/src/test/java/util/selenium/Selenese.java b/tests/src/test/java/util/selenium/Selenese.java new file mode 100644 index 00000000000..187e33d9024 --- /dev/null +++ b/tests/src/test/java/util/selenium/Selenese.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.sonar.orchestrator.Orchestrator; +import java.io.File; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.sonarqube.tests.Tester; + +/** + * Selenium HTML tests, generally written with Selenium IDE + * @deprecated replaced by {@link Tester} + */ +@Deprecated +public final class Selenese { + + private File[] htmlTests; + + public Selenese(Builder builder) { + this.htmlTests = builder.htmlTests; + } + + public File[] getHtmlTests() { + return htmlTests; + } + + /** + * @deprecated replaced by {@link Tester#runHtmlTests(String...)} + */ + @Deprecated + public static void runSelenese(Orchestrator orchestrator, String... htmlFiles) { + Selenese selenese = new Builder() + .setHtmlTests(htmlFiles) + .build(); + new SeleneseRunner().runOn(selenese, orchestrator); + } + + private static final class Builder { + private File[] htmlTests; + + private Builder() { + } + + public Builder setHtmlTests(File... htmlTests) { + this.htmlTests = htmlTests; + return this; + } + + public Builder setHtmlTests(String... htmlTestPaths) { + this.htmlTests = new File[htmlTestPaths.length]; + for (int index = 0; index < htmlTestPaths.length; index++) { + htmlTests[index] = FileUtils.toFile(getClass().getResource(htmlTestPaths[index])); + } + return this; + } + + public Selenese build() { + if (htmlTests == null || htmlTests.length == 0) { + throw new IllegalArgumentException("HTML suite or tests are missing"); + } + for (File htmlTest : htmlTests) { + checkPresence(htmlTest); + } + return new Selenese(this); + } + + private static void checkPresence(@Nullable File file) { + if (file == null || !file.isFile() || !file.exists()) { + throw new IllegalArgumentException("HTML file does not exist: " + file); + } + } + } +} diff --git a/tests/src/test/java/util/selenium/SeleneseRunner.java b/tests/src/test/java/util/selenium/SeleneseRunner.java new file mode 100644 index 00000000000..96cc4e609a2 --- /dev/null +++ b/tests/src/test/java/util/selenium/SeleneseRunner.java @@ -0,0 +1,440 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.sonar.orchestrator.Orchestrator; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; +import org.assertj.core.util.Strings; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.NotFoundException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.logging.LogEntries; +import org.openqa.selenium.logging.LogEntry; +import org.openqa.selenium.logging.LogType; +import org.sonarqube.pageobjects.SelenideConfig; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; +import static java.util.regex.Pattern.DOTALL; +import static org.assertj.core.api.Assertions.assertThat; +import static util.selenium.Retry._30_SECONDS; + +class SeleneseRunner { + + private Map<String, String> variables; + private String baseUrl; + private WebDriver driver; + + void runOn(Selenese selenese, Orchestrator orchestrator) { + this.variables = new HashMap<>(); + this.baseUrl = orchestrator.getServer().getUrl(); + this.driver = SelenideConfig.configure(orchestrator); + + driver.manage().deleteAllCookies(); + + for (File file : selenese.getHtmlTests()) { + System.out.println(); + System.out.println("============ " + file.getName() + " ============"); + Document doc = parse(file); + for (Element table : doc.getElementsByTag("table")) { + for (Element tbody : table.getElementsByTag("tbody")) { + for (Element tr : tbody.getElementsByTag("tr")) { + String action = tr.child(0).text(); + String param1 = tr.child(1).text(); + String param2 = tr.child(2).text(); + + try { + action(action, param1, param2); + } catch (AssertionError e) { + analyzeLog(driver); + throw e; + } + } + } + } + } + } + + private static void analyzeLog(WebDriver driver) { + LogEntries logEntries = driver.manage().logs().get(LogType.BROWSER); + for (LogEntry entry : logEntries) { + System.out.println(new Date(entry.getTimestamp()) + " " + entry.getLevel() + " " + entry.getMessage()); + } + } + + private static Document parse(File file) { + try { + return Jsoup.parse(file, UTF_8.name()); + } catch (IOException e) { + throw new RuntimeException("Unable to parse file: " + file, e); + } + } + + public SeleneseRunner action(String action, String param1, String param2) { + switch (action) { + case "open": + open(param1); + return this; + case "type": + type(param1, param2); + return this; + case "keyPressAndWait": + keyPressAndWait(param1, param2); + return this; + case "select": + select(param1, param2); + return this; + case "clickAndWait": + case "click": + click(param1); + return this; + case "check": + check(param1); + return this; + case "selectFrame": + selectFrame(param1); + return this; + case "assertElementPresent": + assertElementPresent(param1); + return this; + case "assertElementNotPresent": + assertElementNotPresent(param1); + return this; + case "storeText": + storeText(param1, param2); + return this; + case "storeEval": + storeEval(param1, param2); + return this; + case "store": + store(param1, param2); + return this; + case "assertText": + case "waitForText": + assertText(param1, param2); + return this; + case "assertNotText": + case "waitForNotText": + assertNotText(param1, param2); + return this; + case "assertTextPresent": + assertTextPresent(param1); + return this; + case "assertTextNotPresent": + assertTextNotPresent(param1); + return this; + case "assertLocation": + assertLocation(param1); + return this; + case "verifyHtmlSource": + verifyHtmlSource(param1); + return this; + case "waitForElementPresent": + waitForElementPresent(param1, param2); + return this; + case "waitForElementNotPresent": + waitForElementNotPresent(param1, param2); + return this; + case "waitForVisible": + waitForVisible(param1); + return this; + case "waitForXpathCount": + waitForXpathCount(param1, Integer.parseInt(param2)); + return this; + case "assertValue": + case "waitForValue": + case "verifyValue": + assertInputValue(param1, param2); + return this; + case "assertConfirmation": + confirm(param1); + return this; + case "setTimeout": + case "pause": + // Ignore + return this; + } + + throw new IllegalArgumentException("Unsupported action: " + action); + } + + private void open(String url) { + if (url.startsWith("/sonar/")) { + goTo(url.substring(6)); + } else { + goTo(url); + } + } + + private void goTo(String url) { + requireNonNull(url, "The url cannot be null"); + + url = replacePlaceholders(url); + + URI uri = URI.create(url.replace(" ", "%20").replace("|", "%7C")); + if (!uri.isAbsolute()) { + url = baseUrl + url; + } + + System.out.println("goTo " + url); + driver.get(url); + System.out.println(" - current url " + driver.getCurrentUrl()); + } + + private LazyDomElement find(String selector) { + selector = replacePlaceholders(selector); + + if (selector.startsWith("link=") || selector.startsWith("Link=")) { + return find("a").withText(selector.substring(5)); + } + + By by; + if (selector.startsWith("//")) { + by = new By.ByXPath(selector); + } else if (selector.startsWith("xpath=")) { + by = new By.ByXPath(selector.substring(6)); + } else if (selector.startsWith("id=")) { + by = new By.ById(selector.substring(3)); + } else if (selector.startsWith("name=")) { + by = new By.ByName(selector.substring(5)); + } else if (selector.startsWith("css=")) { + by = new By.ByCssSelector(selector.substring(4)); + } else if (selector.startsWith("class=")) { + by = new By.ByCssSelector("." + selector.substring(6)); + } else { + by = new ByCssSelectorOrByNameOrById(selector); + } + + return new LazyDomElement(driver, by); + } + + private void click(String selector) { + find(selector).click(); + } + + private void check(String selector) { + find(selector).check(); + } + + private void selectFrame(final String id) { + if ("relative=parent".equals(id)) { + return; + } + + System.out.println(" - selectFrame(" + id + ")"); + _30_SECONDS.execute(new Runnable() { + @Override + public void run() { + driver.switchTo().frame(id); + } + }); + } + + private void type(String selector, String text) { + find(selector).fill(replacePlaceholders(text)); + } + + private void keyPressAndWait(String selector, String key) { + if (!key.equals("\\13")) { + throw new IllegalArgumentException("Invalid key: " + key); + } + find(selector).pressEnter(); + } + + private void select(String selector, String text) { + if (text.startsWith("label=")) { + find(selector).select(text.substring(6)); + } else { + find(selector).select(text); + } + } + + private void assertElementPresent(String selector) { + find(selector).should().beDisplayed(); + } + + private void assertElementNotPresent(String selector) { + find(selector).should().not().beDisplayed(); + } + + private void storeText(String selector, String name) { + find(selector).execute(new ExtractVariable(name)); + } + + private void storeEval(final String expression, final String name) { + // Retry until it's not null and doesn't fail + _30_SECONDS.execute(new Runnable() { + @Override + public void run() { + Object result = ((JavascriptExecutor) driver).executeScript("return " + expression); + if (result == null) { + throw new NotFoundException(expression); + } + String value = result.toString(); + variables.put(name, value); + } + }); + } + + private void store(String expression, String name) { + if (expression.startsWith("javascript{") && expression.endsWith("}")) { + storeEval(expression.substring(11, expression.length() - 1), name); + } else { + throw new IllegalArgumentException("Invalid store expression: " + expression); + } + } + + private class ExtractVariable implements Consumer<WebElement> { + private final String name; + + ExtractVariable(String name) { + this.name = name; + } + + @Override + public void accept(WebElement webElement) { + variables.put(name, webElement.getText()); + } + + public String toString() { + return "read value into " + name; + } + } + + private void assertText(String selector, String pattern) { + pattern = replacePlaceholders(pattern); + + if (pattern.startsWith("exact:")) { + String expectedText = pattern.substring(6); + find(selector).withText(expectedText).should().exist(); + return; + } + + if (pattern.startsWith("regexp:")) { + find(selector).should().match(regex(pattern)); + return; + } + + find(selector).should().match(glob(pattern)); + } + + private void assertNotText(String selector, String pattern) { + pattern = replacePlaceholders(pattern); + + if (pattern.startsWith("exact:")) { + String expectedText = pattern.substring(6); + find(selector).withText(expectedText).should().not().exist(); + return; + } + + if (pattern.startsWith("regexp:")) { + find(selector).should().not().match(regex(pattern)); + return; + } + + find(selector).should().not().match(glob(pattern)); + } + + private static Pattern glob(String pattern) { + String regexp = pattern.replaceFirst("glob:", ""); + regexp = regexp.replaceAll("([\\]\\[\\\\{\\}$\\(\\)\\|\\^\\+.])", "\\\\$1"); + regexp = regexp.replaceAll("\\*", ".*"); + regexp = regexp.replaceAll("\\?", "."); + return Pattern.compile(regexp, DOTALL | Pattern.CASE_INSENSITIVE); + } + + private static Pattern regex(String pattern) { + String regexp = pattern.replaceFirst("regexp:", ".*") + ".*"; + return Pattern.compile(regexp, DOTALL | Pattern.CASE_INSENSITIVE); + } + + private void assertTextPresent(String text) { + find("body").should().contain(text); + } + + private void assertTextNotPresent(String text) { + find("body").should().not().contain(text); + } + + private void waitForElementPresent(String selector, String text) { + if (Strings.isNullOrEmpty(text)) { + find(selector).should().exist(); + } else { + find(selector).withText(text).should().exist(); + } + } + + private void waitForElementNotPresent(String selector, String text) { + if (Strings.isNullOrEmpty(text)) { + find(selector).should().not().exist(); + } else { + find(selector).withText(text).should().not().exist(); + } + } + + private void waitForVisible(String selector) { + find(selector).should().beDisplayed(); + } + + private void assertInputValue(String selector, String text) { + find(selector).should().contain(text); + } + + private void waitForXpathCount(String selector, int expectedCount) { + assertThat(find(selector).stream().size()).isEqualTo(expectedCount); + } + + private void confirm(final String message) { + System.out.println(" - confirm(" + message + ")"); + + _30_SECONDS.execute(new Runnable() { + @Override + public void run() { + driver.switchTo().alert().accept(); + } + }); + } + + private void assertLocation(String urlPattern) { + assertThat(driver.getCurrentUrl()).matches(glob(urlPattern)); + } + + private void verifyHtmlSource(String expect) { + assertThat(driver.getPageSource()).matches(glob(expect)); + } + + private String replacePlaceholders(String text) { + for (Map.Entry<String, String> entry : variables.entrySet()) { + text = text.replace("${" + entry.getKey() + "}", entry.getValue()); + } + return text; + } +} diff --git a/tests/src/test/java/util/selenium/Text.java b/tests/src/test/java/util/selenium/Text.java new file mode 100644 index 00000000000..5f3b8d259ce --- /dev/null +++ b/tests/src/test/java/util/selenium/Text.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import com.google.common.base.Joiner; +import org.openqa.selenium.By; + +public abstract class Text { + private Text() { + // Static utility class + } + + public static String doesOrNot(boolean not, String verb) { + if (!verb.contains(" ")) { + if (not) { + return "doesn't " + verb; + } else if (verb.endsWith("h")) { + return verb + "es"; + } else { + return verb + "s"; + } + } + + String[] verbs = verb.split(" "); + verbs[0] = doesOrNot(not, verbs[0]); + + return Joiner.on(" ").join(verbs); + } + + public static String isOrNot(boolean not, String state) { + return (not ? "is not " : "is ") + state; + } + + public static String plural(int n, String word) { + return (n + " " + word) + (n <= 1 ? "" : "s"); + } + + public static String toString(By selector) { + return selector.toString().replace("By.selector: ", "").replace("By.cssSelector: ", ""); + } +} diff --git a/tests/src/test/java/util/selenium/WebElementHelper.java b/tests/src/test/java/util/selenium/WebElementHelper.java new file mode 100644 index 00000000000..edb9d9dccb5 --- /dev/null +++ b/tests/src/test/java/util/selenium/WebElementHelper.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.selenium; + +import org.openqa.selenium.WebElement; + +class WebElementHelper { + WebElementHelper() { + // Static class + } + + public static String text(WebElement element) { + String text = element.getText(); + if (!"".equals(text)) { + return nullToEmpty(text); + } + + return nullToEmpty(element.getAttribute("value")); + } + + private static String nullToEmpty(String text) { + return (text == null) ? "" : text; + } +} diff --git a/tests/src/test/java/util/user/GroupManagement.java b/tests/src/test/java/util/user/GroupManagement.java new file mode 100644 index 00000000000..41b362f3e0a --- /dev/null +++ b/tests/src/test/java/util/user/GroupManagement.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.user; + +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; + +/** + * @deprecated replaced by {@link org.sonarqube.tests.Tester} + */ +@Deprecated +public interface GroupManagement { + void createGroup(String name); + + void createGroup(String name, @Nullable String description); + + void removeGroups(List<String> groupNames); + + void removeGroups(String... groupNames); + + Optional<Groups.Group> getGroupByName(String name); + + Groups getGroups(); + + void verifyUserGroupMembership(String userLogin, String... groups); + + Groups getUserGroups(String userLogin); + + void associateGroupsToUser(String userLogin, String... groups); +} diff --git a/tests/src/test/java/util/user/Groups.java b/tests/src/test/java/util/user/Groups.java new file mode 100644 index 00000000000..7b87547bc17 --- /dev/null +++ b/tests/src/test/java/util/user/Groups.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.user; + +import com.google.gson.Gson; +import java.util.List; + +/** + * @deprecated replaced by {@link org.sonarqube.tests.Tester} + */ +@Deprecated + +public class Groups { + + private List<Group> groups; + + private Groups(List<Group> groups) { + this.groups = groups; + } + + public List<Group> getGroups() { + return groups; + } + + public static Groups parse(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, Groups.class); + } + + public static class Group { + private final String name; + + private Group(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/tests/src/test/java/util/user/UserRule.java b/tests/src/test/java/util/user/UserRule.java new file mode 100644 index 00000000000..fe93e631c01 --- /dev/null +++ b/tests/src/test/java/util/user/UserRule.java @@ -0,0 +1,395 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.user; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.sonar.orchestrator.Orchestrator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.rules.ExternalResource; +import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; +import org.sonarqube.ws.client.permission.AddUserWsRequest; +import org.sonarqube.ws.client.user.CreateRequest; +import org.sonarqube.ws.client.user.SearchRequest; +import org.sonarqube.ws.client.user.UsersService; +import util.selenium.Consumer; + +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static util.ItUtils.newAdminWsClient; + +/** + * @deprecated replaced by {@link org.sonarqube.tests.Tester} + */ +@Deprecated +public class UserRule extends ExternalResource implements GroupManagement { + + public static final String ADMIN_LOGIN = "admin"; + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + private final Orchestrator orchestrator; + + private WsClient adminWsClient; + private final GroupManagement defaultOrganizationGroupManagement; + + public UserRule(Orchestrator orchestrator) { + this.orchestrator = orchestrator; + this.defaultOrganizationGroupManagement = new GroupManagementImpl(null); + } + + public static UserRule from(Orchestrator orchestrator) { + return new UserRule(requireNonNull(orchestrator, "Orchestrator instance cannot be null")); + } + + @Override + protected void after() { + deactivateAllUsers(); + // TODO delete groups + } + + // ***************** + // Users + // ***************** + + public void resetUsers() { + for (Users.User user : getUsers().getUsers()) { + String userLogin = user.getLogin(); + if (!userLogin.equals(ADMIN_LOGIN)) { + deactivateUsers(userLogin); + } + } + } + + public Users.User verifyUserExists(String login, String name, @Nullable String email) { + Optional<Users.User> user = getUserByLogin(login); + assertThat(user).as("User with login '%s' hasn't been found", login).isPresent(); + assertThat(user.get().getLogin()).isEqualTo(login); + assertThat(user.get().getName()).isEqualTo(name); + assertThat(user.get().getEmail()).isEqualTo(email); + return user.get(); + } + + public void verifyUserExists(String login, String name, @Nullable String email, boolean local) { + Users.User user = verifyUserExists(login, name, email); + assertThat(user.isLocal()).isEqualTo(local); + } + + public void verifyUserDoesNotExist(String login) { + assertThat(getUserByLogin(login)).as("Unexpected user with login '%s' has been found", login).isAbsent(); + } + + public WsUsers.CreateWsResponse.User createUser(String login, String name, @Nullable String email, String password) { + CreateRequest.Builder request = CreateRequest.builder() + .setLogin(login) + .setName(name) + .setEmail(email) + .setPassword(password); + return adminWsClient().users().create(request.build()).getUser(); + } + + /** + * Create user with randomly generated values. By default password is the login. + */ + @SafeVarargs + public final WsUsers.CreateWsResponse.User generate(Consumer<CreateRequest.Builder>... populators) { + int id = ID_GENERATOR.getAndIncrement(); + String login = "login" + id; + CreateRequest.Builder request = CreateRequest.builder() + .setLogin(login) + .setName("name" + id) + .setEmail(id + "@test.com") + .setPassword(login); + stream(populators).forEach(p -> p.accept(request)); + return adminWsClient().users().create(request.build()).getUser(); + } + + public void createUser(String login, String password) { + createUser(login, login, null, password); + } + + public WsUsers.CreateWsResponse.User createAdministrator(Organizations.Organization organization, String password) { + WsUsers.CreateWsResponse.User user = generate(p -> p.setPassword(password)); + adminWsClient.organizations().addMember(organization.getKey(), user.getLogin()); + forOrganization(organization.getKey()).associateGroupsToUser(user.getLogin(), "Owners"); + return user; + } + + /** + * Create a new admin user with random login, having password same as login + */ + public String createAdminUser() { + String login = randomAlphabetic(10).toLowerCase(); + return createAdminUser(login, login); + } + + public String createAdminUser(String login, String password) { + createUser(login, password); + adminWsClient.permissions().addUser(new AddUserWsRequest().setLogin(login).setPermission("admin")); + adminWsClient.userGroups().addUser(org.sonarqube.ws.client.usergroup.AddUserWsRequest.builder().setLogin(login).setName("sonar-administrators").build()); + return login; + } + + /** + * Create a new root user with random login, having password same as login + */ + public String createRootUser() { + String login = randomAlphabetic(10).toLowerCase(); + return createRootUser(login, login); + } + + public String createRootUser(String login, String password) { + createUser(login, password); + setRoot(login); + return login; + } + + public void setRoot(String login) { + adminWsClient().roots().setRoot(login); + } + + public void unsetRoot(String login) { + adminWsClient().roots().unsetRoot(login); + } + + public Optional<Users.User> getUserByLogin(String login) { + return FluentIterable.from(getUsers().getUsers()).firstMatch(new MatchUserLogin(login)); + } + + public Users getUsers() { + WsResponse response = adminWsClient().wsConnector().call( + new GetRequest("api/users/search")) + .failIfNotSuccessful(); + return Users.parse(response.content()); + } + + public void deactivateUsers(List<String> userLogins) { + for (String userLogin : userLogins) { + if (getUserByLogin(userLogin).isPresent()) { + adminWsClient().wsConnector().call(new PostRequest("api/users/deactivate").setParam("login", userLogin)).failIfNotSuccessful(); + } + } + } + + public void deactivateUsers(String... userLogins) { + deactivateUsers(asList(userLogins)); + } + + public void deactivateAllUsers() { + UsersService service = newAdminWsClient(orchestrator).users(); + List<String> logins = service.search(SearchRequest.builder().build()).getUsersList() + .stream() + .filter(u -> !u.getLogin().equals("admin")) + .map(u -> u.getLogin()) + .collect(Collectors.toList()); + deactivateUsers(logins); + } + + // ***************** + // User groups + // ***************** + + public GroupManagement forOrganization(String organizationKey) { + return new GroupManagementImpl(organizationKey); + } + + private final class GroupManagementImpl implements GroupManagement { + @CheckForNull + private final String organizationKey; + + private GroupManagementImpl(@Nullable String organizationKey) { + this.organizationKey = organizationKey; + } + + @Override + public void createGroup(String name) { + createGroup(name, null); + } + + @Override + public void createGroup(String name, @Nullable String description) { + PostRequest request = new PostRequest("api/user_groups/create") + .setParam("name", name) + .setParam("description", description); + addOrganizationParam(request); + adminWsClient().wsConnector().call(request).failIfNotSuccessful(); + } + + private void addOrganizationParam(PostRequest request) { + request.setParam("organization", organizationKey); + } + + private void addOrganizationParam(GetRequest request) { + request.setParam("organization", organizationKey); + } + + @Override + public void removeGroups(List<String> groupNames) { + for (String groupName : groupNames) { + if (getGroupByName(groupName).isPresent()) { + PostRequest request = new PostRequest("api/user_groups/delete") + .setParam("name", groupName); + addOrganizationParam(request); + adminWsClient().wsConnector().call(request).failIfNotSuccessful(); + } + } + } + + @Override + public void removeGroups(String... groupNames) { + removeGroups(asList(groupNames)); + } + + @Override + public java.util.Optional<Groups.Group> getGroupByName(String name) { + return getGroups().getGroups().stream().filter(new MatchGroupName(name)::apply).findFirst(); + } + + @Override + public Groups getGroups() { + GetRequest request = new GetRequest("api/user_groups/search"); + addOrganizationParam(request); + WsResponse response = adminWsClient().wsConnector().call(request).failIfNotSuccessful(); + return Groups.parse(response.content()); + } + + @Override + public void verifyUserGroupMembership(String userLogin, String... expectedGroups) { + Groups userGroup = getUserGroups(userLogin); + List<String> userGroupName = userGroup.getGroups().stream().map(Groups.Group::getName).collect(Collectors.toList()); + assertThat(userGroupName).containsOnly(expectedGroups); + } + + @Override + public Groups getUserGroups(String userLogin) { + GetRequest request = new GetRequest("api/users/groups") + .setParam("login", userLogin) + .setParam("selected", "selected"); + addOrganizationParam(request); + WsResponse response = adminWsClient().wsConnector().call(request).failIfNotSuccessful(); + return Groups.parse(response.content()); + } + + @Override + public void associateGroupsToUser(String userLogin, String... groups) { + for (String group : groups) { + PostRequest request = new PostRequest("api/user_groups/add_user") + .setParam("login", userLogin) + .setParam("name", group); + addOrganizationParam(request); + adminWsClient().wsConnector().call(request).failIfNotSuccessful(); + } + } + } + + @Override + public void createGroup(String name) { + defaultOrganizationGroupManagement.createGroup(name); + } + + @Override + public void createGroup(String name, @Nullable String description) { + defaultOrganizationGroupManagement.createGroup(name, description); + } + + @Override + public void removeGroups(List<String> groupNames) { + defaultOrganizationGroupManagement.removeGroups(groupNames); + } + + @Override + public void removeGroups(String... groupNames) { + defaultOrganizationGroupManagement.removeGroups(groupNames); + } + + @Override + public java.util.Optional<Groups.Group> getGroupByName(String name) { + return defaultOrganizationGroupManagement.getGroupByName(name); + } + + @Override + public Groups getGroups() { + return defaultOrganizationGroupManagement.getGroups(); + } + + @Override + public void verifyUserGroupMembership(String userLogin, String... groups) { + defaultOrganizationGroupManagement.verifyUserGroupMembership(userLogin, groups); + } + + @Override + public Groups getUserGroups(String userLogin) { + return defaultOrganizationGroupManagement.getUserGroups(userLogin); + } + + @Override + public void associateGroupsToUser(String userLogin, String... groups) { + defaultOrganizationGroupManagement.associateGroupsToUser(userLogin, groups); + } + + private WsClient adminWsClient() { + if (adminWsClient == null) { + adminWsClient = newAdminWsClient(orchestrator); + } + return adminWsClient; + } + + private class MatchUserLogin implements Predicate<Users.User> { + private final String login; + + private MatchUserLogin(String login) { + this.login = login; + } + + @Override + public boolean apply(@Nonnull Users.User user) { + String login = user.getLogin(); + return login != null && login.equals(this.login) && user.isActive(); + } + } + + private class MatchGroupName implements Predicate<Groups.Group> { + private final String groupName; + + private MatchGroupName(String groupName) { + this.groupName = groupName; + } + + @Override + public boolean apply(@Nonnull Groups.Group group) { + String groupName = group.getName(); + return groupName != null && groupName.equals(this.groupName); + } + } + +} diff --git a/tests/src/test/java/util/user/Users.java b/tests/src/test/java/util/user/Users.java new file mode 100644 index 00000000000..2f0a252e6c7 --- /dev/null +++ b/tests/src/test/java/util/user/Users.java @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package util.user; + +import com.google.gson.Gson; +import java.util.List; + +public class Users { + + private List<User> users; + + private Users(List<User> users) { + this.users = users; + } + + public List<User> getUsers() { + return users; + } + + public static Users parse(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, Users.class); + } + + public static class User { + private final String login; + private final String name; + private final String email; + private final String externalIdentity; + private final String externalProvider; + private final List<String> groups; + private final List<String> scmAccounts; + private final boolean active; + private final boolean local; + private int tokensCount; + + private User(String login, String name, String email, String externalIdentity, String externalProvider, List<String> groups, List<String> scmAccounts, + boolean active, boolean local, int tokensCount) { + this.login = login; + this.name = name; + this.externalIdentity = externalIdentity; + this.externalProvider = externalProvider; + this.email = email; + this.groups = groups; + this.scmAccounts = scmAccounts; + this.active = active; + this.tokensCount = tokensCount; + this.local = local; + } + + public String getLogin() { + return login; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public List<String> getGroups() { + return groups; + } + + public List<String> getScmAccounts() { + return scmAccounts; + } + + public boolean isActive() { + return active; + } + + public boolean isLocal() { + return local; + } + + public int getTokensCount() { + return tokensCount; + } + + public String getExternalIdentity() { + return externalIdentity; + } + + public String getExternalProvider() { + return externalProvider; + } + } +} diff --git a/tests/src/test/resources/analysis/BatchTest/one-issue-per-line.xml b/tests/src/test/resources/analysis/BatchTest/one-issue-per-line.xml new file mode 100644 index 00000000000..7bb4ed5593a --- /dev/null +++ b/tests/src/test/resources/analysis/BatchTest/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/multiline.xml b/tests/src/test/resources/analysis/IssueJsonReportTest/multiline.xml new file mode 100644 index 00000000000..778866e91c2 --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/multiline.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>multiline</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>MultilineIssue</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/no-server-analysis.json b/tests/src/test/resources/analysis/IssueJsonReportTest/no-server-analysis.json new file mode 100644 index 00000000000..4e1b18df6ee --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/no-server-analysis.json @@ -0,0 +1,212 @@ +{ + "components": [ + { + "key": "sample" + }, + { + "key": "sample:src/main/xoo/sample/Sample.xoo", + "moduleKey": "sample", + "path": "src/main/xoo/sample/Sample.xoo", + "status": "ADDED" + }, + { + "key": "sample:src/main/xoo/sample", + "moduleKey": "sample", + "path": "src/main/xoo/sample" + } + ], + "issues": [ + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 2, + "endOffset": 0, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 6, + "endOffset": 14, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 1, + "endOffset": 15, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 5, + "endOffset": 23, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 8, + "endOffset": 1, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 9, + "endOffset": 28, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 7, + "endOffset": 2, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 4, + "endOffset": 1, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 11, + "endOffset": 2, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 3, + "endOffset": 21, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 13, + "endOffset": 0, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 13, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 12, + "endOffset": 1, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 10, + "endOffset": 17, + "isNew": true, + "key": "<ISSUE_KEY>", + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "startOffset": 0, + "status": "OPEN" + } + ], + "rules": [ + { + "key": "xoo:OneIssuePerLine", + "name": "One Issue Per Line", + "repository": "xoo", + "rule": "OneIssuePerLine" + } + ], + "users": [], + "version": "<SONAR_VERSION>" +} diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/one-issue-per-line.xml b/tests/src/test/resources/analysis/IssueJsonReportTest/one-issue-per-line.xml new file mode 100644 index 00000000000..7bb4ed5593a --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-root-module.json b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-root-module.json new file mode 100644 index 00000000000..ad3c0218cdb --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-root-module.json @@ -0,0 +1,880 @@ +{ + "components": [ + { + "key": "com.sonarsource.it.samples:multi-modules-sample" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a", + "path": "module_a" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", + "path": "module_a1" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2", + "path": "module_a2" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b", + "path": "module_b" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1", + "path": "module_b1" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2", + "path": "module_b2" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", + "path": "src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "status": "SAME" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2", + "path": "src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "status": "SAME" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1", + "path": "src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "status": "SAME" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2", + "path": "src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "status": "SAME" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", + "path": "src/main/xoo/com/sonar/it/samples/modules/a1" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2", + "path": "src/main/xoo/com/sonar/it/samples/modules/a2" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1", + "path": "src/main/xoo/com/sonar/it/samples/modules/b1" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2", + "path": "src/main/xoo/com/sonar/it/samples/modules/b2" + } + ], + "issues": [ + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 11, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 13, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 13, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 13, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 14, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 14, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 14, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 15, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 15, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 15, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 16, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 16, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 16, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 7, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 7, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 11, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 13, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 13, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 13, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 14, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 14, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 14, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 15, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 15, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 15, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 16, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 16, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 16, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 17, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 17, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 17, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 18, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 18, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 18, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 19, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 19, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 19, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 20, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 20, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 20, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a2:src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 21, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 21, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 21, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 7, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 11, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b1:src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 7, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_b:module_b2:src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 11, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "status": "OPEN" + } + ], + "rules": [ + { + "key": "xoo:OneIssuePerLine", + "name": "One Issue Per Line", + "repository": "xoo", + "rule": "OneIssuePerLine" + } + ], + "users": [], + "version": "<SONAR_VERSION>" +} diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-single-module-branch.json b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-single-module-branch.json new file mode 100644 index 00000000000..aa066bbbf2f --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-single-module-branch.json @@ -0,0 +1,255 @@ +{ + "components": [ + { + "key": "sample:mybranch" + }, + { + "key": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "moduleKey": "sample:mybranch", + "path": "src/main/xoo/sample/Sample.xoo", + "status": "CHANGED" + }, + { + "key": "sample:mybranch:src/main/xoo/sample", + "moduleKey": "sample:mybranch", + "path": "src/main/xoo/sample" + } + ], + "issues": [ + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "endOffset": 15, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "endOffset": 0, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "endOffset": 21, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "endOffset": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "endOffset": 23, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "endOffset": 14, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "endOffset": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "endOffset": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "endOffset": 28, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "endOffset": 17, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 13, + "endOffset": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 13, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 13, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 14, + "endOffset": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 14, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 14, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 15, + "endOffset": 0, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 15, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 15, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 11, + "endOffset": 28, + "isNew": true, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:mybranch:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 7, + "endOffset": 28, + "isNew": true, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "startOffset": 0, + "status": "OPEN" + } + ], + "rules": [ + { + "key": "xoo:OneIssuePerLine", + "name": "One Issue Per Line", + "repository": "xoo", + "rule": "OneIssuePerLine" + } + ], + "users": [], + "version": "<SONAR_VERSION>" +} diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-single-module.json b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-single-module.json new file mode 100644 index 00000000000..6c3aab91f02 --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-single-module.json @@ -0,0 +1,255 @@ +{ + "components": [ + { + "key": "sample" + }, + { + "key": "sample:src/main/xoo/sample/Sample.xoo", + "moduleKey": "sample", + "path": "src/main/xoo/sample/Sample.xoo", + "status": "CHANGED" + }, + { + "key": "sample:src/main/xoo/sample", + "moduleKey": "sample", + "path": "src/main/xoo/sample" + } + ], + "issues": [ + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 11, + "endOffset": 28, + "isNew": true, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-02T00:00:00+0200", + "endLine": 7, + "endOffset": 28, + "isNew": true, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "endOffset": 15, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "endOffset": 0, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "endOffset": 21, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "endOffset": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "endOffset": 23, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "endOffset": 14, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "endOffset": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "endOffset": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "endOffset": 28, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "endOffset": 17, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 13, + "endOffset": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 13, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 13, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 14, + "endOffset": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 14, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 14, + "startOffset": 0, + "status": "OPEN" + }, + { + "component": "sample:src/main/xoo/sample/Sample.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 15, + "endOffset": 0, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 15, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 15, + "startOffset": 0, + "status": "OPEN" + } + ], + "rules": [ + { + "key": "xoo:OneIssuePerLine", + "name": "One Issue Per Line", + "repository": "xoo", + "rule": "OneIssuePerLine" + } + ], + "users": [], + "version": "<SONAR_VERSION>" +} diff --git a/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-sub-module.json b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-sub-module.json new file mode 100644 index 00000000000..84a68c4d5a4 --- /dev/null +++ b/tests/src/test/resources/analysis/IssueJsonReportTest/report-on-sub-module.json @@ -0,0 +1,238 @@ +{ + "components": [ + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", + "path": "src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "status": "SAME" + }, + { + "key": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1", + "moduleKey": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1", + "path": "src/main/xoo/com/sonar/it/samples/modules/a1" + } + ], + "issues": [ + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 1, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 1, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 1, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 2, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 2, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 2, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 3, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 3, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 3, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 4, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 4, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 4, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 5, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 5, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 5, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 6, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 6, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 6, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 7, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 7, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 7, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 8, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 8, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 8, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 9, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 9, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 9, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 10, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 10, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 10, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 11, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 11, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 11, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 12, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 12, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 12, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 13, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 13, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 13, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 14, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 14, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 14, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 15, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 15, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 15, + "status": "OPEN" + }, + { + "component": "com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo", + "creationDate": "2013-05-01T00:00:00+0200", + "endLine": 16, + "isNew": false, + "key": "<ISSUE_KEY>", + "line": 16, + "message": "This issue is generated on each line", + "rule": "xoo:OneIssuePerLine", + "severity": "MAJOR", + "startLine": 16, + "status": "OPEN" + } + ], + "rules": [ + { + "key": "xoo:OneIssuePerLine", + "name": "One Issue Per Line", + "repository": "xoo", + "rule": "OneIssuePerLine" + } + ], + "users": [], + "version": "<SONAR_VERSION>" +} diff --git a/tests/src/test/resources/analysis/IssuesModeTest/empty.xml b/tests/src/test/resources/analysis/IssuesModeTest/empty.xml new file mode 100644 index 00000000000..8bab61d6c85 --- /dev/null +++ b/tests/src/test/resources/analysis/IssuesModeTest/empty.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>empty</name> + <language>xoo</language> + <rules> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/IssuesModeTest/one-issue-per-line-empty.xml b/tests/src/test/resources/analysis/IssuesModeTest/one-issue-per-line-empty.xml new file mode 100644 index 00000000000..0ba34f10dbd --- /dev/null +++ b/tests/src/test/resources/analysis/IssuesModeTest/one-issue-per-line-empty.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + </rules> +</profile> diff --git a/tests/src/test/resources/analysis/IssuesModeTest/one-issue-per-line.xml b/tests/src/test/resources/analysis/IssuesModeTest/one-issue-per-line.xml new file mode 100644 index 00000000000..7bb4ed5593a --- /dev/null +++ b/tests/src/test/resources/analysis/IssuesModeTest/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/IssuesModeTest/with-many-rules.xml b/tests/src/test/resources/analysis/IssuesModeTest/with-many-rules.xml new file mode 100644 index 00000000000..f3d0baf0616 --- /dev/null +++ b/tests/src/test/resources/analysis/IssuesModeTest/with-many-rules.xml @@ -0,0 +1,32 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/MultiLanguageTest/one-issue-per-line-xoo2.xml b/tests/src/test/resources/analysis/MultiLanguageTest/one-issue-per-line-xoo2.xml new file mode 100644 index 00000000000..67215f91a52 --- /dev/null +++ b/tests/src/test/resources/analysis/MultiLanguageTest/one-issue-per-line-xoo2.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line-xoo2</name> + <language>xoo2</language> + <rules> + <rule> + <repositoryKey>xoo2</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/analysis/MultiLanguageTest/one-issue-per-line.xml b/tests/src/test/resources/analysis/MultiLanguageTest/one-issue-per-line.xml new file mode 100644 index 00000000000..7bb4ed5593a --- /dev/null +++ b/tests/src/test/resources/analysis/MultiLanguageTest/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/SSLTest/README b/tests/src/test/resources/analysis/SSLTest/README new file mode 100644 index 00000000000..fb535a907c6 --- /dev/null +++ b/tests/src/test/resources/analysis/SSLTest/README @@ -0,0 +1,184 @@ +$ openssl req -config ./openssl.cnf -newkey rsa:2048 -nodes -keyform PEM -keyout ca.key -x509 -days 3650 -extensions certauth -outform PEM -out ca.cer +Generating a 2048 bit RSA private key +..............................................+++ +........................................................................................................+++ +writing new private key to 'ca.key' +----- +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country [FR]: +Locality [Poitiers]: +Organization [SonarSource]: +Common Name []:CA for SonarQube ITs + +$ openssl genrsa -out server.key 2048 +Generating RSA private key, 2048 bit long modulus +........................+++ +............................+++ +e is 65537 (0x10001) + +$ openssl req -config ./openssl.cnf -new -key server.key -out server.req +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country [FR]: +Locality [Poitiers]: +Organization [SonarSource]: +Common Name []:localhost + +$ openssl x509 -req -in server.req -CA ca.cer -CAkey ca.key -set_serial 100 -extfile openssl.cnf -extensions server -days 3650 -outform PEM -out server.cer +Signature ok +subject=/C=FR/L=Poitiers/O=SonarSource/CN=localhost +Getting CA Private Key + +$ openssl genrsa -out client.key 2048 +Generating RSA private key, 2048 bit long modulus +...................................+++ +.....................................................+++ +e is 65537 (0x10001) + +$ openssl req -config ./openssl.cnf -new -key client.key -out client.req +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country [FR]: +Locality [Poitiers]: +Organization [SonarSource]: +Common Name []:Julien Henry + +$ openssl x509 -req -in client.req -CA ca.cer -CAkey ca.key -set_serial 101 -extfile openssl.cnf -extensions client -days 3650 -outform PEM -out client.cer +Signature ok +subject=/C=FR/L=Poitiers/O=SonarSource/CN=Julien Henry +Getting CA Private Key + +$ openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12 +Enter Export Password: clientp12pwd +Verifying - Enter Export Password: clientp12pwd + +$ openssl pkcs12 -inkey server.key -in server.cer -export -out server.p12 +Enter Export Password: serverp12pwd +Verifying - Enter Export Password: serverp12pwd + +$ keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore serverkeystore.jks +Entrez le mot de passe du fichier de clés de destination : serverkeystorepwd +Ressaisissez le nouveau mot de passe : serverkeystorepwd +Entrez le mot de passe du fichier de clés source : serverp12pwd +L'entrée de l'alias 1 a été importée. +Commande d'import exécutée : 1 entrées importées, échec ou annulation de 0 entrées + +$ keytool -import -file ca.cer -keystore servertruststore.jks +Entrez le mot de passe du fichier de clés : servertruststorepwd +Ressaisissez le nouveau mot de passe : servertruststorepwd +Propriétaire : CN=Test CA, O=SonarSource, L=Poitiers, C=FR +Emetteur : CN=Test CA, O=SonarSource, L=Poitiers, C=FR +Numéro de série : dabbebc7bce2fc6a +Valide du : Wed Aug 31 14:42:15 CEST 2016 au : Sat Aug 29 14:42:15 CEST 2026 +Empreintes du certificat : + MD5: 69:36:AE:65:51:CD:F4:C3:83:77:DE:45:AE:49:01:1A + SHA1 : 77:92:45:84:18:FC:34:7A:2A:B0:EC:3D:22:48:15:1A:19:71:1D:B3 + SHA256 : 99:03:89:84:6E:E3:D3:B7:12:2D:70:7E:49:88:49:41:52:1C:89:3A:9B:C0:83:D1:C5:44:D4:93:FB:42:C8:07 + Nom de l'algorithme de signature : SHA1withRSA + Version : 3 + +Extensions : + +#1: ObjectId: 2.5.29.35 Criticality=false +AuthorityKeyIdentifier [ +KeyIdentifier [ +0000: 3A 61 C1 86 AD BE FC 15 82 B3 59 FF 00 28 5E F9 :a........Y..(^. +0010: B5 5A 87 04 .Z.. +] +[CN=Test CA, O=SonarSource, L=Poitiers, C=FR] +SerialNumber: [ dabbebc7 bce2fc6a] +] + +#2: ObjectId: 2.5.29.19 Criticality=false +BasicConstraints:[ + CA:true + PathLen:2147483647 +] + +#3: ObjectId: 2.5.29.31 Criticality=false +CRLDistributionPoints [ + [DistributionPoint: + [URIName: http://testca.local/ca.crl] +]] + +#4: ObjectId: 2.5.29.14 Criticality=false +SubjectKeyIdentifier [ +KeyIdentifier [ +0000: 3A 61 C1 86 AD BE FC 15 82 B3 59 FF 00 28 5E F9 :a........Y..(^. +0010: B5 5A 87 04 .Z.. +] +] + +Faire confiance à ce certificat ? [non] : oui +Certificat ajouté au fichier de clés + +$ keytool -import -file server.cer -keystore clienttruststore.jks +Entrez le mot de passe du fichier de clés : clienttruststorepwd +Ressaisissez le nouveau mot de passe : clienttruststorepwd +Propriétaire : CN=localhost, O=SonarSource, L=Poitiers, C=FR +Emetteur : CN=Test CA, O=SonarSource, L=Poitiers, C=FR +Numéro de série : 64 +Valide du : Wed Aug 31 14:45:30 CEST 2016 au : Thu Aug 31 14:45:30 CEST 2017 +Empreintes du certificat : + MD5: 40:52:F4:5E:67:C3:68:B6:00:7D:70:C0:1E:8E:75:2E + SHA1 : 83:3F:4F:AC:4E:E6:F4:06:14:01:E6:5B:F2:63:34:94:68:12:8C:3A + SHA256 : 7C:03:9A:CA:0D:B5:57:A5:66:56:75:09:23:45:9E:D5:CC:6C:72:14:0B:4B:9B:E8:29:3F:78:4C:9F:D6:77:E2 + Nom de l'algorithme de signature : SHA256withRSA + Version : 3 + +Extensions : + +#1: ObjectId: 2.5.29.19 Criticality=false +BasicConstraints:[ + CA:false + PathLen: undefined +] + +#2: ObjectId: 2.5.29.31 Criticality=false +CRLDistributionPoints [ + [DistributionPoint: + [URIName: http://testca.local/ca.crl] +]] + +#3: ObjectId: 2.5.29.37 Criticality=false +ExtendedKeyUsages [ + serverAuth +] + +#4: ObjectId: 2.5.29.15 Criticality=false +KeyUsage [ + DigitalSignature + Key_Encipherment + Data_Encipherment +] + +#5: ObjectId: 2.16.840.1.113730.1.1 Criticality=false +NetscapeCertType [ + SSL server +] + +Faire confiance à ce certificat ? [non] : oui +Certificat ajouté au fichier de clés + +$ keytool -importkeystore -srckeystore client.p12 -srcstoretype PKCS12 -destkeystore clientkeystore.jks +Entrez le mot de passe du fichier de clés de destination : clientp12pwd +Ressaisissez le nouveau mot de passe : clientp12pwd +Entrez le mot de passe du fichier de clés source : clientp12pwd +L'entrée de l'alias 1 a été importée. +Commande d'import exécutée : 1 entrées importées, échec ou annulation de 0 entrées diff --git a/tests/src/test/resources/analysis/SSLTest/clientkeystore.jks b/tests/src/test/resources/analysis/SSLTest/clientkeystore.jks Binary files differnew file mode 100644 index 00000000000..653f3c7f093 --- /dev/null +++ b/tests/src/test/resources/analysis/SSLTest/clientkeystore.jks diff --git a/tests/src/test/resources/analysis/SSLTest/clienttruststore.jks b/tests/src/test/resources/analysis/SSLTest/clienttruststore.jks Binary files differnew file mode 100644 index 00000000000..2529245e1c6 --- /dev/null +++ b/tests/src/test/resources/analysis/SSLTest/clienttruststore.jks diff --git a/tests/src/test/resources/analysis/SSLTest/openssl.cnf b/tests/src/test/resources/analysis/SSLTest/openssl.cnf new file mode 100644 index 00000000000..4a5148fd4d7 --- /dev/null +++ b/tests/src/test/resources/analysis/SSLTest/openssl.cnf @@ -0,0 +1,38 @@ +[ req ] +default_md = sha1 +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +countryName = Country +countryName_default = FR +countryName_min = 2 +countryName_max = 2 +localityName = Locality +localityName_default = Poitiers +organizationName = Organization +organizationName_default = SonarSource +commonName = Common Name +commonName_max = 64 + +[ certauth ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = CA:true +crlDistributionPoints = @crl + +[ server ] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment, dataEncipherment +extendedKeyUsage = serverAuth +nsCertType = server +crlDistributionPoints = @crl + +[ client ] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment, dataEncipherment +extendedKeyUsage = clientAuth +nsCertType = client +crlDistributionPoints = @crl + +[ crl ] +URI=http://testca.local/ca.crl
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/SSLTest/serverkeystore.jks b/tests/src/test/resources/analysis/SSLTest/serverkeystore.jks Binary files differnew file mode 100644 index 00000000000..319c899278a --- /dev/null +++ b/tests/src/test/resources/analysis/SSLTest/serverkeystore.jks diff --git a/tests/src/test/resources/analysis/SSLTest/servertruststore.jks b/tests/src/test/resources/analysis/SSLTest/servertruststore.jks Binary files differnew file mode 100644 index 00000000000..a3aee3b8a9d --- /dev/null +++ b/tests/src/test/resources/analysis/SSLTest/servertruststore.jks diff --git a/tests/src/test/resources/analysis/SettingsEncryptionTest/sonar-secret.txt b/tests/src/test/resources/analysis/SettingsEncryptionTest/sonar-secret.txt new file mode 100644 index 00000000000..65b98c522da --- /dev/null +++ b/tests/src/test/resources/analysis/SettingsEncryptionTest/sonar-secret.txt @@ -0,0 +1 @@ +0PZz+G+f8mjr3sPn4+AhHg==
\ No newline at end of file diff --git a/tests/src/test/resources/analysis/TempFolderTest/one-issue-per-line.xml b/tests/src/test/resources/analysis/TempFolderTest/one-issue-per-line.xml new file mode 100644 index 00000000000..7bb4ed5593a --- /dev/null +++ b/tests/src/test/resources/analysis/TempFolderTest/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_create.html b/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_create.html new file mode 100644 index 00000000000..0e67e0f4fab --- /dev/null +++ b/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_create.html @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>root-user</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>root-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>name=commit</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/permission_templates</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.page-actions button</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=.page-actions button</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#permission-template-name</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>css=#permission-template-name</td> + <td>Custom</td> + </tr> + <tr> + <td>type</td> + <td>css=#permission-template-description</td> + <td>Description</td> + </tr> + <tr> + <td>type</td> + <td>css=#permission-template-project-key-pattern</td> + <td>.*</td> + </tr> + <tr> + <td>click</td> + <td>css=#permission-template-submit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=tr[data-name="Custom"]</td> + <td></td> + </tr> + <tr> + <td>assertText</td> + <td>css=tr[data-name="Custom"] .js-name</td> + <td>*Custom*</td> + </tr> + <tr> + <td>assertText</td> + <td>css=tr[data-name="Custom"] .js-description</td> + <td>*Description*</td> + </tr> + <tr> + <td>assertText</td> + <td>css=tr[data-name="Custom"] .js-project-key-pattern</td> + <td>*.*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_display_page.html b/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_display_page.html new file mode 100644 index 00000000000..8d9640f25b6 --- /dev/null +++ b/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_display_page.html @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_display_page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/permission_templates</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=tr[data-id="default_template"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=tr[data-id="default_template"] .js-name</td> + <td>*Default template*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=tr[data-id="default_template"] .js-defaults</td> + <td>*Projects*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=tr[data-id="default_template"] .js-description</td> + <td>*This permission template will be used*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=td[data-permission="user"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=td[data-permission="codeviewer"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=td[data-permission="issueadmin"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=td[data-permission="admin"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=td[data-permission="scan"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_manage_project_creators.html b/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_manage_project_creators.html new file mode 100644 index 00000000000..3d1c172a783 --- /dev/null +++ b/tests/src/test/resources/authorisation/PermissionTemplatesPageTest/should_manage_project_creators.html @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_manage_project_creators</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>name=commit</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/permission_templates</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=td[data-permission="user"]</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=td[data-permission="user"] .js-update-users</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#grant-to-project-creators</td> + <td></td> + </tr> + <tr> + <td>assertElementPresent</td> + <td>css=#grant-to-project-creators:not(:checked)</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=#grant-to-project-creators</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=.js-modal-close</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=td[data-permission="user"] .js-project-creators</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=td[data-permission="user"] .js-update-users</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#grant-to-project-creators</td> + <td></td> + </tr> + <tr> + <td>assertElementPresent</td> + <td>css=#grant-to-project-creators:checked</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=#grant-to-project-creators</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=.js-modal-close</td> + <td></td> + </tr> + <tr> + <td>waitForElementNotPresent</td> + <td>css=td[data-permission="user"] .js-project-creators</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-be-able-to-provision-project.html b/tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-be-able-to-provision-project.html new file mode 100644 index 00000000000..c5db3b4898a --- /dev/null +++ b/tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-be-able-to-provision-project.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>user-cannot-administrate-profile</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-with-provisioning</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>password</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/projects_admin</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#projects-type__ALL</td> + <td></td> + </tr> + <tr> + <td>assertText</td> + <td>css=.page-actions button</td> + <td>*Create Project*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-not-be-able-to-provision-project.html b/tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-not-be-able-to-provision-project.html new file mode 100644 index 00000000000..270daf67cd9 --- /dev/null +++ b/tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-not-be-able-to-provision-project.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>user-cannot-administrate-profile</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-without-provisioning</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>password</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/projects_admin</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#projects-type__ALL</td> + <td></td> + </tr> + <tr> + <td>assertNotText</td> + <td>css=.page-actions button</td> + <td>*Create Project*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/normal-user.html b/tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/normal-user.html new file mode 100644 index 00000000000..b9190a27027 --- /dev/null +++ b/tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/normal-user.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>user-cannot-administrate-profile</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>not_profileadm</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>userpwd</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/profiles</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-row[data-name="foo"]</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=.quality-profiles-table-row[data-name="foo"] .quality-profiles-table-name a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*foo*</td> + </tr> + <tr> + <td>assertElementNotPresent</td> + <td>css=.js-change-parent</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html b/tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html new file mode 100644 index 00000000000..86d6b26ece8 --- /dev/null +++ b/tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>user-can-administrate-profile</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>profileadm</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>papwd</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-row[data-name="foo"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profiles-table-row[data-name="foo"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*foo*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.js-change-parent</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/project/quality_profiles?id=sample</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Quality Profiles*</td> +</tr> +<tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Xoo*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/authorisation/one-issue-per-line-profile.xml b/tests/src/test/resources/authorisation/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..521adc7e06f --- /dev/null +++ b/tests/src/test/resources/authorisation/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/dbCleaner/one-issue-per-line-profile.xml b/tests/src/test/resources/dbCleaner/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..2006b6fb365 --- /dev/null +++ b/tests/src/test/resources/dbCleaner/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications-with-deleted-project.html b/tests/src/test/resources/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications-with-deleted-project.html new file mode 100644 index 00000000000..37a48368f83 --- /dev/null +++ b/tests/src/test/resources/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications-with-deleted-project.html @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>duplications-with-deleted-project</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">duplications-with-deleted-project</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=duplicate-project%3Asrc%2Fmain%2Fxoo%2Fsample%2FFile1.xoo</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.source-viewer</td> + <td>*src/main/xoo/sample/File1.xoo*</td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.source-table</td> + <td>*File1*</td> + </tr> + <tr> + <td>click</td> + <td>css=.source-line-duplicated</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.bubble-popup</td> + <td></td> + </tr> + <tr> + <td>assertElementPresent</td> + <td>css=.bubble-popup .alert</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications_on_removed_file-expected.json b/tests/src/test/resources/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications_on_removed_file-expected.json new file mode 100644 index 00000000000..37f0e402120 --- /dev/null +++ b/tests/src/test/resources/duplication/CrossProjectDuplicationsOnRemoveFileTest/duplications_on_removed_file-expected.json @@ -0,0 +1,25 @@ +{ + "duplications": [ + { + "blocks": [ + { + "from": 9, + "size": 27 + }, + { + "from": 9, + "size": 27, + "_ref": "1" + } + ] + } + ], + "files": { + "1": { + "key": "duplicate-project:src/main/xoo/sample/File1.xoo", + "name": "src/main/xoo/sample/File1.xoo", + "project": "duplicate-project", + "projectName": "duplicate-project" + } + } +} diff --git a/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/cross-project-duplications-viewer.html b/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/cross-project-duplications-viewer.html new file mode 100644 index 00000000000..689594fdbb1 --- /dev/null +++ b/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/cross-project-duplications-viewer.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>cross-project-duplications-viewer</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">cross-project-duplications-viewer</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=duplicate-project%3Asrc%2Fmain%2Fxoo%2Fsample%2FFile1.xoo</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.source-line</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.source-viewer-header</td> + <td>*75.0%*</td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.source-line-duplicated</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=[data-line-number='3']</td> + <td>*File1*</td> + </tr> + <tr> + <td>click</td> + <td>css=.source-line-duplicated</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.bubble-popup</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.bubble-popup</td> + <td>glob:*origin-project*File1.xoo*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/duplications_show-expected.json b/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/duplications_show-expected.json new file mode 100644 index 00000000000..a9b0c623f53 --- /dev/null +++ b/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/duplications_show-expected.json @@ -0,0 +1,32 @@ +{ + "duplications": [ + { + "blocks": [ + { + "from": 9, + "size": 27, + "_ref": "1" + }, + { + "from": 9, + "size": 27, + "_ref": "2" + } + ] + } + ], + "files": { + "2": { + "key": "origin-project:src/main/xoo/sample/File1.xoo", + "name": "src/main/xoo/sample/File1.xoo", + "project": "origin-project", + "projectName": "origin-project" + }, + "1": { + "key": "duplicate-project:src/main/xoo/sample/File1.xoo", + "name": "src/main/xoo/sample/File1.xoo", + "project": "duplicate-project", + "projectName": "duplicate-project" + } + } +} diff --git a/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/sources_lines_duplication-expected.json b/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/sources_lines_duplication-expected.json new file mode 100644 index 00000000000..0ce4c68a243 --- /dev/null +++ b/tests/src/test/resources/duplication/CrossProjectDuplicationsTest/sources_lines_duplication-expected.json @@ -0,0 +1,184 @@ +{ + "sources": [ + { + "line": 1, + "code": "package sample;", + "duplicated": false + }, + { + "line": 2, + "code": "", + "duplicated": false + }, + { + "line": 3, + "code": "public class File1 {", + "duplicated": false + }, + { + "line": 4, + "code": "", + "duplicated": false + }, + { + "line": 5, + "code": " public File1() {", + "duplicated": false + }, + { + "line": 6, + "code": " }", + "duplicated": false + }, + { + "line": 7, + "code": "", + "duplicated": false + }, + { + "line": 8, + "code": " public void test() {", + "duplicated": false + }, + { + "line": 9, + "code": " char[] charList = new char[30];", + "duplicated": true + }, + { + "line": 10, + "code": " for (int i = 0; i < 10; i++) {", + "duplicated": true + }, + { + "line": 11, + "code": " charList[i] = 'a';", + "duplicated": true + }, + { + "line": 12, + "code": " }", + "duplicated": true + }, + { + "line": 13, + "code": " for (int i = 0; i < 10; i++) {", + "duplicated": true + }, + { + "line": 14, + "code": " charList[i] = 'a';", + "duplicated": true + }, + { + "line": 15, + "code": " }", + "duplicated": true + }, + { + "line": 16, + "code": " int intergerToBeIncremented = 0;", + "duplicated": true + }, + { + "line": 17, + "code": " while (intergerToBeIncremented < 100) {", + "duplicated": true + }, + { + "line": 18, + "code": " intergerToBeIncremented++;", + "duplicated": true + }, + { + "line": 19, + "code": " }", + "duplicated": true + }, + { + "line": 20, + "code": " int intergerToBeIncremented2 = 0;", + "duplicated": true + }, + { + "line": 21, + "code": " while (intergerToBeIncremented2 < 100) {", + "duplicated": true + }, + { + "line": 22, + "code": " intergerToBeIncremented2++;", + "duplicated": true + }, + { + "line": 23, + "code": " }", + "duplicated": true + }, + { + "line": 24, + "code": " String temp = \"\";", + "duplicated": true + }, + { + "line": 25, + "code": " for (int i=0; i<10; i++){", + "duplicated": true + }, + { + "line": 26, + "code": " temp += \"say something\"+i;", + "duplicated": true + }, + { + "line": 27, + "code": " }", + "duplicated": true + }, + { + "line": 28, + "code": " for (int i=0; i<20; i++){", + "duplicated": true + }, + { + "line": 29, + "code": " temp += \"say nothing\"+i;", + "duplicated": true + }, + { + "line": 30, + "code": " }", + "duplicated": true + }, + { + "line": 31, + "code": " for (int i=0; i<30; i++){", + "duplicated": true + }, + { + "line": 32, + "code": " temp += \"always say nothing\"+i;", + "duplicated": true + }, + { + "line": 33, + "code": " }", + "duplicated": true + }, + { + "line": 34, + "code": " }", + "duplicated": true + }, + { + "line": 35, + "code": "}", + "duplicated": true + }, + { + "line": 36, + "code": "", + "duplicated": false + } + ] +} diff --git a/tests/src/test/resources/duplication/DuplicationsTest/duplications_show-expected.json b/tests/src/test/resources/duplication/DuplicationsTest/duplications_show-expected.json new file mode 100644 index 00000000000..83b69f2be1d --- /dev/null +++ b/tests/src/test/resources/duplication/DuplicationsTest/duplications_show-expected.json @@ -0,0 +1,26 @@ +{ + "duplications": [ + { + "blocks": [ + { + "from": 9, + "size": 29, + "_ref": "1" + }, + { + "from": 40, + "size": 31, + "_ref": "1" + } + ] + } + ], + "files": { + "1": { + "key": "file-duplications:src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo", + "name": "src/main/xoo/duplicated_lines_within_same_file/DuplicatedLinesInSameFile.xoo", + "project": "file-duplications", + "projectName": "file-duplications" + } + } +} diff --git a/tests/src/test/resources/duplication/DuplicationsTest/sources_lines_duplication-expected.json b/tests/src/test/resources/duplication/DuplicationsTest/sources_lines_duplication-expected.json new file mode 100644 index 00000000000..f6ccc926f29 --- /dev/null +++ b/tests/src/test/resources/duplication/DuplicationsTest/sources_lines_duplication-expected.json @@ -0,0 +1,359 @@ +{ + "sources": [ + { + "line": 1, + "code": "package duplicated_lines_within_same_file;", + "duplicated": false + }, + { + "line": 2, + "code": "", + "duplicated": false + }, + { + "line": 3, + "code": "public class DuplicatedLinesInSameFile {", + "duplicated": false + }, + { + "line": 4, + "code": "", + "duplicated": false + }, + { + "line": 5, + "code": " public DuplicatedLinesInSameFile() {", + "duplicated": false + }, + { + "line": 6, + "code": " }", + "duplicated": false + }, + { + "line": 7, + "code": "", + "duplicated": false + }, + { + "line": 8, + "code": " public void duplicatedMethodInSameFile1() {", + "duplicated": false + }, + { + "line": 9, + "code": " String temp = \"\";", + "duplicated": true + }, + { + "line": 10, + "code": " for (int i=0; i<10; i++){", + "duplicated": true + }, + { + "line": 11, + "code": " temp += \"say something\"+i;", + "duplicated": true + }, + { + "line": 12, + "code": " }", + "duplicated": true + }, + { + "line": 13, + "code": " for (int i=0; i<20; i++){", + "duplicated": true + }, + { + "line": 14, + "code": " temp += \"say nothing\"+i;", + "duplicated": true + }, + { + "line": 15, + "code": " }", + "duplicated": true + }, + { + "line": 16, + "code": " for (int i=0; i<30; i++){", + "duplicated": true + }, + { + "line": 17, + "code": " temp += \"always say nothing\"+i;", + "duplicated": true + }, + { + "line": 18, + "code": " }", + "duplicated": true + }, + { + "line": 19, + "code": " for (int i=0; i<40; i++){", + "duplicated": true + }, + { + "line": 20, + "code": " temp += \"really nothing to say \"+i;", + "duplicated": true + }, + { + "line": 21, + "code": " }", + "duplicated": true + }, + { + "line": 22, + "code": " for (int i=0; i<50; i++){", + "duplicated": true + }, + { + "line": 23, + "code": " temp += \"really really nothing to say \"+i;", + "duplicated": true + }, + { + "line": 24, + "code": " }", + "duplicated": true + }, + { + "line": 25, + "code": " for (int i=0; i<60; i++){", + "duplicated": true + }, + { + "line": 26, + "code": " temp += \".. \"+i;", + "duplicated": true + }, + { + "line": 27, + "code": " }", + "duplicated": true + }, + { + "line": 28, + "code": " for (int i=0; i<70; i++){", + "duplicated": true + }, + { + "line": 29, + "code": " temp += \"you say something? \"+i;", + "duplicated": true + }, + { + "line": 30, + "code": " }", + "duplicated": true + }, + { + "line": 31, + "code": " for (int i=0; i<80; i++){", + "duplicated": true + }, + { + "line": 32, + "code": " temp += \"ah no...\"+i;", + "duplicated": true + }, + { + "line": 33, + "code": " }", + "duplicated": true + }, + { + "line": 34, + "code": " for (int i=0; i<90; i++){", + "duplicated": true + }, + { + "line": 35, + "code": " temp += \"bye\"+i;", + "duplicated": true + }, + { + "line": 36, + "code": " }", + "duplicated": true + }, + { + "line": 37, + "code": " }", + "duplicated": true + }, + { + "line": 38, + "code": "", + "duplicated": false + }, + { + "line": 39, + "code": " public void duplicatedMethodInSameFile2() {", + "duplicated": false + }, + { + "line": 40, + "code": " String temp = \"\";", + "duplicated": true + }, + { + "line": 41, + "code": " for (int i=0; i<10; i++){", + "duplicated": true + }, + { + "line": 42, + "code": " temp += \"say something\"+i;", + "duplicated": true + }, + { + "line": 43, + "code": " }", + "duplicated": true + }, + { + "line": 44, + "code": " for (int i=0; i<20; i++){", + "duplicated": true + }, + { + "line": 45, + "code": " temp += \"say nothing\"+i;", + "duplicated": true + }, + { + "line": 46, + "code": " }", + "duplicated": true + }, + { + "line": 47, + "code": " for (int i=0; i<30; i++){", + "duplicated": true + }, + { + "line": 48, + "code": " temp += \"always say nothing\"+i;", + "duplicated": true + }, + { + "line": 49, + "code": " }", + "duplicated": true + }, + { + "line": 50, + "code": " for (int i=0; i<40; i++){", + "duplicated": true + }, + { + "line": 51, + "code": " temp += \"really nothing to say \"+i;", + "duplicated": true + }, + { + "line": 52, + "code": " }", + "duplicated": true + }, + { + "line": 53, + "code": " for (int i=0; i<50; i++){", + "duplicated": true + }, + { + "line": 54, + "code": " temp += \"really really nothing to say \"+i;", + "duplicated": true + }, + { + "line": 55, + "code": " }", + "duplicated": true + }, + { + "line": 56, + "code": " for (int i=0; i<60; i++){", + "duplicated": true + }, + { + "line": 57, + "code": " temp += \".. \"+i;", + "duplicated": true + }, + { + "line": 58, + "code": " }", + "duplicated": true + }, + { + "line": 59, + "code": " for (int i=0; i<70; i++){", + "duplicated": true + }, + { + "line": 60, + "code": " temp += \"you say something? \"+i;", + "duplicated": true + }, + { + "line": 61, + "code": " }", + "duplicated": true + }, + { + "line": 62, + "code": " for (int i=0; i<80; i++){", + "duplicated": true + }, + { + "line": 63, + "code": " temp += \"ah no...\"+i;", + "duplicated": true + }, + { + "line": 64, + "code": " }", + "duplicated": true + }, + { + "line": 65, + "code": " for (int i=0; i<90; i++){", + "duplicated": true + }, + { + "line": 66, + "code": " temp += \"bye\"+i;", + "duplicated": true + }, + { + "line": 67, + "code": " }", + "duplicated": true + }, + { + "line": 68, + "code": " }", + "duplicated": true + }, + { + "line": 69, + "code": "", + "duplicated": true + }, + { + "line": 70, + "code": "}", + "duplicated": true + }, + { + "line": 71, + "code": "", + "duplicated": false + } + ] +} diff --git a/tests/src/test/resources/duplication/xoo-duplication-profile.xml b/tests/src/test/resources/duplication/xoo-duplication-profile.xml new file mode 100644 index 00000000000..999b64a8251 --- /dev/null +++ b/tests/src/test/resources/duplication/xoo-duplication-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>xoo-duplication-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>DuplicatedBlocks</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/exclusions/IssueExclusionsTest/with-many-rules.xml b/tests/src/test/resources/exclusions/IssueExclusionsTest/with-many-rules.xml new file mode 100644 index 00000000000..f3d0baf0616 --- /dev/null +++ b/tests/src/test/resources/exclusions/IssueExclusionsTest/with-many-rules.xml @@ -0,0 +1,32 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/i18n/default-locale-is-english.html b/tests/src/test/resources/i18n/default-locale-is-english.html new file mode 100644 index 00000000000..978ceaa382e --- /dev/null +++ b/tests/src/test/resources/i18n/default-locale-is-english.html @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>default_locale_is_english</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">default_locale_is_english</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/dashboard/index?id=sample&name=Dashboard&locale=foo</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>regexp:.*Jan.*|.*Feb.*|.*Mar.*|.*Apr.*|.*May.*|.*Jun.*|.*Jul.*|.*Aug.*|.*Sep.*|.*Oct.*|.*Nov.*|.*Dec.*</td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*0.0%*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/i18n/french-locale.html b/tests/src/test/resources/i18n/french-locale.html new file mode 100644 index 00000000000..389f9e04ec4 --- /dev/null +++ b/tests/src/test/resources/i18n/french-locale.html @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>french</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/dashboard/index?id=sample&name=Dashboard&locale=fr</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>regexp:.*jan.*|.*fév.*|.*mar.*|.*avr.*|.*mai.*|.*juin.*|.*juil.*|.*août.*|.*sept.*|.*oct.*|.*nov.*|.*déc.*</td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*0,0%*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/i18n/french-pack.html b/tests/src/test/resources/i18n/french-pack.html new file mode 100644 index 00000000000..3f801c9a6ec --- /dev/null +++ b/tests/src/test/resources/i18n/french-pack.html @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>french-pack</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french-pack</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/?locale=fr</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.navbar</td> + <td>glob:*Se connecter*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/i18n/locale-with-france-country.html b/tests/src/test/resources/i18n/locale-with-france-country.html new file mode 100644 index 00000000000..e84051475eb --- /dev/null +++ b/tests/src/test/resources/i18n/locale-with-france-country.html @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>french-france</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french-france</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/dashboard/index?id=sample&name=Dashboard&locale=fr-FR</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>regexp:.*jan.*|.*fév.*|.*mar.*|.*avr.*|.*mai.*|.*juin.*|.*juil.*|.*août.*|.*sept.*|.*oct.*|.*nov.*|.*déc.*</td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*0,0%*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/i18n/locale-with-swiss-country.html b/tests/src/test/resources/i18n/locale-with-swiss-country.html new file mode 100644 index 00000000000..be1453e002f --- /dev/null +++ b/tests/src/test/resources/i18n/locale-with-swiss-country.html @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>french-switzerland</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french-switzerland</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/dashboard/index?id=sample&name=Dashboard&locale=fr-CH</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>regexp:.*jan.*|.*fév.*|.*mar.*|.*avr.*|.*mai.*|.*juin.*|.*juil.*|.*août.*|.*sept.*|.*oct.*|.*nov.*|.*déc.*</td> + </tr> + <tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*0.0%*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/issue/CommonRulesTest/xoo-common-rules-profile.xml b/tests/src/test/resources/issue/CommonRulesTest/xoo-common-rules-profile.xml new file mode 100644 index 00000000000..9803527b94f --- /dev/null +++ b/tests/src/test/resources/issue/CommonRulesTest/xoo-common-rules-profile.xml @@ -0,0 +1,55 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>xoo-common-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>DuplicatedBlocks</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientBranchCoverage</key> + <priority>CRITICAL</priority> + <parameters> + <parameter> + <key>minimumBranchCoverageRatio</key> + <value>90</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientCommentDensity</key> + <priority>CRITICAL</priority> + <parameters> + <parameter> + <key>minimumCommentDensity</key> + <value>90.0</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientLineCoverage</key> + <priority>CRITICAL</priority> + <parameters> + <parameter> + <key>minimumLineCoverageRatio</key> + <value>90</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>FailedUnitTests</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>SkippedUnitTests</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/CustomRulesTest/custom.xml b/tests/src/test/resources/issue/CustomRulesTest/custom.xml new file mode 100644 index 00000000000..b04d126115d --- /dev/null +++ b/tests/src/test/resources/issue/CustomRulesTest/custom.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>Custom</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>MyCustomRule</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/issue/IssueActionTest/xoo-one-issue-per-line-profile.xml b/tests/src/test/resources/issue/IssueActionTest/xoo-one-issue-per-line-profile.xml new file mode 100644 index 00000000000..608f80cae96 --- /dev/null +++ b/tests/src/test/resources/issue/IssueActionTest/xoo-one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>xoo-one-issue-per-line-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueBulkChangeTest/one-issue-per-line-profile.xml b/tests/src/test/resources/issue/IssueBulkChangeTest/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..365aa896a73 --- /dev/null +++ b/tests/src/test/resources/issue/IssueBulkChangeTest/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueChangelogTest/one-issue-per-line-profile.xml b/tests/src/test/resources/issue/IssueChangelogTest/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..365aa896a73 --- /dev/null +++ b/tests/src/test/resources/issue/IssueChangelogTest/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueCreationDateTest/no-rules.xml b/tests/src/test/resources/issue/IssueCreationDateTest/no-rules.xml new file mode 100644 index 00000000000..62ec52d9d10 --- /dev/null +++ b/tests/src/test/resources/issue/IssueCreationDateTest/no-rules.xml @@ -0,0 +1,6 @@ +<profile> + <name>creation-date-quality-profile</name> + <language>xoo</language> + <rules> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueCreationDateTest/one-rule.xml b/tests/src/test/resources/issue/IssueCreationDateTest/one-rule.xml new file mode 100644 index 00000000000..c1d7d44f02c --- /dev/null +++ b/tests/src/test/resources/issue/IssueCreationDateTest/one-rule.xml @@ -0,0 +1,11 @@ +<profile> + <name>creation-date-quality-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueCreationTest/override-profile-severity.xml b/tests/src/test/resources/issue/IssueCreationTest/override-profile-severity.xml new file mode 100644 index 00000000000..d048760be60 --- /dev/null +++ b/tests/src/test/resources/issue/IssueCreationTest/override-profile-severity.xml @@ -0,0 +1,11 @@ +<profile> + <name>override-profile-severity</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneBlockerIssuePerFile</key> + <priority>INFO</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueCreationTest/with-custom-message.xml b/tests/src/test/resources/issue/IssueCreationTest/with-custom-message.xml new file mode 100644 index 00000000000..323e50985cd --- /dev/null +++ b/tests/src/test/resources/issue/IssueCreationTest/with-custom-message.xml @@ -0,0 +1,11 @@ +<profile> + <name>with-custom-message</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>CustomMessage</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueFilterExtensionTest/xoo-with-many-rules.xml b/tests/src/test/resources/issue/IssueFilterExtensionTest/xoo-with-many-rules.xml new file mode 100644 index 00000000000..a08c9cdd246 --- /dev/null +++ b/tests/src/test/resources/issue/IssueFilterExtensionTest/xoo-with-many-rules.xml @@ -0,0 +1,32 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueFilterOnCommonRulesTest/xoo-common-rules-profile.xml b/tests/src/test/resources/issue/IssueFilterOnCommonRulesTest/xoo-common-rules-profile.xml new file mode 100644 index 00000000000..9803527b94f --- /dev/null +++ b/tests/src/test/resources/issue/IssueFilterOnCommonRulesTest/xoo-common-rules-profile.xml @@ -0,0 +1,55 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>xoo-common-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>DuplicatedBlocks</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientBranchCoverage</key> + <priority>CRITICAL</priority> + <parameters> + <parameter> + <key>minimumBranchCoverageRatio</key> + <value>90</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientCommentDensity</key> + <priority>CRITICAL</priority> + <parameters> + <parameter> + <key>minimumCommentDensity</key> + <value>90.0</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientLineCoverage</key> + <priority>CRITICAL</priority> + <parameters> + <parameter> + <key>minimumLineCoverageRatio</key> + <value>90</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>FailedUnitTests</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>SkippedUnitTests</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssueFilterTest/with-many-rules.xml b/tests/src/test/resources/issue/IssueFilterTest/with-many-rules.xml new file mode 100644 index 00000000000..366a3ab7576 --- /dev/null +++ b/tests/src/test/resources/issue/IssueFilterTest/with-many-rules.xml @@ -0,0 +1,43 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientLineCoverage</key> + <priority>BLOCKER</priority> + <parameters> + <parameter> + <key>minimumLineCoverageRatio</key> + <value>90</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/IssuePurgeTest/with-many-rules.xml b/tests/src/test/resources/issue/IssuePurgeTest/with-many-rules.xml new file mode 100644 index 00000000000..f3d0baf0616 --- /dev/null +++ b/tests/src/test/resources/issue/IssuePurgeTest/with-many-rules.xml @@ -0,0 +1,32 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/issue/IssueTrackingTest/one-issue-per-module-profile.xml b/tests/src/test/resources/issue/IssueTrackingTest/one-issue-per-module-profile.xml new file mode 100644 index 00000000000..8b863ce9a46 --- /dev/null +++ b/tests/src/test/resources/issue/IssueTrackingTest/one-issue-per-module-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-module</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/issue/IssueWorkflowTest/xoo-one-issue-per-line-profile.xml b/tests/src/test/resources/issue/IssueWorkflowTest/xoo-one-issue-per-line-profile.xml new file mode 100644 index 00000000000..608f80cae96 --- /dev/null +++ b/tests/src/test/resources/issue/IssueWorkflowTest/xoo-one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>xoo-one-issue-per-line-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/NewIssuesMeasureTest/profile1.xml b/tests/src/test/resources/issue/NewIssuesMeasureTest/profile1.xml new file mode 100644 index 00000000000..3acc7f6a4ed --- /dev/null +++ b/tests/src/test/resources/issue/NewIssuesMeasureTest/profile1.xml @@ -0,0 +1,27 @@ +<profile> + <name>profile1</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/NewIssuesMeasureTest/profile2.xml b/tests/src/test/resources/issue/NewIssuesMeasureTest/profile2.xml new file mode 100644 index 00000000000..70e0f8cd25a --- /dev/null +++ b/tests/src/test/resources/issue/NewIssuesMeasureTest/profile2.xml @@ -0,0 +1,32 @@ +<profile> + <name>profile2</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/issue-on-tag-foobar.xml b/tests/src/test/resources/issue/issue-on-tag-foobar.xml new file mode 100644 index 00000000000..d49b8f7c6c5 --- /dev/null +++ b/tests/src/test/resources/issue/issue-on-tag-foobar.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>issue-on-tag-foobar</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>MAJOR</priority> + <parameters> + <parameter> + <key>tag</key> + <value>foobar</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/one-issue-per-file-profile.xml b/tests/src/test/resources/issue/one-issue-per-file-profile.xml new file mode 100644 index 00000000000..141921f02e7 --- /dev/null +++ b/tests/src/test/resources/issue/one-issue-per-file-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-file-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/one-issue-per-line-profile.xml b/tests/src/test/resources/issue/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..28cebcc2380 --- /dev/null +++ b/tests/src/test/resources/issue/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-line-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/issue/with-many-rules.xml b/tests/src/test/resources/issue/with-many-rules.xml new file mode 100644 index 00000000000..f97ef70cefd --- /dev/null +++ b/tests/src/test/resources/issue/with-many-rules.xml @@ -0,0 +1,42 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneBugIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneVulnerabilityIssuePerModule</key> + <priority>BLOCKER</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/logback-test.xml b/tests/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..599faa969f0 --- /dev/null +++ b/tests/src/test/resources/logback-test.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<configuration debug="false"> + <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> + + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern> + %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n + </pattern> + </encoder> + </appender> + + <root> + <!-- Don't set to DEBUG or it will show the dev licenses into the console!!!! --> + <level value="INFO"/> + <appender-ref ref="CONSOLE"/> + </root> + +</configuration> diff --git a/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html b/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html new file mode 100644 index 00000000000..ac440b184aa --- /dev/null +++ b/tests/src/test/resources/measure/ProjectDashboardTest/test_project_overview_after_first_analysis.html @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>test_project_overview_after_first_analysis</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">test_project_overview_after_first_analysis</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/dashboard?id=project-for-overview</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Quality Gate*Passed*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=overview-code-smells</td> + <td>*0*A*Debt*0*Code Smells*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*0.0%*Duplications*0*Duplicated Blocks*</td> + </tr> + <tr> + <td>assertNotText</td> + <td>id=content</td> + <td>*Coverage*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Quality Gate*SonarQube way*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Quality Profiles*Xoo*Basic*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html b/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html new file mode 100644 index 00000000000..4c5a77a9545 --- /dev/null +++ b/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_display_measures_page.html @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_display_measure_drilldown</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/component_measures?id=project-measures-page-test-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=measure-code_smells</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>id=measure-code_smells-value</td> + <td>*0*</td> +</tr> +<tr> + <td>assertText</td> + <td>id=measure-new_code_smells-leak</td> + <td>*0*</td> +</tr> +<tr> + <td>click</td> + <td>css=#measure-code_smells > a</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Code Smells*0*</td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*src/main/xoo/sample*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html b/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html new file mode 100644 index 00000000000..e68d8b87dfb --- /dev/null +++ b/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_list_view.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_drilldown_on_list_view</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/component_measures/metric/ncloc/list?id=project-measures-page-test-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td>*src/main/xoo/sample/Sample.xoo*</td> +</tr> +<tr> + <td>assertText</td> + <td>id=component-measures-component-measure-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo-ncloc</td> + <td>*13*</td> +</tr> +<tr> + <td>click</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.source-line-code</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.component-measures-breadcrumbs</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=component-measures-breadcrumb-project-measures-page-test-project</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.component-measures-breadcrumbs</td> + <td>*Sample.xoo*13*</td> +</tr> +<tr> + <td>click</td> + <td>id=component-measures-breadcrumb-project-measures-page-test-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html b/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html new file mode 100644 index 00000000000..489c9d93afc --- /dev/null +++ b/tests/src/test/resources/measure/ProjectMeasuresPageTest/should_drilldown_on_tree_view.html @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_drilldown_on_tree_view</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/component_measures/metric/ncloc/tree?id=project-measures-page-test-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample</td> + <td>*src/main/xoo/sample*</td> +</tr> +<tr> + <td>assertText</td> + <td>id=component-measures-component-measure-project-measures-page-test-project:src/main/xoo/sample-ncloc</td> + <td>*13*</td> +</tr> +<tr> + <td>click</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td>*src/main/xoo/sample/Sample.xoo*</td> +</tr> +<tr> + <td>assertText</td> + <td>id=component-measures-component-measure-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo-ncloc</td> + <td>*13*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=.component-measures-breadcrumbs</td> + <td>*ProjectMeasuresPageTest Project*src/main/xoo/sample*13*</td> +</tr> +<tr> + <td>click</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample/Sample.xoo</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.source-line-code</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.component-measures-breadcrumbs</td> + <td>*ProjectMeasuresPageTest Project*src/main/xoo/sample*13*Sample.xoo*13*</td> +</tr> +<tr> + <td>click</td> + <td>id=component-measures-breadcrumb-project-measures-page-test-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=component-measures-component-link-project-measures-page-test-project:src/main/xoo/sample</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/measure/one-issue-per-file.xml b/tests/src/test/resources/measure/one-issue-per-file.xml new file mode 100644 index 00000000000..7193ebfd779 --- /dev/null +++ b/tests/src/test/resources/measure/one-issue-per-file.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-file</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/measure/one-issue-per-line-profile.xml b/tests/src/test/resources/measure/one-issue-per-line-profile.xml new file mode 100644 index 00000000000..521adc7e06f --- /dev/null +++ b/tests/src/test/resources/measure/one-issue-per-line-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/measure/one-issue-per-line.xml b/tests/src/test/resources/measure/one-issue-per-line.xml new file mode 100644 index 00000000000..365aa896a73 --- /dev/null +++ b/tests/src/test/resources/measure/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/one-xoo-issue-per-line.xml b/tests/src/test/resources/one-xoo-issue-per-line.xml new file mode 100644 index 00000000000..b2f49460d34 --- /dev/null +++ b/tests/src/test/resources/one-xoo-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?><!-- Generated by Sonar --> +<profile> + <name>one-xoo-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MAJOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/organization/IssueAssignTest/one-issue-per-file-profile.xml b/tests/src/test/resources/organization/IssueAssignTest/one-issue-per-file-profile.xml new file mode 100644 index 00000000000..141921f02e7 --- /dev/null +++ b/tests/src/test/resources/organization/IssueAssignTest/one-issue-per-file-profile.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?><!-- Generated by Sonar --> +<profile> + <name>one-issue-per-file-profile</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_compare.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_compare.html new file mode 100644 index 00000000000..b8ea98d1690 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_compare.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_changelog</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_changelog</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-name a[href^="/organizations/test-org/quality_profiles/show?key=xoo-sample"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profiles-table-name a[href^="/organizations/test-org/quality_profiles/show?key=xoo-sample"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-compare</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-profile-comparison .Select</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-profile-comparison .Select-control</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_copy.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_copy.html new file mode 100644 index 00000000000..1ed2b8a9018 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_copy.html @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin2</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin2</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-copy</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=#copy-profile-name</td> + <td>copied</td> +</tr> +<tr> + <td>click</td> + <td>css=#copy-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*copied*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-rules</td> + <td>*1*Bugs*0*Vulnerabilities*0*Code Smells*1*</td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="copied"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_create.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_create.html new file mode 100644 index 00000000000..a6716b8c06a --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_create.html @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin2</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin2</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#quality-profiles-create</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profiles-create</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#create-profile-name</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=#create-profile-form-backup-XooProfileImporter</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=#create-profile-name</td> + <td>test</td> +</tr> +<tr> + <td>click</td> + <td>css=#create-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*test*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-rules</td> + <td>*Bugs*0*Vulnerabilities*0*Code Smells*0*</td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-row[data-name="test"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_delete.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_delete.html new file mode 100644 index 00000000000..9610b0db66c --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_delete.html @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin2</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin2</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-delete</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#delete-profile-submit</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#delete-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementNotPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"]</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_changelog.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_changelog.html new file mode 100644 index 00000000000..12536be7cbf --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_changelog.html @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_changelog</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_changelog</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=a[href^="/organizations/test-org/quality_profiles/changelog"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=a[href^="/organizations/test-org/quality_profiles/changelog"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-profile-changelog-event</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-profile-changelog-event</td> + <td>*System*Activated*Has Tag*Major*tag*xoo*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_list.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_list.html new file mode 100644 index 00000000000..4d9242c77de --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_list.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_display_list</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_list</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name</td> + <td>*Basic*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-projects</td> + <td>*Default*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules</td> + <td>*1*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href^="/organizations/test-org/rules#qprofile"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo2"] tr[data-name="Basic"] .quality-profiles-table-name</td> + <td>*Basic*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_exporters.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_exporters.html new file mode 100644 index 00000000000..b26d162f2e7 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_exporters.html @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_projects.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_projects.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-exporters</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-exporters [data-key="XooFakeExporter"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-exporters a[href^="/api/qualityprofiles/export"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_inheritance.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_inheritance.html new file mode 100644 index 00000000000..c403fbf5196 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_inheritance.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_inheritance.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_inheritance.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-inheritance</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-inheritance-ancestor</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-inheritance-ancestor</td> + <td>*Basic*1*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.js-inheritance-current</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-inheritance-current</td> + <td>*sample*1*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_projects.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_projects.html new file mode 100644 index 00000000000..062014eb239 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_projects.html @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_projects.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_projects.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-projects</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-profile-project</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-profile-project</td> + <td>*Sample*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_rules.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_rules.html new file mode 100644 index 00000000000..6b497fc7b58 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_display_profile_rules.html @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_rules.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_rules.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-rules</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-rules</td> + <td>*Active*1*Bugs*0*Vulnerabilities*0*Code Smells*1*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_filter_by_language.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_filter_by_language.html new file mode 100644 index 00000000000..a913a70a34f --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_filter_by_language.html @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_filter_by_language</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_filter_by_language</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-language-filter</td> + <td>*All*</td> +</tr> +<tr> + <td>click</td> + <td>css=.js-language-filter</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-language-filter-option[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-language-filter-option[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementNotPresent</td> + <td>css=.quality-profiles-table[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-language-filter</td> + <td>*Xoo2*</td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles?language=xoo2</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.quality-profiles-table[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-language-filter</td> + <td>*Xoo2*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_open_from_list.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_open_from_list.html new file mode 100644 index 00000000000..2e753ba1cd7 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_open_from_list.html @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_open_from_list</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_open_from_list</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-rules</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-inheritance</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-projects</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_rename.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_rename.html new file mode 100644 index 00000000000..5da4d40ca0c --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_rename.html @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin2</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin2</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-rename</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#rename-profile-name</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=#rename-profile-name</td> + <td>new name</td> +</tr> +<tr> + <td>click</td> + <td>css=#rename-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*new name*</td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="new name"] .quality-profiles-table-name a</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_restore.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_restore.html new file mode 100644 index 00000000000..dd2d21ba31f --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_restore.html @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin2</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin2</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-more-admin-actions</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-more-admin-actions</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profiles-restore</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#restore-profile-backup</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#restore-profile-submit</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_set_default.html b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_set_default.html new file mode 100644 index 00000000000..c267bf702e3 --- /dev/null +++ b/tests/src/test/resources/organization/OrganizationQualityProfilesUiTest/should_set_default.html @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin2</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin2</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-set-as-default</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-projects</td> + <td>*Default*</td> +</tr> +<tr> + <td>open</td> + <td>/organizations/test-org/quality_profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-row[data-name="sample"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.quality-profiles-table-row[data-name="sample"] .quality-profiles-table-projects</td> + <td>*Default*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectAdministration/BackgroundTasksTest/should_not_display_failing_and_search_and_filter_elements_on_project_level_page.html b/tests/src/test/resources/projectAdministration/BackgroundTasksTest/should_not_display_failing_and_search_and_filter_elements_on_project_level_page.html new file mode 100644 index 00000000000..e28cdbba827 --- /dev/null +++ b/tests/src/test/resources/projectAdministration/BackgroundTasksTest/should_not_display_failing_and_search_and_filter_elements_on_project_level_page.html @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_not_display_failing_and_search_and_filter_elements_on_project_level_page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">should_not_display_failing_and_search_and_filter_elements_on_project_level_page</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>click</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/project/background_tasks?id=test-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.background-tasks .badge</td> + <td></td> +</tr> +<tr> + <td>assertNotText</td> + <td>css=.background-tasks</td> + <td>*Another Test Project*</td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.js-search</td> + <td></td> +</tr> +<tr> + <td>assertNotText</td> + <td>css=#content</td> + <td>*still failing*</td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.icon-filter</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectAdministration/BulkDeletionTest/bulk-delete-filter-projects.html b/tests/src/test/resources/projectAdministration/BulkDeletionTest/bulk-delete-filter-projects.html new file mode 100644 index 00000000000..b6256e49b35 --- /dev/null +++ b/tests/src/test/resources/projectAdministration/BulkDeletionTest/bulk-delete-filter-projects.html @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>bulk-delete-filter-projects</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/projects_admin</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Bar-Sonar-Plugin*Foo-Application*Sample-Project*</td> +</tr> +<tr> + <td>type</td> + <td>css=.search-box-input</td> + <td>s</td> +</tr> +<tr> + <td>click</td> + <td>css=.search-box-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Bar-Sonar-Plugin*Sample-Project*</td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*cameleon-3*cameleon-1*</td> +</tr> +<tr> + <td>assertTextNotPresent</td> + <td>content</td> + <td>*Foo-Application*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-delete-version.html b/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-delete-version.html new file mode 100644 index 00000000000..496917b9e4c --- /dev/null +++ b/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-delete-version.html @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>multimodule-project-delete-version</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">project-modify-versions</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/project/history/com.sonarsource.it.samples:multi-modules-sample</td> + <td></td> + </tr> + <tr> + <td>clickAndWait</td> + <td>link=Remove</td> + <td></td> + </tr> + <tr> + <td>assertConfirmation</td> + <td>Are you sure you want to remove 'RELEASE' from this snapshot?</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>infomsg</td> + <td>glob:*Version 'RELEASE' was removed from current project*</td> + </tr> + <tr> + <td>assertNotText</td> + <td>version_1</td> + <td>glob:*RELEASE*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-modify-version.html b/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-modify-version.html new file mode 100644 index 00000000000..f336f7e93b9 --- /dev/null +++ b/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-administration/multimodule-project-modify-version.html @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>multimodule-project-modify-version</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">project-modify-versions</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>click</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/project/history/com.sonarsource.it.samples:multi-modules-sample</td> + <td></td> + </tr> + <tr> + <td>assertElementNotPresent</td> + <td>link=Remove</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>version_1_change</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>version_name_1</td> + <td>RELEASE</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>save_version_1</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>infomsg</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>infomsg</td> + <td>Version 'RELEASE' was created for current project.</td> + </tr> + <tr> + <td>waitForText</td> + <td>version_1</td> + <td>glob:*RELEASE*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html b/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html new file mode 100644 index 00000000000..9345f6e3f75 --- /dev/null +++ b/tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>project-deletion</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">project-deletion</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>project-deletion-with-admin-permission-on-project</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>password</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/project/deletion?id=sample</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#delete-project</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#delete-project</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#delete-project-confirm</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#delete-project-confirm</td> + <td></td> +</tr> +<tr> + <td>waitForElementNotPresent</td> + <td>css=#delete-project-confirm</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/dashboard?id=sample</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.process-spinner-failed</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectAdministration/ProjectPermissionsTest/test_project_permissions_page_shows_only_single_project.html b/tests/src/test/resources/projectAdministration/ProjectPermissionsTest/test_project_permissions_page_shows_only_single_project.html new file mode 100644 index 00000000000..cbb6595ff20 --- /dev/null +++ b/tests/src/test/resources/projectAdministration/ProjectPermissionsTest/test_project_permissions_page_shows_only_single_project.html @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>test_project_overview_after_first_analysis</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">test_project_overview_after_first_analysis</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/project_roles?id=project-permissions-project</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#projects</td> + <td></td> + </tr> + <tr> + <td>assertNotText</td> + <td>css=#projects</td> + <td>*Another Test Project*</td> + </tr> + <tr> + <td>assertElementNotPresent</td> + <td>css=footer</td> + <td></td> + </tr> + <tr> + <td>assertElementNotPresent</td> + <td>css=.search-box</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-add-metrics.html b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-add-metrics.html new file mode 100644 index 00000000000..ab7758bd71a --- /dev/null +++ b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-add-metrics.html @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-add-metrics</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/comparison/index</td> + <td></td> + </tr> + <tr> + <td>assertNotText</td> + <td>comparison-page</td> + <td>*Major issues*</td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>new_metric</td> + <td></td> + </tr> + <tr> + <td>typeAndWait</td> + <td>new_metric</td> + <td>major_violations</td> + </tr> + <tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Lines of Code*Complexity*Comments (%)*Duplicated Lines (%)*Issues*Coverage*Major Issues*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-add-projects.html b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-add-projects.html new file mode 100644 index 00000000000..18c882331ad --- /dev/null +++ b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-add-projects.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-add-projects</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/comparison/index</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>new_resource</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>new_resource</td> + <td>project-comparison-test-project</td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>//option[text()='1.0-SNAPSHOT']</td> + <td></td> +</tr> +<tr> + <td>selectAndWait</td> + <td>new_version</td> + <td>1.0-SNAPSHOT</td> +</tr> +<tr> + <td>waitForText</td> + <td>id=content</td> + <td>*ProjectComparisonTest Project*1.0-SNAPSHOT*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-display-basic-set-of-metrics.html b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-display-basic-set-of-metrics.html new file mode 100644 index 00000000000..3a8e1256198 --- /dev/null +++ b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-display-basic-set-of-metrics.html @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-display-basic-set-of-metrics</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/comparison/index</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Lines of Code*Complexity*Comments (%)*Duplicated Lines (%)*Issues*Coverage*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-move-and-remove-metrics.html b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-move-and-remove-metrics.html new file mode 100644 index 00000000000..3b89b4af128 --- /dev/null +++ b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-move-and-remove-metrics.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-move-and-remove-metrics</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/comparison/index</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Lines of Code*Complexity*Comments (%)*Duplicated Lines (%)*Issues*Coverage*</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>down-0</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Complexity*Lines of Code*Comments (%)*Duplicated Lines (%)*Issues*Coverage*</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>up-5</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Complexity*Lines of Code*Comments (%)*Duplicated Lines (%)*Coverage*Issues*</td> +</tr> +<tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*%*</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>del-m-2</td> + <td></td> +</tr> +<tr> + <td>clickAndWait</td> + <td>del-m-2</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Complexity*Lines of Code*Coverage*Issues*</td> +</tr> +<tr> + <td>assertNotText</td> + <td>comparison-page</td> + <td>*%*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-move-and-remove-projects.html b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-move-and-remove-projects.html new file mode 100644 index 00000000000..f7d71dbfd2f --- /dev/null +++ b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-move-and-remove-projects.html @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-move-and-remove-projects</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/comparison/index</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>new_resource</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>new_resource</td> + <td>project-comparison-test-project</td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>//option[text()='1.0-SNAPSHOT']</td> + <td></td> +</tr> +<tr> + <td>selectAndWait</td> + <td>new_version</td> + <td>1.0-SNAPSHOT</td> +</tr> +<tr> + <td>waitForText</td> + <td>id=content</td> + <td>*ProjectComparisonTest Project*1.0-SNAPSHOT*</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>del-r-0</td> + <td></td> +</tr> +<tr> + <td>assertNotText</td> + <td>id=content</td> + <td>*Sample*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-not-add-differential-metrics.html b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-not-add-differential-metrics.html new file mode 100644 index 00000000000..ff433b66005 --- /dev/null +++ b/tests/src/test/resources/projectComparison/ProjectComparisonTest/should-not-add-differential-metrics.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-not-add-differential-metrics</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/comparison/index</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Lines of Code*Complexity*Comments (%)*Duplicated Lines (%)*Issues*Coverage*</td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>new_metric</td> + <td></td> + </tr> + <tr> + <td>typeAndWait</td> + <td>new_metric</td> + <td>new_violations</td> + </tr> + <tr> + <td>waitForText</td> + <td>comparison-page</td> + <td>*Lines of Code*Complexity*Comments (%)*Duplicated Lines (%)*Issues*Coverage*</td> + </tr> + <tr> + <td>assertNotText</td> + <td>comparison-page</td> + <td>*New Issues*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/projectSearch/SearchProjectsTest/with-many-rules.xml b/tests/src/test/resources/projectSearch/SearchProjectsTest/with-many-rules.xml new file mode 100644 index 00000000000..366a3ab7576 --- /dev/null +++ b/tests/src/test/resources/projectSearch/SearchProjectsTest/with-many-rules.xml @@ -0,0 +1,43 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>common-xoo</repositoryKey> + <key>InsufficientLineCoverage</key> + <priority>BLOCKER</priority> + <parameters> + <parameter> + <key>minimumLineCoverageRatio</key> + <value>90</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityGate/QualityGateOnRatingMeasuresTest/with-many-rules.xml b/tests/src/test/resources/qualityGate/QualityGateOnRatingMeasuresTest/with-many-rules.xml new file mode 100644 index 00000000000..6668eff9096 --- /dev/null +++ b/tests/src/test/resources/qualityGate/QualityGateOnRatingMeasuresTest/with-many-rules.xml @@ -0,0 +1,42 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneBugIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneVulnerabilityIssuePerModule</key> + <priority>BLOCKER</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityGate/QualityGateUiTest/should-display-alerts-correctly-history-page.html b/tests/src/test/resources/qualityGate/QualityGateUiTest/should-display-alerts-correctly-history-page.html new file mode 100644 index 00000000000..951ae58d38a --- /dev/null +++ b/tests/src/test/resources/qualityGate/QualityGateUiTest/should-display-alerts-correctly-history-page.html @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2016 SonarSource + ~ mailto:contact AT sonarsource DOT com + ~ + ~ SonarQube 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. + ~ + ~ SonarQube 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 this program; if not, write to the Free Software Foundation, + ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should-display-alerts-correctly-history-page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">should-display-alerts-correctly-history-page</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/dashboard/index/sample</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=#context-navigation .navbar-admin-link</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>link=History</td> + <td></td> + </tr> + <tr> + <td>clickAndWait</td> + <td>link=History</td> + <td></td> + </tr> + <tr> + <td>assertElementPresent</td> + <td>//img[@title='Quality Gate Status: Green (was Orange). ']</td> + <td></td> + </tr> + <tr> + <td>assertElementPresent</td> + <td>//img[@title='Quality Gate Status: Orange. Lines > 5']</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html b/tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html new file mode 100644 index 00000000000..b0975df675d --- /dev/null +++ b/tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2016 SonarSource + ~ mailto:contact AT sonarsource DOT com + ~ + ~ SonarQube 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. + ~ + ~ SonarQube 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 this program; if not, write to the Free Software Foundation, + ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_display_quality_gates_page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">should_display_quality_gates_page</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/quality_gates</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-gates-results a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-gates-results a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=quality-gate-conditions</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityGate/notifications/activate_notification_channels.html b/tests/src/test/resources/qualityGate/notifications/activate_notification_channels.html new file mode 100644 index 00000000000..40de82c178a --- /dev/null +++ b/tests/src/test/resources/qualityGate/notifications/activate_notification_channels.html @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>reate_user_with_email</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">create_user_with_email</td> + </tr> + </thead> + <tbody> +<tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>tester</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>tester</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sonar/account/notifications</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>id=global_notifs_NewAlerts_EmailNotificationChannel</td> + <td></td> +</tr> +<tr> + <td>check</td> + <td>id=global_notifs_NewAlerts_EmailNotificationChannel</td> + <td></td> +</tr> +<tr> + <td>clickAndWait</td> + <td>id=submit-notifications</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityGate/notifications/email_configuration.html b/tests/src/test/resources/qualityGate/notifications/email_configuration.html new file mode 100644 index 00000000000..7b43cf8ee03 --- /dev/null +++ b/tests/src/test/resources/qualityGate/notifications/email_configuration.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>email_configuration</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>setTimeout</td> + <td>300000</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/email_configuration</td> + <td></td> + </tr> + <tr> + <td>assertValue</td> + <td>smtp_host</td> + <td>localhost</td> + </tr> + <tr> + <td>type</td> + <td>to_address</td> + <td>test@example.org</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>submit_test</td> + <td></td> + </tr> + <tr> + <td>waitForVisible</td> + <td>info</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityModel/has-hello-tag.xml b/tests/src/test/resources/qualityModel/has-hello-tag.xml new file mode 100644 index 00000000000..88cf4f9273e --- /dev/null +++ b/tests/src/test/resources/qualityModel/has-hello-tag.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>has-tag</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>hello</value> + </parameter> + </parameters> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityModel/one-day-debt-per-file.xml b/tests/src/test/resources/qualityModel/one-day-debt-per-file.xml new file mode 100644 index 00000000000..cdebd7554c9 --- /dev/null +++ b/tests/src/test/resources/qualityModel/one-day-debt-per-file.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-day-debt-per-file</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneDayDebtPerFile</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityModel/one-issue-per-file.xml b/tests/src/test/resources/qualityModel/one-issue-per-file.xml new file mode 100644 index 00000000000..7193ebfd779 --- /dev/null +++ b/tests/src/test/resources/qualityModel/one-issue-per-file.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-file</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityModel/one-issue-per-line.xml b/tests/src/test/resources/qualityModel/one-issue-per-line.xml new file mode 100644 index 00000000000..521adc7e06f --- /dev/null +++ b/tests/src/test/resources/qualityModel/one-issue-per-line.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<profile> + <name>one-issue-per-line</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + </rules> +</profile>
\ No newline at end of file diff --git a/tests/src/test/resources/qualityModel/with-many-rules.xml b/tests/src/test/resources/qualityModel/with-many-rules.xml new file mode 100644 index 00000000000..6668eff9096 --- /dev/null +++ b/tests/src/test/resources/qualityModel/with-many-rules.xml @@ -0,0 +1,42 @@ +<profile> + <name>with-many-rules</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>HasTag</key> + <priority>INFO</priority> + <parameters> + <parameter> + <key>tag</key> + <value>xoo</value> + </parameter> + </parameters> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneBugIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneVulnerabilityIssuePerModule</key> + <priority>BLOCKER</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityModel/without-type-bug.xml b/tests/src/test/resources/qualityModel/without-type-bug.xml new file mode 100644 index 00000000000..89c92cf10fa --- /dev/null +++ b/tests/src/test/resources/qualityModel/without-type-bug.xml @@ -0,0 +1,26 @@ +<profile> + <name>without-type-bug</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneVulnerabilityIssuePerModule</key> + <priority>BLOCKER</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityModel/without-type-code-smells.xml b/tests/src/test/resources/qualityModel/without-type-code-smells.xml new file mode 100644 index 00000000000..4bbb6f88ed7 --- /dev/null +++ b/tests/src/test/resources/qualityModel/without-type-code-smells.xml @@ -0,0 +1,16 @@ +<profile> + <name>without-type-code-smells</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneBugIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneVulnerabilityIssuePerModule</key> + <priority>BLOCKER</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityModel/without-type-vulnerability.xml b/tests/src/test/resources/qualityModel/without-type-vulnerability.xml new file mode 100644 index 00000000000..c5fb823bd7c --- /dev/null +++ b/tests/src/test/resources/qualityModel/without-type-vulnerability.xml @@ -0,0 +1,26 @@ +<profile> + <name>without-type-vulnerability</name> + <language>xoo</language> + <rules> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerLine</key> + <priority>MINOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerFile</key> + <priority>MAJOR</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneIssuePerModule</key> + <priority>CRITICAL</priority> + </rule> + <rule> + <repositoryKey>xoo</repositoryKey> + <key>OneBugIssuePerLine</key> + <priority>CRITICAL</priority> + </rule> + </rules> +</profile> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_compare.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_compare.html new file mode 100644 index 00000000000..c2339d690f6 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_compare.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_changelog</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_changelog</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-compare</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-profile-comparison .Select</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-profile-comparison .Select-control</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_copy.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_copy.html new file mode 100644 index 00000000000..d2f0a12a8b3 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_copy.html @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-copy</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=#copy-profile-name</td> + <td>copied</td> +</tr> +<tr> + <td>click</td> + <td>css=#copy-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*copied*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-rules</td> + <td>*1*Bugs*0*Vulnerabilities*0*Code Smells*1*</td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="copied"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_create.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_create.html new file mode 100644 index 00000000000..7fefabfbd4a --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_create.html @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#quality-profiles-create</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profiles-create</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#create-profile-name</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=#create-profile-form-backup-XooProfileImporter</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=#create-profile-name</td> + <td>test</td> +</tr> +<tr> + <td>click</td> + <td>css=#create-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*test*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-rules</td> + <td>*Bugs*0*Vulnerabilities*0*Code Smells*0*</td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-row[data-name="test"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_delete.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_delete.html new file mode 100644 index 00000000000..85e85c0391c --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_delete.html @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-delete</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#delete-profile-submit</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#delete-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementNotPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"]</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_changelog.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_changelog.html new file mode 100644 index 00000000000..8614c2fdab9 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_changelog.html @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_changelog</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_changelog</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=a[href^="/profiles/changelog"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=a[href^="/profiles/changelog"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-profile-changelog-event</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-profile-changelog-event</td> + <td>*System*Activated*Has Tag*Major*tag*xoo*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_list.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_list.html new file mode 100644 index 00000000000..66f83c9af47 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_list.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_display_list</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_list</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name</td> + <td>*Basic*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-projects</td> + <td>*Default*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules</td> + <td>*1*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-rules a[href^="/coding_rules#qprofile"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=table[data-language="xoo2"] tr[data-name="Basic"] .quality-profiles-table-name</td> + <td>*Basic*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_exporters.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_exporters.html new file mode 100644 index 00000000000..ea8b1d321f1 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_exporters.html @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_projects.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_projects.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-exporters</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-exporters [data-key="XooFakeExporter"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-exporters a[href^="/api/qualityprofiles/export"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_inheritance.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_inheritance.html new file mode 100644 index 00000000000..501ae5b5df8 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_inheritance.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_inheritance.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_inheritance.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-inheritance</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-inheritance-ancestor</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-inheritance-ancestor</td> + <td>*Basic*1*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.js-inheritance-current</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-inheritance-current</td> + <td>*sample*1*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_projects.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_projects.html new file mode 100644 index 00000000000..ff38495e318 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_projects.html @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_projects.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_projects.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-projects</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-profile-project</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-profile-project</td> + <td>*Sample*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_rules.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_rules.html new file mode 100644 index 00000000000..eb5ccf3194b --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_display_profile_rules.html @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_profile_rules.html</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_display_profile_rules.html</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-rules</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-rules</td> + <td>*Active*1*Bugs*0*Vulnerabilities*0*Code Smells*1*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_filter_by_language.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_filter_by_language.html new file mode 100644 index 00000000000..cfe5321b816 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_filter_by_language.html @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_filter_by_language</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_filter_by_language</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-language-filter</td> + <td>*All*</td> +</tr> +<tr> + <td>click</td> + <td>css=.js-language-filter</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-language-filter-option[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-language-filter-option[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>waitForElementNotPresent</td> + <td>css=.quality-profiles-table[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-language-filter</td> + <td>*Xoo2*</td> +</tr> +<tr> + <td>open</td> + <td>/profiles?language=xoo2</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table .data[data-language="xoo2"]</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.quality-profiles-table[data-language="xoo"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.js-language-filter</td> + <td>*Xoo2*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_open_from_list.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_open_from_list.html new file mode 100644 index 00000000000..de19c00a017 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_open_from_list.html @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>should_open_from_list</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_open_from_list</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="Basic"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-rules</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-inheritance</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-projects</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_rename.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_rename.html new file mode 100644 index 00000000000..dfa3138f8b1 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_rename.html @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">should_create</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-rename</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#rename-profile-name</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=#rename-profile-name</td> + <td>new name</td> +</tr> +<tr> + <td>click</td> + <td>css=#rename-profile-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-header</td> + <td>*new name*</td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="new name"] .quality-profiles-table-name a</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_restore.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_restore.html new file mode 100644 index 00000000000..8def7727001 --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_restore.html @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-more-admin-actions</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-more-admin-actions</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profiles-restore</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#restore-profile-backup</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#restore-profile-submit</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_set_default.html b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_set_default.html new file mode 100644 index 00000000000..6306820ba6a --- /dev/null +++ b/tests/src/test/resources/qualityProfile/QualityProfilesUiTest/should_set_default.html @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_create</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<tbody> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=table[data-language="xoo"] tr[data-name="sample"] .quality-profiles-table-name a</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.quality-profile-header .dropdown-toggle</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=#quality-profile-set-as-default</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.quality-profile-projects</td> + <td>*Default*</td> +</tr> +<tr> + <td>open</td> + <td>/profiles</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.quality-profiles-table-row[data-name="sample"]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.quality-profiles-table-row[data-name="sample"] .quality-profiles-table-projects</td> + <td>*Default*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/serverSystem/HttpsTest/keystore.jks b/tests/src/test/resources/serverSystem/HttpsTest/keystore.jks Binary files differnew file mode 100644 index 00000000000..13234b49dc2 --- /dev/null +++ b/tests/src/test/resources/serverSystem/HttpsTest/keystore.jks diff --git a/tests/src/test/resources/serverSystem/HttpsTest/keystore.jks.txt b/tests/src/test/resources/serverSystem/HttpsTest/keystore.jks.txt new file mode 100644 index 00000000000..ce4a4adfc39 --- /dev/null +++ b/tests/src/test/resources/serverSystem/HttpsTest/keystore.jks.txt @@ -0,0 +1,3 @@ +keytool -genkey -alias tests -keyalg RSA -keystore keystore.jks +keystore password: thepassword +key password for <tests>: thetests
\ No newline at end of file diff --git a/tests/src/test/resources/serverSystem/ServerSystemTest/derby-warning.html b/tests/src/test/resources/serverSystem/ServerSystemTest/derby-warning.html new file mode 100644 index 00000000000..c2e13c56aad --- /dev/null +++ b/tests/src/test/resources/serverSystem/ServerSystemTest/derby-warning.html @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>derby-warning</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">derby-warning</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/</td> + <td></td> + </tr> + <tr> + <td>assertElementPresent</td> + <td>evaluation_warning</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>footer</td> + <td>glob:*evaluation*</td> + </tr> + + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/serverSystem/ServerSystemTest/hide-jdbc-settings.html b/tests/src/test/resources/serverSystem/ServerSystemTest/hide-jdbc-settings.html new file mode 100644 index 00000000000..311d000f9cd --- /dev/null +++ b/tests/src/test/resources/serverSystem/ServerSystemTest/hide-jdbc-settings.html @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2016 SonarSource + ~ mailto:contact AT sonarsource DOT com + ~ + ~ SonarQube 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. + ~ + ~ SonarQube 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 this program; if not, write to the Free Software Foundation, + ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>hide-jdbc-settings</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">hide-jdbc-settings</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/setup/index</td> + <td></td> + </tr> + <tr> + <td>assertTextNotPresent</td> + <td>jdbc:</td> + <td></td> + </tr> + + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/serverSystem/ServerSystemTest/incompatible-plugin-1.0.jar b/tests/src/test/resources/serverSystem/ServerSystemTest/incompatible-plugin-1.0.jar Binary files differnew file mode 100644 index 00000000000..541131c5093 --- /dev/null +++ b/tests/src/test/resources/serverSystem/ServerSystemTest/incompatible-plugin-1.0.jar diff --git a/tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html b/tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html new file mode 100644 index 00000000000..e304586c59c --- /dev/null +++ b/tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>system_info</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">system_info</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/system/index</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Official Distribution*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Database Version*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Pool Active Connections*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Start Time*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*Processors*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*java.class.path*java.specification.version*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/serverSystem/ServerSystemTest/url_ending_by_jsp.html b/tests/src/test/resources/serverSystem/ServerSystemTest/url_ending_by_jsp.html new file mode 100644 index 00000000000..12056550fc2 --- /dev/null +++ b/tests/src/test/resources/serverSystem/ServerSystemTest/url_ending_by_jsp.html @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2016 SonarSource + ~ mailto:contact AT sonarsource DOT com + ~ + ~ SonarQube 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. + ~ + ~ SonarQube 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 this program; if not, write to the Free Software Foundation, + ~ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/dashboard/index/myproject.jsp</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=#content</td> + <td></td> + </tr> +</table> +</body> +</html> diff --git a/tests/src/test/resources/settings/SettingsTest/sonar-secret.txt b/tests/src/test/resources/settings/SettingsTest/sonar-secret.txt new file mode 100644 index 00000000000..65b98c522da --- /dev/null +++ b/tests/src/test/resources/settings/SettingsTest/sonar-secret.txt @@ -0,0 +1 @@ +0PZz+G+f8mjr3sPn4+AhHg==
\ No newline at end of file diff --git a/tests/src/test/resources/sourceCode/EncodingTest/japanese_sources.html b/tests/src/test/resources/sourceCode/EncodingTest/japanese_sources.html new file mode 100644 index 00000000000..d873fba3939 --- /dev/null +++ b/tests/src/test/resources/sourceCode/EncodingTest/japanese_sources.html @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>japanese_sources</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">japanese_sources</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=japanese-charset%3Asrc%2Fmain%2Fxoo%2Fcom%2Ftest%2FAppDuplication.xoo</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.source-viewer</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.source-table</td> + <td>glob:*public class AppDuplication*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.source-table</td> + <td>glob:*証明書パス構築に失敗しました。*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/HighlightingTest/symbol-usages-highlighting.html b/tests/src/test/resources/sourceCode/HighlightingTest/symbol-usages-highlighting.html new file mode 100644 index 00000000000..42e1fc154f2 --- /dev/null +++ b/tests/src/test/resources/sourceCode/HighlightingTest/symbol-usages-highlighting.html @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>highlight-symbol-usages</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">highlight-symbol-usages</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=sample-with-highlighting%3Asrc%2Fmain%2Fxoo%2Fsample%2FSample.xoo</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.source-line</td> + <td></td> +</tr> +<tr> + <td>waitForXpathCount</td> + <td>//span[contains(@class, 'sym')]</td> + <td>3</td> +</tr> +<tr> + <td>waitForXpathCount</td> + <td>//span[contains(@class, 'sym highlighted')]</td> + <td>0</td> +</tr> +<tr> + <td>click</td> + <td>//span[contains(@class, 'sym')][2]</td> + <td></td> +</tr> +<tr> + <td>waitForXpathCount</td> + <td>//span[contains(@class, 'sym highlighted')]</td> + <td>2</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting-v1.html b/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting-v1.html new file mode 100644 index 00000000000..7a1e36cf816 --- /dev/null +++ b/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting-v1.html @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>java-syntax-highlighting</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">highlight-syntax-v1</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=sample-with-highlighting%3Asrc%2Fmain%2Fxoo%2Fsample%2FSample.xoo</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.source-line</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">package</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">public</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">class</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <!-- Here class is wrong and will be fixed during next analysis --> + <td>glob:*<span class="s">return</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="s">"hello"</span>*</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting-v2.html b/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting-v2.html new file mode 100644 index 00000000000..7f473dd9e43 --- /dev/null +++ b/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting-v2.html @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>java-syntax-highlighting</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">highlight-syntax-v2</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=sample-with-highlighting%3Asrc%2Fmain%2Fxoo%2Fsample%2FSample.xoo</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.source-line</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">package</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">public</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">class</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">return</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="s">"hello"</span>*</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting.html b/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting.html new file mode 100644 index 00000000000..53e4602a978 --- /dev/null +++ b/tests/src/test/resources/sourceCode/HighlightingTest/syntax-highlighting.html @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>java-syntax-highlighting</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">highlight-syntax</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/component/index?id=sample-with-highlighting%3Asrc%2Fmain%2Fxoo%2Fsample%2FSample.xoo</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.source-line</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">package</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">public</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">class</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="k">return</span>*</td> + <td></td> + </tr> + <tr> + <td>verifyHtmlSource</td> + <td>glob:*<span class="s">"hello"</span>*</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/ProjectCodeTest/code_page_should_expand_root_dir.html b/tests/src/test/resources/sourceCode/ProjectCodeTest/code_page_should_expand_root_dir.html new file mode 100644 index 00000000000..c9737d5fe25 --- /dev/null +++ b/tests/src/test/resources/sourceCode/ProjectCodeTest/code_page_should_expand_root_dir.html @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>code_page_should_expand_root_dir</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">code_page_should_expand_root_dir</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/code?id=project-for-code-root-dir</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*Hello.xoo*src/main/xoo/sample*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/ProjectCodeTest/permalink.html b/tests/src/test/resources/sourceCode/ProjectCodeTest/permalink.html new file mode 100644 index 00000000000..69364a69079 --- /dev/null +++ b/tests/src/test/resources/sourceCode/ProjectCodeTest/permalink.html @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>test_project_code_page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">test_project_code_page</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/code?id=project-for-code&selected=project-for-code%3Asrc%2Fmain%2Fxoo%2Fsample%2FSample.xoo</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*public class Sample*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.code-breadcrumbs</td> + <td>*Project For Code*src/main/xoo/sample*Sample.xoo*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/ProjectCodeTest/search.html b/tests/src/test/resources/sourceCode/ProjectCodeTest/search.html new file mode 100644 index 00000000000..1594ee28cd5 --- /dev/null +++ b/tests/src/test/resources/sourceCode/ProjectCodeTest/search.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>test_project_code_page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">test_project_code_page</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/code?id=project-for-code</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*Project For Code*13*0*0*0.0%*</td> +</tr> +<tr> + <td>type</td> + <td>css=.search-box-input</td> + <td>xoo</td> +</tr> +<tr> + <td>click</td> + <td>css=.search-box-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*Sample.xoo*</td> +</tr> +<tr> + <td>click</td> + <td>css=.code-name-cell a</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*public class Sample*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.code-breadcrumbs</td> + <td>*Project For Code*src/main/xoo/sample*Sample.xoo*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/sourceCode/ProjectCodeTest/test_project_code_page.html b/tests/src/test/resources/sourceCode/ProjectCodeTest/test_project_code_page.html new file mode 100644 index 00000000000..9c6b163fa44 --- /dev/null +++ b/tests/src/test/resources/sourceCode/ProjectCodeTest/test_project_code_page.html @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>test_project_code_page</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">test_project_code_page</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/code?id=project-for-code</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*Project For Code*13*0*0*0.0%*</td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*src/main/xoo/sample*</td> +</tr> +<tr> + <td>click</td> + <td>css=.code-name-cell a</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=#content</td> + <td>*Sample.xoo*</td> +</tr> +<tr> + <td>click</td> + <td>css=.code-breadcrumbs a</td> + <td></td> +</tr> +<tr> + <td>waitForNotText</td> + <td>css=#content</td> + <td>*Sample.xoo*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/test/CoverageTest/it_coverage-expected.json b/tests/src/test/resources/test/CoverageTest/it_coverage-expected.json new file mode 100644 index 00000000000..521b372b6ea --- /dev/null +++ b/tests/src/test/resources/test/CoverageTest/it_coverage-expected.json @@ -0,0 +1,84 @@ +{ + "sources": [ + { + "line": 1, + "code": "package sample;" + }, + { + "line": 2, + "code": "" + }, + { + "line": 3, + "code": "public class Sample {" + }, + { + "line": 4, + "code": "\t" + }, + { + "line": 5, + "code": "\tpublic Sample(int i) {" + }, + { + "line": 6, + "code": "\t\tint j = i++;", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 7, + "code": "\t}" + }, + { + "line": 8, + "code": "\t" + }, + { + "line": 9, + "code": "\tprivate String myMethod() {" + }, + { + "line": 10, + "code": "\t\tif (foo == bar) {", + "lineHits": 0, + "utLineHits": 0, + "conditions": 2, + "utConditions": 2, + "coveredConditions": 1, + "utCoveredConditions": 1 + }, + { + "line": 11, + "code": "\t\t\treturn \"hello\";", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 12, + "code": "\t\t} else {" + }, + { + "line": 13, + "code": "\t\t\tthrow new IllegalStateException();", + "lineHits": 0, + "utLineHits": 0 + }, + { + "line": 14, + "code": "\t\t}" + }, + { + "line": 15, + "code": "\t}" + }, + { + "line": 16, + "code": "}" + }, + { + "line": 17, + "code": "" + } + ] +} diff --git a/tests/src/test/resources/test/CoverageTest/unit_test_coverage-expected.json b/tests/src/test/resources/test/CoverageTest/unit_test_coverage-expected.json new file mode 100644 index 00000000000..c2f25a610a9 --- /dev/null +++ b/tests/src/test/resources/test/CoverageTest/unit_test_coverage-expected.json @@ -0,0 +1,84 @@ +{ + "sources":[ + { + "line": 1, + "code": "package sample;", + }, + { + "line": 2, + "code": "", + }, + { + "line": 3, + "code": "public class Sample {", + }, + { + "line": 4, + "code": "\t", + }, + { + "line": 5, + "code": "\tpublic Sample(int i) {", + }, + { + "line": 6, + "code": "\t\tint j = i++;", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 7, + "code": "\t}", + }, + { + "line": 8, + "code": "\t", + }, + { + "line": 9, + "code": "\tprivate String myMethod() {", + }, + { + "line": 10, + "code": "\t\tif (foo == bar) {", + "lineHits": 0, + "utLineHits": 0, + "conditions": 2, + "utConditions": 2, + "coveredConditions": 1, + "utCoveredConditions": 1 + }, + { + "line": 11, + "code": "\t\t\treturn \"hello\";", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 12, + "code": "\t\t} else {", + }, + { + "line": 13, + "code": "\t\t\tthrow new IllegalStateException();", + "lineHits": 0, + "utLineHits": 0 + }, + { + "line": 14, + "code": "\t\t}", + }, + { + "line": 15, + "code": "\t}", + }, + { + "line": 16, + "code": "}", + }, + { + "line": 17, + "code": "", + } + ] +} diff --git a/tests/src/test/resources/test/CoverageTest/unit_test_coverage_no_condition-expected.json b/tests/src/test/resources/test/CoverageTest/unit_test_coverage_no_condition-expected.json new file mode 100644 index 00000000000..aabf73102ff --- /dev/null +++ b/tests/src/test/resources/test/CoverageTest/unit_test_coverage_no_condition-expected.json @@ -0,0 +1,80 @@ +{ + "sources": [ + { + "line": 1, + "code": "package sample;", + }, + { + "line": 2, + "code": "", + }, + { + "line": 3, + "code": "public class Sample {", + }, + { + "line": 4, + "code": "\t", + }, + { + "line": 5, + "code": "\tpublic Sample(int i) {", + }, + { + "line": 6, + "code": "\t\tint j = i++;", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 7, + "code": "\t}", + }, + { + "line": 8, + "code": "\t", + }, + { + "line": 9, + "code": "\tprivate String myMethod() {", + }, + { + "line": 10, + "code": "\t\tif (foo == bar) {", + "lineHits": 0, + "utLineHits": 0 + }, + { + "line": 11, + "code": "\t\t\treturn \"hello\";", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 12, + "code": "\t\t} else {", + }, + { + "line": 13, + "code": "\t\t\tthrow new IllegalStateException();", + "lineHits": 0, + "utLineHits": 0 + }, + { + "line": 14, + "code": "\t\t}", + }, + { + "line": 15, + "code": "\t}", + }, + { + "line": 16, + "code": "}", + }, + { + "line": 17, + "code": "", + } + ] +} diff --git a/tests/src/test/resources/test/CoverageTest/ut_and_it_coverage-expected.json b/tests/src/test/resources/test/CoverageTest/ut_and_it_coverage-expected.json new file mode 100644 index 00000000000..d2b26d19c61 --- /dev/null +++ b/tests/src/test/resources/test/CoverageTest/ut_and_it_coverage-expected.json @@ -0,0 +1,85 @@ +{ + "sources": [ + { + "line": 1, + "code": "package sample;" + }, + { + "line": 2, + "code": "" + }, + { + "line": 3, + "code": "public class Sample {" + }, + { + "line": 4, + "code": "\t" + }, + { + "line": 5, + "code": "\tpublic Sample(int i) {" + }, + { + "line": 6, + "code": "\t\tint j = i++;", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 7, + "code": "\t}" + }, + { + "line": 8, + "code": "\t" + }, + { + "line": 9, + "code": "\tprivate String myMethod() {" + }, + { + "line": 10, + "code": "\t\tif (foo == bar && biz > 1) {", + "lineHits": 0, + "utLineHits": 0, + "conditions": 4, + "utConditions": 4, + "coveredConditions": 2, + "utCoveredConditions": 2 + }, + { + "line": 11, + "code": "\t\t\treturn \"hello\";", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 12, + "code": "\t\t} else {" + }, + { + "line": 13, + "code": "\t\t\tthrow new IllegalStateException();", + "lineHits": 1, + "utLineHits": 1 + }, + { + "line": 14, + "code": "\t\t}" + }, + { + "line": 15, + "code": "\t}" + }, + { + "line": 16, + "code": "}" + }, + { + "line": 17, + "code": "" + } + ] + +} diff --git a/tests/src/test/resources/test/CoverageTrackingTest/covered_files-expected.json b/tests/src/test/resources/test/CoverageTrackingTest/covered_files-expected.json new file mode 100644 index 00000000000..5dda4f3dd11 --- /dev/null +++ b/tests/src/test/resources/test/CoverageTrackingTest/covered_files-expected.json @@ -0,0 +1,14 @@ +{ + "files": [ + { + "key": "sample-with-tests:src/main/xoo/sample/Sample.xoo", + "longName": "src/main/xoo/sample/Sample.xoo", + "coveredLines": 2 + }, + { + "key": "sample-with-tests:src/main/xoo/sample/Sample2.xoo", + "longName": "src/main/xoo/sample/Sample2.xoo", + "coveredLines": 1 + } + ] +} diff --git a/tests/src/test/resources/test/CoverageTrackingTest/tests-expected.json b/tests/src/test/resources/test/CoverageTrackingTest/tests-expected.json new file mode 100644 index 00000000000..121e6753074 --- /dev/null +++ b/tests/src/test/resources/test/CoverageTrackingTest/tests-expected.json @@ -0,0 +1,30 @@ +{ + "tests": [ + { + "name": "success", + "status": "OK", + "durationInMs": 4, + "coveredLines": 3 + }, + { + "name": "error", + "status": "ERROR", + "durationInMs": 2, + "coveredLines": 0, + "message": "Error", + "stacktrace": "The stack" + }, + { + "name": "failure", + "status": "FAILURE", + "durationInMs": 2, + "coveredLines": 1, + "message": "Failure" + }, + { + "name": "skipped", + "status": "SKIPPED", + "coveredLines": 0 + } + ] +} diff --git a/tests/src/test/resources/test/TestExecutionTest/expected.json b/tests/src/test/resources/test/TestExecutionTest/expected.json new file mode 100644 index 00000000000..843d28f5284 --- /dev/null +++ b/tests/src/test/resources/test/TestExecutionTest/expected.json @@ -0,0 +1,31 @@ +{ + "tests": [ + { + "name": "success", + "status": "OK", + "durationInMs": 4, + "coveredLines": 0 + }, + { + "name": "error", + "status": "ERROR", + "durationInMs": 2, + "coveredLines": 0, + "message": "Error", + "stacktrace": "The stack" + }, + { + "name": "failure", + "status": "FAILURE", + "durationInMs": 2, + "coveredLines": 0, + "message": "Failure" + }, + { + "name": "skipped", + "status": "SKIPPED", + "coveredLines": 0, + "durationInMs": 0 + } + ] +} diff --git a/tests/src/test/resources/ui/UiExtensionsTest/static-files.html b/tests/src/test/resources/ui/UiExtensionsTest/static-files.html new file mode 100644 index 00000000000..0acd31cc1fa --- /dev/null +++ b/tests/src/test/resources/ui/UiExtensionsTest/static-files.html @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>static-files</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/static/uiextensionsplugin/file.html</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>//body</td> + <td>Text from static resource</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/updateCenter/UpdateCenterTest/update-center.properties b/tests/src/test/resources/updateCenter/UpdateCenterTest/update-center.properties new file mode 100644 index 00000000000..5882d43188f --- /dev/null +++ b/tests/src/test/resources/updateCenter/UpdateCenterTest/update-center.properties @@ -0,0 +1,62 @@ +# THIS FILE IS USED BY THE UPDATE CENTER +# DO NOT REMOVE OR RENAME +# +# Note : prefix all : by \ +# + +publicVersions=3.0,100.0 + +3.0.description=Encryption of database password, TimeMachine available as widgets, New algorithm for tracking violations, 40 bugs and 40 improvements +3.0.downloadUrl=http\://dist.sonar.codehaus.org/sonar-3.0.zip +3.0.changelogUrl=http\://www.sonarsource.org/downloads/#3.0 +3.0.date=2012-04-17 + +100.0.description=Hundred dot zero! +100.0.downloadUrl=http\://dist.sonar.codehaus.org/sonar-100.0.zip +100.0.changelogUrl=http\://www.sonarsource.org/downloads/#100.0 +100.0.date=2112-06-13 + + +plugins=fake,abap + +#-------------------------------------------------------------------------------------------------------------------------- +abap.homepageUrl=http\://www.sonarsource.com/products/plugins/languages/abap/ +abap.name=ABAP +abap.category=Additional Languages +abap.publicVersions=1.0,1.0.1,1.1,2.0.1 + +abap.1.0.description=Initial version of the product +abap.1.0.sqVersions=2.7,2.8,2.9,2.10,2.11,2.12,2.13,2.13.1,2.14,3.0,3.0.1,3.1,3.1.1 +abap.1.0.downloadUrl= +abap.1.0.date=2011-07-29 + +abap.1.0.1.description=Adjust computation of certain metrics +abap.1.0.1.sqVersions=2.7,2.8,2.9,2.10,2.11,2.12,2.13,2.13.1,2.14,3.0,3.0.1,3.1,3.1.1 +abap.1.0.1.downloadUrl= +abap.1.0.1.date=2011-08-28 + +abap.1.1.description=Adjust computation of certain metrics +abap.1.1.sqVersions=2.7,2.8,2.9,2.10,2.11,2.12,2.13,2.13.1,2.14,3.0,3.0.1,3.1,3.1.1 +abap.1.1.downloadUrl= +abap.1.1.date=2012-01-05 + +abap.2.0.1.description=This new version provides an ABAP source code extractor, adds 5 new rules and improves others +abap.2.0.1.sqVersions=3.2,3.3,3.4,3.5,3.6,3.7,3.7.1,3.7.2,3.7.3,3.7.4,3.7.5,3.7.6,3.7.7,4.0,4.0.1,4.1,4.2,4.3,4.4,4.5,4.6,4.7,5.0,5.1,5.2,5.3,5.4,5.5,5.6,5.7,6.0,6.1,6.2,6.3,6.4,6.5,6.6 +abap.2.0.1.downloadUrl= +abap.2.0.1.date=2012-06-25 + +#-------------------------------------------------------------------------------------------------------------------------- +fake.category=Additional Metrics +fake.publicVersions=1.0,1.1 +fake.name=Fake +fake.description=Fake plugin for integration tests + +fake.1.0.description=Initial release +fake.1.0.sqVersions=3.2,3.3,3.4,3.5,3.6,3.7 +fake.1.0.downloadUrl= +fake.1.0.date=2011-05-06 + +fake.1.1.description=Support sonarqube v100.0 +fake.1.1.sqVersions=3.2,3.3,3.4,3.5,3.6,3.7,3.7.1,3.7.2,3.7.3,3.7.4,3.7.5,3.7.6,3.7.7,4.0,4.0.1,4.1,4.2,4.3,4.4,4.5,4.6,4.7,5.0,5.1,5.2,5.3,5.4,5.5,5.6,5.7,6.0,6.1,6.2,6.3,6.4,6.5,6.6,100.0 +fake.1.1.downloadUrl= +fake.1.1.date=2012-04-27 diff --git a/tests/src/test/resources/updateCenter/installed-plugins.html b/tests/src/test/resources/updateCenter/installed-plugins.html new file mode 100644 index 00000000000..d1986f6f897 --- /dev/null +++ b/tests/src/test/resources/updateCenter/installed-plugins.html @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>installed-plugins</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/updatecenter</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Fake*</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=update-center-plugins</td> + <td>*Fake*1.0-SNAPSHOT*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html b/tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html new file mode 100644 index 00000000000..b62763fb7c9 --- /dev/null +++ b/tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>fail_to_authenticate_when_not_allowed_to_sign_up</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake base identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*</td> + </tr> + <tr> + <td>assertText</td> + <td>bd</td> + <td>*Reason : A functional error has happened*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html b/tests/src/test/resources/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html new file mode 100644 index 00000000000..47a19a2df41 --- /dev/null +++ b/tests/src/test/resources/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>display_unauthorized_page_when_authentication_failed</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake base identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html b/tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html new file mode 100644 index 00000000000..40c300bd701 --- /dev/null +++ b/tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>fail_to_authenticate_when_not_allowed_to_sign_up</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake base identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-base-id-provider' users are not allowed to sign up*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/BaseIdentityProviderTest/fail_when_email_already_exists.html b/tests/src/test/resources/user/BaseIdentityProviderTest/fail_when_email_already_exists.html new file mode 100644 index 00000000000..b6f7e600ac3 --- /dev/null +++ b/tests/src/test/resources/user/BaseIdentityProviderTest/fail_when_email_already_exists.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>fail_when_email_already_exists</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake base identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*</td> + </tr> + <tr> + <td>assertText</td> + <td>bd</td> + <td>*You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/create-and-delete-user.html b/tests/src/test/resources/user/ExternalAuthenticationTest/create-and-delete-user.html new file mode 100644 index 00000000000..f7d0dd6ffb6 --- /dev/null +++ b/tests/src/test/resources/user/ExternalAuthenticationTest/create-and-delete-user.html @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http:/selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>create-and-delete-users</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">create-and-delete-users</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/users</td> + <td></td> +</tr> +<tr> + <td>store</td> + <td>javascript{new Date().getTime()}</td> + <td>userId</td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Create User*</td> +</tr> +<tr> + <td>click</td> + <td>id=users-create</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>id=create-user-form</td> + <td>*Create User*</td> +</tr> +<tr> + <td>type</td> + <td>id=create-user-login</td> + <td>${userId}</td> +</tr> +<tr> + <td>type</td> + <td>id=create-user-name</td> + <td>Name of ${userId}</td> +</tr> +<tr> + <td>type</td> + <td>id=create-user-password</td> + <td>password</td> +</tr> +<tr> + <td>click</td> + <td>id=create-user-submit</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>id=users-list</td> + <td>*${userId}*</td> +</tr> +<tr> + <td>click</td> + <td>css=[data-login="${userId}"] .js-user-deactivate</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>id=deactivate-user-form</td> + <td>*${userId}*</td> +</tr> +<tr> + <td>click</td> + <td>id=deactivate-user-submit</td> + <td></td> +</tr> +<tr> + <td>waitForNotText</td> + <td>id=users-list</td> + <td>*${userId}*</td> +</tr> +<tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>tester</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>password</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td>deactivate-user-form</td> +</tr> +<tr> + <td>waitForText</td> + <td>login_form</td> + <td>*Authentication failed*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details.html b/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details.html new file mode 100644 index 00000000000..b2d712f516c --- /dev/null +++ b/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details.html @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http:/selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>external_user_details</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">external_user_details</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>tester</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>123</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/account/</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>login</td> + <td>tester</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=name</td> + <td>Tester Testerovich</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=email</td> + <td>tester@example.org</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details2.html b/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details2.html new file mode 100644 index 00000000000..d18ae08afb9 --- /dev/null +++ b/tests/src/test/resources/user/ExternalAuthenticationTest/external-user-details2.html @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http:/selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>external_user_details</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">external_user_details</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>tester</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>123</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/account</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>login</td> + <td>tester</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=name</td> + <td>Tester2 Testerovich</td> + </tr> + <tr> + <td>waitForText</td> + <td>id=email</td> + <td>tester2@example.org</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html b/tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html new file mode 100644 index 00000000000..1daf46d205d --- /dev/null +++ b/tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http:/selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>external_user_details</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">external_user_details</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/system</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>id=content</td> + <td>*External User Authentication*FakeRealm*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/allow_users_to_sign_up.html b/tests/src/test/resources/user/LocalAuthenticationTest/allow_users_to_sign_up.html new file mode 100644 index 00000000000..dab0c303ea5 --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/allow_users_to_sign_up.html @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>allow_users_to_sign_up</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*Not a member*</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>link=Sign up</td> + <td></td> + </tr> + <tr> + <td>type</td> + <td>id=user_login</td> + <td>signuplogin</td> + </tr> + <tr> + <td>type</td> + <td>id=user_name</td> + <td>SignUpName</td> + </tr> + <tr> + <td>type</td> + <td>id=user_password</td> + <td>password</td> + </tr> + <tr> + <td>type</td> + <td>id=user_password_confirmation</td> + <td>password</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>global-navigation</td> + <td>*Log in*</td> + </tr> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>signuplogin</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>password</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.navbar</td> + <td>*SignUpName*</td> + </tr> + <tr> + <td>click</td> + <td>Link=SignUpName</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>css=.navbar</td> + <td>*Log out*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/force-authentication.html b/tests/src/test/resources/user/LocalAuthenticationTest/force-authentication.html new file mode 100644 index 00000000000..4c5a751665d --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/force-authentication.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>force-authentication</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">force-authentication</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/login_successful.html b/tests/src/test/resources/user/LocalAuthenticationTest/login_successful.html new file mode 100644 index 00000000000..18da805f66b --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/login_successful.html @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>login_successful</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">login_successful</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>click</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>assertTextPresent</td> + <td>Log out</td> + <td></td> + </tr> + <tr> + <td>clickAndWait</td> + <td>link=Log out</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>link=Log in</td> + <td>Log in</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/login_wrong_password.html b/tests/src/test/resources/user/LocalAuthenticationTest/login_wrong_password.html new file mode 100644 index 00000000000..dc66db8ee9b --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/login_wrong_password.html @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>login_wrong_password</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>wrong</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>assertTextPresent</td> + <td>Authentication failed</td> + <td></td> + </tr> + + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_login_when_not_enough_privilege.html b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_login_when_not_enough_privilege.html new file mode 100644 index 00000000000..9b5a1645ace --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_login_when_not_enough_privilege.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>redirect-to-original-url-after-direct-login</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>simple-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>password</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/settings</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> +</tr> +<tr> + <td>assertText</td> + <td>content</td> + <td>*You are not authorized to access this page. Please log in with more privileges and try again.*</td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_direct_login.html b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_direct_login.html new file mode 100644 index 00000000000..c6519726f27 --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_direct_login.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>redirect-to-original-url-after-direct-login</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>global-navigation</td> + <td>*Log in*</td> + </tr> + <tr> + <td>open</td> + <td>/settings?category=general</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>assertLocation</td> + <td>glob:*/settings?category=general*</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html new file mode 100644 index 00000000000..aecfd8af765 --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>global-navigation</td> + <td>*Log in*</td> + </tr> + <tr> + <td>open</td> + <td>/settings</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>assertLocation</td> + <td>*/settings</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_with_parameters_after_direct_login.html b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_with_parameters_after_direct_login.html new file mode 100644 index 00000000000..5359e3308e0 --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_with_parameters_after_direct_login.html @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>global-navigation</td> + <td>*Log in*</td> + </tr> + <tr> + <td>open</td> + <td>/projects?gate=OK&reliability=1&security=1</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>global-navigation</td> + <td>*Log in*</td> + </tr> + <tr> + <td>click</td> + <td>link=Log in</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> + </tr> + <tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> + </tr> + <tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> + </tr> + <tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> + </tr> + <tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> + </tr> + <tr> + <td>assertLocation</td> + <td>*/projects?gate=OK&reliability=1&security=1</td> + <td></td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/LocalAuthenticationTest/should_not_be_unlogged_when_going_to_login_page.html b/tests/src/test/resources/user/LocalAuthenticationTest/should_not_be_unlogged_when_going_to_login_page.html new file mode 100644 index 00000000000..af5b4362f0e --- /dev/null +++ b/tests/src/test/resources/user/LocalAuthenticationTest/should_not_be_unlogged_when_going_to_login_page.html @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>redirect-to-original-url-after-direct-login</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>simple-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>password</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>content</td> + <td>*Log In to SonarQube*</td> +</tr> +<tr> + <td>open</td> + <td>/</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/MyAccountPageTest/should_display_no_projects.html b/tests/src/test/resources/user/MyAccountPageTest/should_display_no_projects.html new file mode 100644 index 00000000000..2c2f0f026db --- /dev/null +++ b/tests/src/test/resources/user/MyAccountPageTest/should_display_no_projects.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_no_projects</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">should_display_no_projects</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>account-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>password</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/account/projects/</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=#account-projects</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.account-projects-list</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/MyAccountPageTest/should_display_projects.html b/tests/src/test/resources/user/MyAccountPageTest/should_display_projects.html new file mode 100644 index 00000000000..b199152803f --- /dev/null +++ b/tests/src/test/resources/user/MyAccountPageTest/should_display_projects.html @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>should_display_projects</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">should_display_projects</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>account-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>password</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/account/projects/</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.account-project-card</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>css=.account-project-name</td> + <td>*Sample*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=.account-project-quality-gate</td> + <td>*Passed*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=.account-project-key</td> + <td>*sample*</td> +</tr> +<tr> + <td>assertText</td> + <td>css=.account-project-description</td> + <td>*Description of a project*</td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.account-project-analysis</td> + <td></td> +</tr> +<tr> + <td>assertElementPresent</td> + <td>css=.account-project-links a[href="http://example.com"]</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html new file mode 100644 index 00000000000..6a38ed69063 --- /dev/null +++ b/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>fail_to_authenticate_when_not_allowed_to_sign_up</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake oauth2 identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*</td> + </tr> + <tr> + <td>assertText</td> + <td>bd</td> + <td>*Reason : A functional error has happened*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html new file mode 100644 index 00000000000..b01d24aad4c --- /dev/null +++ b/tests/src/test/resources/user/OAuth2IdentityProviderTest/display_unauthorized_page_when_authentication_failed.html @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>display_unauthorized_page_when_authentication_failed</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake oauth2 identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html new file mode 100644 index 00000000000..a3da2de8ed0 --- /dev/null +++ b/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>fail_to_authenticate_when_not_allowed_to_sign_up</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake oauth2 identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-oauth2-id-provider' users are not allowed to sign up*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_when_email_already_exists.html b/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_when_email_already_exists.html new file mode 100644 index 00000000000..7d038ac592d --- /dev/null +++ b/tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_when_email_already_exists.html @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>fail_when_email_already_exists</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">french</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>content</td> + <td>*Log in with Fake oauth2 identity provider*</td> + </tr> + <tr> + <td>click</td> + <td>css=.oauth-providers a</td> + <td></td> + </tr> + <tr> + <td>waitForText</td> + <td>bd</td> + <td>*You're not authorized to access this page. Please contact the administrator.*</td> + </tr> + <tr> + <td>assertText</td> + <td>bd</td> + <td>*You can't sign up because email 'john@email.com' is already used by an existing user. This means that you probably already registered with another account*</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/UsersPageTest/admin_should_change_its_own_password.html b/tests/src/test/resources/user/UsersPageTest/admin_should_change_its_own_password.html new file mode 100644 index 00000000000..06e806cf259 --- /dev/null +++ b/tests/src/test/resources/user/UsersPageTest/admin_should_change_its_own_password.html @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>admin_should_change_its_own_password</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr> +<td rowspan="1" colspan="3">admin_should_change_its_own_password</td> +</tr> +</thead> +<tbody> +<tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/login</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/users</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=[data-login=admin-user]</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=[data-login=admin-user] .js-user-change-password</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.modal</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>id=change-user-password-old-password</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>id=change-user-password-password</td> + <td>new-admin-user</td> +</tr> +<tr> + <td>type</td> + <td>id=change-user-password-password-confirmation</td> + <td>new-admin-user</td> +</tr> +<tr> + <td>click</td> + <td>id=change-user-password-submit</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.modal</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> diff --git a/tests/src/test/resources/user/UsersPageTest/generate_and_revoke_user_token.html b/tests/src/test/resources/user/UsersPageTest/generate_and_revoke_user_token.html new file mode 100644 index 00000000000..c81949c4d10 --- /dev/null +++ b/tests/src/test/resources/user/UsersPageTest/generate_and_revoke_user_token.html @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <link rel="selenium.base" href="http://localhost:49506"/> + <title>generate_and_revoke_user_token</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> + <thead> + <tr> + <td rowspan="1" colspan="3">generate_and_revoke_user_token</td> + </tr> + </thead> + <tbody> + <tr> + <td>open</td> + <td>/sessions/logout</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/sessions/new</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>login</td> + <td>admin-user</td> +</tr> +<tr> + <td>type</td> + <td>password</td> + <td>admin-user</td> +</tr> +<tr> + <td>clickAndWait</td> + <td>commit</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-authenticated</td> + <td></td> +</tr> +<tr> + <td>open</td> + <td>/users</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-user-tokens</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-user-tokens</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.modal</td> + <td>*No tokens*</td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-generate-token-form input</td> + <td></td> +</tr> +<tr> + <td>type</td> + <td>css=.js-generate-token-form input</td> + <td>test-token</td> +</tr> +<tr> + <td>click</td> + <td>css=.js-generate-token-form button</td> + <td></td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.modal code</td> + <td></td> +</tr> +<tr> + <td>waitForText</td> + <td>css=.modal</td> + <td>*test-token*</td> +</tr> +<tr> + <td>waitForElementPresent</td> + <td>css=.js-revoke-token-form</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-revoke-token-form button</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>css=.js-revoke-token-form button</td> + <td></td> +</tr> +<tr> + <td>assertElementNotPresent</td> + <td>css=.js-revoke-token-form</td> + <td></td> +</tr> +</tbody> +</table> +</body> +</html> |