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);
57 private ComponentDbTester componentDb = new ComponentDbTester(db);
58 private DbClient dbClient = db.getDbClient();
59 private DbSession dbSession = db.getSession();
60 private TestProjectIndexers projectIndexers = new TestProjectIndexers();
61 private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
62 private ComponentService underTest = new ComponentService(dbClient, userSession, projectIndexers, projectLifeCycleListeners);
65 public void update_project_key() {
66 ComponentDto project = insertSampleProject();
67 ComponentDto file = componentDb.insertComponent(ComponentTesting.newFileDto(project, null).setKey("sample:root:src/File.xoo"));
68 ComponentDto inactiveFile = componentDb.insertComponent(ComponentTesting.newFileDto(project, null).setKey("sample:root:src/InactiveFile.xoo").setEnabled(false));
72 logInAsProjectAdministrator(project);
73 underTest.updateKey(dbSession, componentDb.getProjectDto(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.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
89 Deque<PushEventDto> pushEvents = db.getDbClient().pushEventDao().selectChunkByProjectUuids(dbSession, Set.of(project.uuid()), 0L, "id", 20);
91 assertThat(pushEvents).isNotEmpty();
93 Optional<PushEventDto> event = pushEvents.stream().filter(e -> e.getProjectUuid().equals(project.uuid()) && 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 ComponentDto provisionedProject = insertProject("provisionedProject");
107 logInAsProjectAdministrator(provisionedProject);
108 underTest.updateKey(dbSession, componentDb.getProjectDto(provisionedProject), "provisionedProject2");
111 assertComponentKeyHasBeenUpdated(provisionedProject.getKey(), "provisionedProject2");
112 assertThat(projectIndexers.hasBeenCalled(provisionedProject.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
116 public void fail_to_update_project_key_without_admin_permission() {
117 ComponentDto project = insertSampleProject();
118 userSession.logIn("john").addProjectPermission(UserRole.USER, project);
120 ProjectDto projectDto = componentDb.getProjectDto(project);
121 assertThatThrownBy(() -> underTest.updateKey(dbSession, projectDto, "sample2:root"))
122 .isInstanceOf(ForbiddenException.class);
126 public void fail_if_old_key_and_new_key_are_the_same() {
127 ComponentDto project = insertSampleProject();
128 ComponentDto anotherProject = componentDb.insertPrivateProject();
129 logInAsProjectAdministrator(project);
131 ProjectDto projectDto = componentDb.getProjectDto(project);
132 String anotherProjectDbKey = anotherProject.getKey();
133 assertThatThrownBy(() -> underTest.updateKey(dbSession, projectDto,
134 anotherProjectDbKey))
135 .isInstanceOf(IllegalArgumentException.class)
136 .hasMessage("Impossible to update key: a component with key \"" + anotherProjectDbKey + "\" already exists.");
140 public void fail_if_new_key_is_empty() {
141 ComponentDto project = insertSampleProject();
142 logInAsProjectAdministrator(project);
144 ProjectDto projectDto = componentDb.getProjectDto(project);
145 assertThatThrownBy(() -> underTest.updateKey(dbSession, projectDto, ""))
146 .isInstanceOf(IllegalArgumentException.class)
147 .hasMessage("Malformed key for ''. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
151 public void fail_if_new_key_is_not_formatted_correctly() {
152 ComponentDto project = insertSampleProject();
153 logInAsProjectAdministrator(project);
155 ProjectDto projectDto = componentDb.getProjectDto(project);
156 assertThatThrownBy(() -> underTest.updateKey(dbSession, projectDto, "sample?root"))
157 .isInstanceOf(IllegalArgumentException.class)
158 .hasMessage("Malformed key for 'sample?root'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
161 private ComponentDto insertSampleProject() {
162 return insertProject("sample:root");
165 private ComponentDto insertProject(String key) {
166 return componentDb.insertPrivateProject(c -> c.setKey(key));
169 private void assertComponentKeyHasBeenUpdated(String oldKey, String newKey) {
170 assertThat(dbClient.componentDao().selectByKey(dbSession, oldKey)).isEmpty();
171 assertThat(dbClient.componentDao().selectByKey(dbSession, newKey)).isPresent();
174 private void logInAsProjectAdministrator(ComponentDto provisionedProject) {
175 userSession.logIn("john").addProjectPermission(UserRole.ADMIN, provisionedProject);