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 com.google.gson.Gson;
23 import java.nio.charset.StandardCharsets;
24 import java.sql.SQLException;
25 import java.util.HashMap;
26 import java.util.List;
29 import java.util.stream.Collectors;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.RegisterExtension;
32 import org.slf4j.event.Level;
33 import org.sonar.api.measures.CoreMetrics;
34 import org.sonar.api.testfixtures.log.LogTesterJUnit5;
35 import org.sonar.api.utils.System2;
36 import org.sonar.core.metric.SoftwareQualitiesMetrics;
37 import org.sonar.core.util.SequenceUuidFactory;
38 import org.sonar.db.MigrationDbTester;
39 import org.sonar.server.platform.db.migration.step.DataChange;
41 import static java.lang.String.format;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.assertj.core.api.Assertions.assertThatCode;
44 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
45 import static org.assertj.core.api.Assertions.tuple;
46 import static org.mockito.Mockito.mock;
48 class MigrateBranchesLiveMeasuresToMeasuresIT {
50 private static final String MEASURES_MIGRATED_COLUMN = "measures_migrated";
51 public static final String SELECT_MEASURE = "select component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at " +
52 "from measures where component_uuid = '%s'";
55 public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigrateBranchesLiveMeasuresToMeasures.class);
58 private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
60 private final SequenceUuidFactory uuidFactory = new SequenceUuidFactory();
61 private final System2 system2 = mock();
62 private final DataChange underTest = new MigrateBranchesLiveMeasuresToMeasures(db.database(), system2);
65 void shall_complete_when_tables_are_empty() throws SQLException {
68 assertThat(db.countRowsOfTable("measures")).isZero();
72 void migration_does_nothing_if_live_measures_table_is_missing() {
73 db.executeDdl("drop table live_measures");
74 db.assertTableDoesNotExist("live_measures");
75 String branch = "branch_3";
76 insertNotMigratedBranch(branch);
78 assertThatCode(underTest::execute)
79 .doesNotThrowAnyException();
83 void log_the_item_uuid_when_the_migration_fails() {
84 String nclocMetricUuid = insertMetric("ncloc", "INT");
85 String branch1 = "branch_1";
86 insertNotMigratedBranch(branch1);
87 insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
89 db.executeDdl("drop table measures");
90 db.assertTableDoesNotExist("measures");
92 assertThatExceptionOfType(SQLException.class)
93 .isThrownBy(underTest::execute);
95 assertThat(logTester.logs(Level.ERROR))
96 .contains("Migration of branch branch_1 failed");
100 void shall_not_migrate_when_branch_is_already_flagged() throws SQLException {
101 String nclocMetricUuid = insertMetric("ncloc", "INT");
102 String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
103 String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
104 String branch1 = "branch_1";
105 insertMigratedBranch(branch1);
106 insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
107 insertMeasure(branch1, qgStatusMetricUuid, Map.of("text_value", "ok"));
108 insertMeasure(branch1, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
110 insertMigratedBranch("branch_2");
111 insertMeasure("branch_2", nclocMetricUuid, Map.of("value", 14220));
115 assertThat(db.countRowsOfTable("measures")).isZero();
119 void should_flag_branch_with_no_measures() throws SQLException {
120 String branch = "branch_3";
121 insertNotMigratedBranch(branch);
125 assertBranchMigrated(branch);
126 assertThat(db.countRowsOfTable("measures")).isZero();
130 void should_migrate_branch_with_measures() throws SQLException {
131 String nclocMetricUuid = insertMetric("ncloc", "INT");
132 String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
133 String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
135 String branch1 = "branch_4";
136 insertNotMigratedBranch(branch1);
137 String component1 = uuidFactory.create();
138 String component2 = uuidFactory.create();
139 insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
140 insertMeasure(branch1, component1, qgStatusMetricUuid, Map.of("text_value", "ok"));
141 insertMeasure(branch1, component2, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
143 String branch2 = "branch_5";
144 insertNotMigratedBranch(branch2);
145 insertMeasure(branch2, nclocMetricUuid, Map.of("value", 64));
147 String migratedBranch = "branch_6";
148 insertMigratedBranch(migratedBranch);
149 insertMeasure(migratedBranch, nclocMetricUuid, Map.of("value", 3684));
153 assertBranchMigrated(branch1);
154 assertBranchMigrated(branch2);
155 assertThat(db.countRowsOfTable("measures")).isEqualTo(3);
157 assertThat(db.select(format(SELECT_MEASURE, component1)))
159 .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
160 .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0,\"quality_gate_status\":\"ok\"}", 6033012287291512746L));
162 assertThat(db.select(format(SELECT_MEASURE, component2)))
164 .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
165 .containsOnly(tuple(component2, branch1, "{\"metric_with_data\":\"some data\"}", -4524184678167636687L));
169 void should_not_migrate_measures_planned_for_deletion() throws SQLException {
170 String nclocMetricUuid = insertMetric("ncloc", "INT");
171 Set<String> deletedMetricUuid = DeleteSoftwareQualityRatingFromProjectMeasures.SOFTWARE_QUALITY_METRICS_TO_DELETE.stream().map(e -> insertMetric(e, "INT"))
172 .collect(Collectors.toSet());
174 String branch1 = "branch_4";
175 insertNotMigratedBranch(branch1);
176 String component1 = uuidFactory.create();
177 String component2 = uuidFactory.create();
178 insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
179 deletedMetricUuid.forEach(metricUuid -> insertMeasure(branch1, component1, metricUuid, Map.of("value", 120)));
180 deletedMetricUuid.forEach(metricUuid -> insertMeasure(branch1, component2, metricUuid, Map.of("value", 120)));
184 assertBranchMigrated(branch1);
185 assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
187 assertThat(db.select(format(SELECT_MEASURE, component1)))
189 .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
190 .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0}", -1557106439558598045L));
192 assertThat(db.select(format(SELECT_MEASURE, component2)))
197 void should_include_new_measures_based_on_previous_available_measures() throws SQLException {
198 Set<String> metricsToMigrate = MeasureMigration.MIGRATION_MAP.keySet().stream().map(e -> insertMetric(e, "DATA"))
199 .collect(Collectors.toSet());
201 String branch = "branch_4";
202 insertNotMigratedBranch(branch);
203 String component1 = uuidFactory.create();
204 metricsToMigrate.forEach(metricUuid -> insertMeasure(branch, component1, metricUuid,
205 Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":7}")));
209 assertBranchMigrated(branch);
210 assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
212 List<Map<String, Object>> measuresFromDB = db.select(format(SELECT_MEASURE, component1));
213 assertThat(measuresFromDB).hasSize(1);
215 Gson gson = new Gson();
216 Map<String, Object> jsonValue = gson.fromJson((String) measuresFromDB.get(0).get("json_value"), Map.class);
218 Map<String, Object> expectedExistingMetrics = MeasureMigration.MIGRATION_MAP.keySet().stream().collect(
219 Collectors.toMap(s -> s, s -> "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":7}"));
221 Map<String, Object> expectedNewMetrics = MeasureMigration.MIGRATION_MAP.values().stream().collect(
222 Collectors.toMap(s -> s, s -> 7.0));
224 assertThat(jsonValue).containsAllEntriesOf(expectedExistingMetrics).containsAllEntriesOf(expectedNewMetrics);
228 void should_migrate_other_measures_when_there_is_an_error_converting_previous_measures() throws SQLException {
229 String nclocMetricUuid = insertMetric("ncloc", "INT");
230 String maintainabilityMetricUuid = insertMetric(CoreMetrics.MAINTAINABILITY_ISSUES_KEY, "DATA");
231 String reliabilityMetricUuid = insertMetric(CoreMetrics.RELIABILITY_ISSUES_KEY, "DATA");
232 String securityMetricUuid = insertMetric(CoreMetrics.SECURITY_ISSUES_KEY, "DATA");
234 String branch = "branch_4";
235 insertNotMigratedBranch(branch);
236 String component1 = uuidFactory.create();
237 insertMeasure(branch, component1, nclocMetricUuid, Map.of("value", 120));
238 // total is not a number
239 insertMeasure(branch, component1, maintainabilityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4," +
240 "\"total\":\"ABC\"}"));
241 // total cannot fit in a long
242 insertMeasure(branch, component1, reliabilityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4," +
243 "\"total\":98723987498723987429874928748748}"));
244 insertMeasure(branch, component1, securityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":37}"));
246 logTester.setLevel(Level.DEBUG);
249 assertBranchMigrated(branch);
250 assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
252 List<Map<String, Object>> measuresFromDB = db.select(format(SELECT_MEASURE, component1));
253 assertThat(measuresFromDB).hasSize(1);
255 Gson gson = new Gson();
256 Map<String, Object> jsonValue = gson.fromJson((String) measuresFromDB.get(0).get("json_value"), Map.class);
258 Map<String, Object> expectedExistingMetrics = Map.of(
260 CoreMetrics.MAINTAINABILITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":\"ABC\"}",
261 CoreMetrics.RELIABILITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":98723987498723987429874928748748}",
262 CoreMetrics.SECURITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":37}"
265 Map<String, Object> expectedNewMetrics = Map.of(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 37.0);
267 assertThat(jsonValue).hasSize(5).containsAllEntriesOf(expectedExistingMetrics).containsAllEntriesOf(expectedNewMetrics);
268 assertThat(logTester.logs(Level.DEBUG)).contains("Failed to migrate metric reliability_issues with value {\"LOW\":3,\"MEDIUM\":0," +
269 "\"HIGH\":4,\"total\":98723987498723987429874928748748}");
272 private void assertBranchMigrated(String branch) {
273 List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from project_branches where uuid = '%s'", MEASURES_MIGRATED_COLUMN, branch));
276 .extracting(t -> t.get("MIGRATED"))
280 private String insertMetric(String metricName, String valueType) {
281 String metricUuid = uuidFactory.create();
282 db.executeInsert("metrics",
285 "val_type", valueType);
289 private void insertMeasure(String branchUuid, String metricUuid, Map<String, Object> data) {
290 insertMeasure(branchUuid, uuidFactory.create(), metricUuid, data);
293 private void insertMeasure(String branchUuid, String componentUuid, String metricUuid, Map<String, Object> data) {
294 Map<String, Object> dataMap = new HashMap<>(data);
295 dataMap.put("uuid", uuidFactory.create());
296 dataMap.put("component_uuid", componentUuid);
297 dataMap.put("project_uuid", branchUuid);
298 dataMap.put("metric_uuid", metricUuid);
299 dataMap.put("created_at", 12L);
300 dataMap.put("updated_at", 12L);
302 db.executeInsert("live_measures", dataMap);
305 private void insertNotMigratedBranch(String branchUuid) {
306 insertBranch(branchUuid, false);
309 private void insertMigratedBranch(String branchUuid) {
310 insertBranch(branchUuid, true);
313 private void insertBranch(String branchUuid, boolean migrated) {
314 db.executeInsert("project_branches",
317 "branch_type", "LONG",
318 "project_uuid", uuidFactory.create(),
319 MEASURES_MIGRATED_COLUMN, migrated,
320 "need_issue_sync", false,