3 * Copyright (C) 2009-2024 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.platform.db.migration.version.v108;
22 import java.nio.charset.StandardCharsets;
23 import java.sql.SQLException;
24 import java.util.HashMap;
25 import java.util.List;
28 import java.util.stream.Collectors;
29 import org.junit.jupiter.api.Test;
30 import org.junit.jupiter.api.extension.RegisterExtension;
31 import org.slf4j.event.Level;
32 import org.sonar.api.testfixtures.log.LogTesterJUnit5;
33 import org.sonar.api.utils.System2;
34 import org.sonar.core.util.SequenceUuidFactory;
35 import org.sonar.db.MigrationDbTester;
36 import org.sonar.server.platform.db.migration.step.DataChange;
38 import static java.lang.String.format;
39 import static org.assertj.core.api.Assertions.assertThat;
40 import static org.assertj.core.api.Assertions.assertThatCode;
41 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
42 import static org.assertj.core.api.Assertions.tuple;
43 import static org.mockito.Mockito.mock;
45 class MigrateBranchesLiveMeasuresToMeasuresIT {
47 private static final String MEASURES_MIGRATED_COLUMN = "measures_migrated";
48 public static final String SELECT_MEASURE = "select component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at " +
49 "from measures where component_uuid = '%s'";
52 public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigrateBranchesLiveMeasuresToMeasures.class);
55 private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
57 private final SequenceUuidFactory uuidFactory = new SequenceUuidFactory();
58 private final System2 system2 = mock();
59 private final DataChange underTest = new MigrateBranchesLiveMeasuresToMeasures(db.database(), system2);
62 void shall_complete_when_tables_are_empty() throws SQLException {
65 assertThat(db.countRowsOfTable("measures")).isZero();
69 void migration_does_nothing_if_live_measures_table_is_missing() {
70 db.executeDdl("drop table live_measures");
71 db.assertTableDoesNotExist("live_measures");
72 String branch = "branch_3";
73 insertNotMigratedBranch(branch);
75 assertThatCode(underTest::execute)
76 .doesNotThrowAnyException();
80 void log_the_item_uuid_when_the_migration_fails() {
81 String nclocMetricUuid = insertMetric("ncloc", "INT");
82 String branch1 = "branch_1";
83 insertNotMigratedBranch(branch1);
84 insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
86 db.executeDdl("drop table measures");
87 db.assertTableDoesNotExist("measures");
89 assertThatExceptionOfType(SQLException.class)
90 .isThrownBy(underTest::execute);
92 assertThat(logTester.logs(Level.ERROR))
93 .contains("Migration of branch branch_1 failed");
97 void shall_not_migrate_when_branch_is_already_flagged() throws SQLException {
98 String nclocMetricUuid = insertMetric("ncloc", "INT");
99 String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
100 String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
101 String branch1 = "branch_1";
102 insertMigratedBranch(branch1);
103 insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
104 insertMeasure(branch1, qgStatusMetricUuid, Map.of("text_value", "ok"));
105 insertMeasure(branch1, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
107 insertMigratedBranch("branch_2");
108 insertMeasure("branch_2", nclocMetricUuid, Map.of("value", 14220));
112 assertThat(db.countRowsOfTable("measures")).isZero();
116 void should_flag_branch_with_no_measures() throws SQLException {
117 String branch = "branch_3";
118 insertNotMigratedBranch(branch);
122 assertBranchMigrated(branch);
123 assertThat(db.countRowsOfTable("measures")).isZero();
127 void should_migrate_branch_with_measures() throws SQLException {
128 String nclocMetricUuid = insertMetric("ncloc", "INT");
129 String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
130 String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
132 String branch1 = "branch_4";
133 insertNotMigratedBranch(branch1);
134 String component1 = uuidFactory.create();
135 String component2 = uuidFactory.create();
136 insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
137 insertMeasure(branch1, component1, qgStatusMetricUuid, Map.of("text_value", "ok"));
138 insertMeasure(branch1, component2, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
140 String branch2 = "branch_5";
141 insertNotMigratedBranch(branch2);
142 insertMeasure(branch2, nclocMetricUuid, Map.of("value", 64));
144 String migratedBranch = "branch_6";
145 insertMigratedBranch(migratedBranch);
146 insertMeasure(migratedBranch, nclocMetricUuid, Map.of("value", 3684));
150 assertBranchMigrated(branch1);
151 assertBranchMigrated(branch2);
152 assertThat(db.countRowsOfTable("measures")).isEqualTo(3);
154 assertThat(db.select(format(SELECT_MEASURE, component1)))
156 .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
157 .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0,\"quality_gate_status\":\"ok\"}", 6033012287291512746L));
159 assertThat(db.select(format(SELECT_MEASURE, component2)))
161 .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
162 .containsOnly(tuple(component2, branch1, "{\"metric_with_data\":\"some data\"}", -4524184678167636687L));
166 void should_not_migrate_measures_planned_for_deletion() throws SQLException {
167 String nclocMetricUuid = insertMetric("ncloc", "INT");
168 Set<String> deletedMetricUuid = DeleteSoftwareQualityRatingFromProjectMeasures.SOFTWARE_QUALITY_METRICS_TO_DELETE.stream().map(e -> insertMetric(e, "INT"))
169 .collect(Collectors.toSet());
171 String branch1 = "branch_4";
172 insertNotMigratedBranch(branch1);
173 String component1 = uuidFactory.create();
174 String component2 = uuidFactory.create();
175 insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
176 deletedMetricUuid.forEach(metricUuid -> insertMeasure(branch1, component1, metricUuid, Map.of("value", 120)));
177 deletedMetricUuid.forEach(metricUuid -> insertMeasure(branch1, component2, metricUuid, Map.of("value", 120)));
181 assertBranchMigrated(branch1);
182 assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
184 assertThat(db.select(format(SELECT_MEASURE, component1)))
186 .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
187 .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0}", -1557106439558598045L));
189 assertThat(db.select(format(SELECT_MEASURE, component2)))
193 private void assertBranchMigrated(String branch) {
194 List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from project_branches where uuid = '%s'", MEASURES_MIGRATED_COLUMN, branch));
197 .extracting(t -> t.get("MIGRATED"))
201 private String insertMetric(String metricName, String valueType) {
202 String metricUuid = uuidFactory.create();
203 db.executeInsert("metrics",
206 "val_type", valueType);
210 private void insertMeasure(String branchUuid, String metricUuid, Map<String, Object> data) {
211 insertMeasure(branchUuid, uuidFactory.create(), metricUuid, data);
214 private void insertMeasure(String branchUuid, String componentUuid, String metricUuid, Map<String, Object> data) {
215 Map<String, Object> dataMap = new HashMap<>(data);
216 dataMap.put("uuid", uuidFactory.create());
217 dataMap.put("component_uuid", componentUuid);
218 dataMap.put("project_uuid", branchUuid);
219 dataMap.put("metric_uuid", metricUuid);
220 dataMap.put("created_at", 12L);
221 dataMap.put("updated_at", 12L);
223 db.executeInsert("live_measures", dataMap);
226 private void insertNotMigratedBranch(String branchUuid) {
227 insertBranch(branchUuid, false);
230 private void insertMigratedBranch(String branchUuid) {
231 insertBranch(branchUuid, true);
234 private void insertBranch(String branchUuid, boolean migrated) {
235 db.executeInsert("project_branches",
238 "branch_type", "LONG",
239 "project_uuid", uuidFactory.create(),
240 MEASURES_MIGRATED_COLUMN, migrated,
241 "need_issue_sync", false,