3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.component;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Deque;
24 import java.util.Optional;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.sonar.api.utils.System2;
29 import org.sonar.api.web.UserRole;
30 import org.sonar.db.DbClient;
31 import org.sonar.db.DbSession;
32 import org.sonar.db.DbTester;
33 import org.sonar.db.component.ComponentDbTester;
34 import org.sonar.db.component.ComponentDto;
35 import org.sonar.db.component.ComponentTesting;
36 import org.sonar.db.project.ProjectDto;
37 import org.sonar.db.pushevent.PushEventDto;
38 import org.sonar.server.es.ProjectIndexer;
39 import org.sonar.server.es.TestProjectIndexers;
40 import org.sonar.server.exceptions.ForbiddenException;
41 import org.sonar.server.project.ProjectLifeCycleListeners;
42 import org.sonar.server.tester.UserSessionRule;
44 import static org.assertj.core.api.Assertions.assertThat;
45 import static org.assertj.core.api.Assertions.assertThatThrownBy;
46 import static org.mockito.Mockito.mock;
48 public class ComponentServiceUpdateKeyIT {
50 private final System2 system2 = System2.INSTANCE;
53 public final UserSessionRule userSession = UserSessionRule.standalone();
55 public final DbTester db = DbTester.create(system2, true);
57 private DbClient dbClient = db.getDbClient();
58 private DbSession dbSession = db.getSession();
59 private TestProjectIndexers projectIndexers = new TestProjectIndexers();
60 private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
61 private ComponentService underTest = new ComponentService(dbClient, userSession, projectIndexers, projectLifeCycleListeners);
64 public void update_project_key() {
65 ProjectDto project = insertSampleProject();
66 ComponentDto mainBranchComponent = db.components().getComponentDto(project);
67 ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(mainBranchComponent).setKey("sample:root:src/File.xoo"));
68 ComponentDto inactiveFile = db.components().insertComponent(ComponentTesting.newFileDto(mainBranchComponent).setKey("sample:root:src/InactiveFile.xoo").setEnabled(false));
72 logInAsProjectAdministrator(project);
73 underTest.updateKey(dbSession, project, "sample2:root");
76 // Check project key has been updated
77 assertThat(db.getDbClient().componentDao().selectByKey(dbSession, project.getKey())).isEmpty();
78 assertThat(db.getDbClient().componentDao().selectByKey(dbSession, "sample2:root")).isNotNull();
80 // Check file key has been updated
81 assertThat(db.getDbClient().componentDao().selectByKey(dbSession, file.getKey())).isEmpty();
82 assertThat(db.getDbClient().componentDao().selectByKey(dbSession, "sample2:root:src/File.xoo")).isNotNull();
83 assertThat(db.getDbClient().componentDao().selectByKey(dbSession, "sample2:root:src/InactiveFile.xoo")).isNotNull();
85 assertThat(dbClient.componentDao().selectByKey(dbSession, inactiveFile.getKey())).isEmpty();
87 assertThat(projectIndexers.hasBeenCalled(project.getUuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
89 Deque<PushEventDto> pushEvents = db.getDbClient().pushEventDao().selectChunkByProjectUuids(dbSession, Set.of(project.getUuid()), 0L, "id", 20);
91 assertThat(pushEvents).isNotEmpty();
93 Optional<PushEventDto> event = pushEvents.stream().filter(e -> e.getProjectUuid().equals(project.getUuid()) && e.getName().equals("ProjectKeyChanged")).findFirst();
94 assertThat(event).isNotEmpty();
96 String payload = new String(event.get().getPayload(), StandardCharsets.UTF_8);
98 assertThat(payload).isEqualTo("{\"oldProjectKey\":\"sample:root\",\"newProjectKey\":\"sample2:root\"}");
102 public void update_provisioned_project_key() {
103 ProjectDto provisionedProject = insertProject("provisionedProject");
107 logInAsProjectAdministrator(provisionedProject);
108 underTest.updateKey(dbSession, provisionedProject, "provisionedProject2");
111 assertComponentKeyHasBeenUpdated(provisionedProject.getKey(), "provisionedProject2");
112 assertThat(projectIndexers.hasBeenCalled(provisionedProject.getUuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
116 public void fail_to_update_project_key_without_admin_permission() {
117 ProjectDto project = insertSampleProject();
118 userSession.logIn("john").addProjectPermission(UserRole.USER, project);
120 assertThatThrownBy(() -> underTest.updateKey(dbSession, project, "sample2:root"))
121 .isInstanceOf(ForbiddenException.class);
125 public void fail_if_old_key_and_new_key_are_the_same() {
126 ProjectDto project = insertSampleProject();
127 ComponentDto anotherProject = db.components().insertPrivateProject().getMainBranchComponent();
128 logInAsProjectAdministrator(project);
130 String anotherProjectDbKey = anotherProject.getKey();
131 assertThatThrownBy(() -> underTest.updateKey(dbSession, project,
132 anotherProjectDbKey))
133 .isInstanceOf(IllegalArgumentException.class)
134 .hasMessage("Impossible to update key: a component with key \"" + anotherProjectDbKey + "\" already exists.");
138 public void fail_if_new_key_is_empty() {
139 ProjectDto project = insertSampleProject();
140 logInAsProjectAdministrator(project);
142 assertThatThrownBy(() -> underTest.updateKey(dbSession, project, ""))
143 .isInstanceOf(IllegalArgumentException.class)
144 .hasMessage("Malformed key for ''. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
148 public void fail_if_new_key_is_not_formatted_correctly() {
149 ProjectDto project = insertSampleProject();
150 logInAsProjectAdministrator(project);
152 assertThatThrownBy(() -> underTest.updateKey(dbSession, project, "sample?root"))
153 .isInstanceOf(IllegalArgumentException.class)
154 .hasMessage("Malformed key for 'sample?root'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
157 private ProjectDto insertSampleProject() {
158 return insertProject("sample:root");
161 private ProjectDto insertProject(String key) {
162 return db.components().insertPrivateProject(c -> c.setKey(key)).getProjectDto();
165 private void assertComponentKeyHasBeenUpdated(String oldKey, String newKey) {
166 assertThat(dbClient.componentDao().selectByKey(dbSession, oldKey)).isEmpty();
167 assertThat(dbClient.componentDao().selectByKey(dbSession, newKey)).isPresent();
170 private void logInAsProjectAdministrator(ProjectDto provisionedProject) {
171 userSession.logIn("john").addProjectPermission(UserRole.ADMIN, provisionedProject);