*/
package org.sonar.server.platform.db.migration.version.v71;
+import com.google.common.collect.Multimap;
import java.sql.SQLException;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.function.Function;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.version.v63.DefaultOrganizationUuidProvider;
-import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toMap;
public class MigrateWebhooksToWebhooksTable extends DataChange {
+ private static final long NO_RESOURCE_ID = -8_435_121;
private static final Logger LOGGER = Loggers.get(MigrateWebhooksToWebhooksTable.class);
private DefaultOrganizationUuidProvider defaultOrganizationUuidProvider;
@Override
public void execute(Context context) throws SQLException {
- Map<String, PropertyRow> rows = context
- .prepareSelect("select id, prop_key, resource_id, text_value, created_at from properties where prop_key like 'sonar.webhooks%'")
+ Multimap<Long, PropertyRow> rows = context
+ .prepareSelect("select" +
+ " props.id, props.prop_key, props.resource_id, prj.uuid, props.text_value, props.created_at" +
+ " from properties props" +
+ " left join projects prj on prj.id = props.resource_id and prj.scope = ? and prj.qualifier = ? and prj.enabled = ?" +
+ " where" +
+ " props.prop_key like 'sonar.webhooks%'" +
+ " and props.text_value is not null")
+ .setString(1, "PRJ")
+ .setString(2, "TRK")
+ .setBoolean(3, true)
.list(row -> new PropertyRow(
row.getLong(1),
row.getString(2),
- row.getLong(3),
- row.getString(4),
- row.getLong(5)))
+ row.getNullableLong(3),
+ row.getNullableString(4),
+ row.getString(5),
+ row.getLong(6)))
.stream()
- .collect(toMap(PropertyRow::key, Function.identity()));
-
- if (!rows.isEmpty()) {
- migrateGlobalWebhooks(context, rows);
- migrateProjectsWebhooks(context, rows);
- context
- .prepareUpsert("delete from properties where prop_key like 'sonar.webhooks.global%' or prop_key like 'sonar.webhooks.project%'")
- .execute()
- .commit();
+ .collect(MoreCollectors.index(PropertyRow::getResourceId, Function.identity()));
+
+ for (Map.Entry<Long, Collection<PropertyRow>> entry : rows.asMap().entrySet()) {
+ long projectId = entry.getKey();
+ if (projectId == NO_RESOURCE_ID) {
+ migrateGlobalWebhooks(context, entry.getValue());
+ } else {
+ migrateProjectsWebhooks(context, entry.getValue());
+ }
+ deleteAllWebhookProperties(context);
}
}
- private void migrateProjectsWebhooks(Context context, Map<String, PropertyRow> properties) throws SQLException {
- PropertyRow index = properties.get("sonar.webhooks.project");
- if (index != null) {
+ private static void deleteAllWebhookProperties(Context context) throws SQLException {
+ context
+ .prepareUpsert("delete from properties where prop_key like 'sonar.webhooks.global%' or prop_key like 'sonar.webhooks.project%'")
+ .execute()
+ .commit();
+ }
+
+ private void migrateGlobalWebhooks(Context context, Collection<PropertyRow> rows) throws SQLException {
+ Multimap<String, PropertyRow> rowsByPropertyKey = rows.stream()
+ .collect(MoreCollectors.index(PropertyRow::getPropertyKey));
+ Optional<PropertyRow> rootProperty = rowsByPropertyKey.get("sonar.webhooks.global").stream().findFirst();
+ if (rootProperty.isPresent()) {
+ PropertyRow row = rootProperty.get();
// can't lambda due to checked exception.
- for (Webhook webhook : extractProjectWebhooksFrom(context, properties, index.value().split(","))) {
+ for (Webhook webhook : extractGlobalWebhooksFrom(context, rowsByPropertyKey, row.value().split(","))) {
insert(context, webhook);
}
}
}
- private void migrateGlobalWebhooks(Context context, Map<String, PropertyRow> properties) throws SQLException {
- PropertyRow index = properties.get("sonar.webhooks.global");
- if (index != null) {
- // can't lambda due to checked exception.
- for (Webhook webhook : extractGlobalWebhooksFrom(context, properties, index.value().split(","))) {
- insert(context, webhook);
+ private List<Webhook> extractGlobalWebhooksFrom(Context context, Multimap<String, PropertyRow> rowsByPropertyKey, String[] values) throws SQLException {
+ String defaultOrganizationUuid = defaultOrganizationUuidProvider.get(context);
+ return Arrays.stream(values)
+ .map(value -> {
+ Optional<PropertyRow> name = rowsByPropertyKey.get("sonar.webhooks.global." + value + ".name").stream().findFirst();
+ Optional<PropertyRow> url = rowsByPropertyKey.get("sonar.webhooks.global." + value + ".url").stream().findFirst();
+ if (name.isPresent() && url.isPresent()) {
+ return new Webhook(
+ name.get(),
+ url.get(),
+ defaultOrganizationUuid,
+ null);
+ }
+ LOGGER.warn(
+ "Global webhook missing name and/or url will be deleted (name='{}', url='{}')",
+ name.map(PropertyRow::value).orElse(null),
+ url.map(PropertyRow::value).orElse(null));
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .collect(toList());
+ }
+
+ private void migrateProjectsWebhooks(Context context, Collection<PropertyRow> rows) throws SQLException {
+ Multimap<String, PropertyRow> rowsByPropertyKey = rows.stream()
+ .collect(MoreCollectors.index(PropertyRow::getPropertyKey));
+ Optional<PropertyRow> rootProperty = rowsByPropertyKey.get("sonar.webhooks.project").stream().findFirst();
+ if (rootProperty.isPresent()) {
+ PropertyRow row = rootProperty.get();
+ if (row.getProjectUuid() == null) {
+ LOGGER.warn("At least one webhook referenced missing or non project resource '{}' and will be deleted", row.getResourceId());
+ } else {
+ for (Webhook webhook : extractProjectWebhooksFrom(row, rowsByPropertyKey, row.value().split(","))) {
+ insert(context, webhook);
+ }
}
}
}
+ private static List<Webhook> extractProjectWebhooksFrom(PropertyRow row, Multimap<String, PropertyRow> properties, String[] values) {
+ return Arrays.stream(values)
+ .map(value -> {
+ Optional<PropertyRow> name = properties.get("sonar.webhooks.project." + value + ".name").stream().findFirst();
+ Optional<PropertyRow> url = properties.get("sonar.webhooks.project." + value + ".url").stream().findFirst();
+ if (name.isPresent() && url.isPresent()) {
+ return new Webhook(name.get(), url.get(), null, row.projectUuid);
+ }
+ LOGGER.warn("Project webhook for project {} (id={}) missing name and/or url will be deleted (name='{}', url='{}')",
+ row.getProjectUuid(), row.getResourceId(), name.map(PropertyRow::value).orElse(null), url.map(PropertyRow::value).orElse(null));
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .collect(MoreCollectors.toList());
+ }
+
private void insert(Context context, Webhook webhook) throws SQLException {
if (webhook.isValid()) {
context.prepareUpsert("insert into webhooks (uuid, name, url, organization_uuid, project_uuid, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)")
}
}
- private List<Webhook> extractGlobalWebhooksFrom(Context context, Map<String, PropertyRow> properties, String[] values) throws SQLException {
- String defaultOrganizationUuid = defaultOrganizationUuidProvider.get(context);
- return Arrays.stream(values)
- .map(value -> new Webhook(
- properties.get("sonar.webhooks.global." + value + ".name"),
- properties.get("sonar.webhooks.global." + value + ".url"),
- defaultOrganizationUuid, null))
- .collect(toList());
- }
-
- private static List<Webhook> extractProjectWebhooksFrom(Context context, Map<String, PropertyRow> properties, String[] values) throws SQLException {
- List<Webhook> webhooks = new ArrayList<>();
- for (String value : values) {
- PropertyRow name = properties.get("sonar.webhooks.project." + value + ".name");
- PropertyRow url = properties.get("sonar.webhooks.project." + value + ".url");
- String projectUuid = checkNotNull(projectUuidOf(context, name), "Project was not found for property : sonar.webhooks.project.%s", value);
- webhooks.add(new Webhook(name, url, null, projectUuid));
- }
- return webhooks;
- }
-
- @CheckForNull
- private static String projectUuidOf(Context context, PropertyRow row) throws SQLException {
- return context
- .prepareSelect("select uuid from projects where id = ?")
- .setLong(1, row.resourceId())
- .list(row1 -> row1.getString(1)).stream().findFirst().orElse(null);
- }
-
private static class PropertyRow {
-
private final Long id;
- private final String key;
+ private final String propertyKey;
private final Long resourceId;
+ private final String projectUuid;
private final String value;
private final Long createdAt;
- public PropertyRow(long id, String key, Long resourceId, String value, Long createdAt) {
+ private PropertyRow(long id, String propertyKey, @Nullable Long resourceId, @Nullable String projectUuid, String value, Long createdAt) {
this.id = id;
- this.key = key;
+ this.propertyKey = propertyKey;
this.resourceId = resourceId;
+ this.projectUuid = projectUuid;
this.value = value;
this.createdAt = createdAt;
}
return id;
}
- public String key() {
- return key;
+ public String getPropertyKey() {
+ return propertyKey;
}
- public Long resourceId() {
- return resourceId;
+ public long getResourceId() {
+ return resourceId == null ? NO_RESOURCE_ID : resourceId;
+ }
+
+ @CheckForNull
+ public String getProjectUuid() {
+ return projectUuid;
}
public String value() {
public String toString() {
return "{" +
"id=" + id +
- ", key='" + key + '\'' +
+ ", propertyKey='" + propertyKey + '\'' +
", resourceId=" + resourceId +
+ ", projectUuid=" + projectUuid +
", value='" + value + '\'' +
", createdAt=" + createdAt +
'}';
*/
package org.sonar.server.platform.db.migration.version.v71;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.SQLException;
+import java.util.Arrays;
import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.internal.TestSystem2;
+import org.junit.runner.RunWith;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.CoreDbTester;
import static java.lang.Long.parseLong;
import static java.lang.String.valueOf;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.apache.commons.lang.RandomStringUtils.randomNumeric;
import static org.assertj.core.api.Assertions.assertThat;
+@RunWith(DataProviderRunner.class)
public class MigrateWebhooksToWebhooksTableTest {
+ private static final long NOW = 1_500_000_000_000L;
+ private static final boolean ENABLED = true;
+ private static final boolean DISABLED = false;
@Rule
public final CoreDbTester dbTester = CoreDbTester.createForSchema(MigrateWebhooksToWebhooksTableTest.class, "migrate_webhooks.sql");
- private static final long NOW = 1_500_000_000_000L;
- private System2 system2 = new TestSystem2().setNow(NOW);
+
private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
private MigrateWebhooksToWebhooksTable underTest = new MigrateWebhooksToWebhooksTable(dbTester.database(), new DefaultOrganizationUuidProviderImpl(), uuidFactory);
@Test
public void should_do_nothing_if_no_webhooks() throws SQLException {
-
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
+ assertNoMoreWebhookProperties();
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
}
@Test
- public void should_migrate_one_global_webhook() throws SQLException {
- String uuid = insertDefaultOrganization();
- insertProperty("sonar.webhooks.global", "1", null, system2.now());
- insertProperty("sonar.webhooks.global.1.name", "a webhook", null, system2.now());
- insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, system2.now());
+ @UseDataProvider("numberOfGlobalWebhooksToMigration")
+ public void execute_migrates_any_number_of_global_webhook_to_default_organization(int numberOfGlobalWebhooks) throws SQLException {
+ String defaultOrganizationUuid = insertDefaultOrganization();
+ insertGlobalWebhookProperties(numberOfGlobalWebhooks);
+ Row[] webhooks = IntStream.range(1, numberOfGlobalWebhooks + 1)
+ .mapToObj(i -> insertGlobalWebhookProperty(i, "name webhook " + i, "url webhook " + i, defaultOrganizationUuid))
+ .map(Row::new)
+ .toArray(Row[]::new);
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
+ assertThat(selectWebhooksInDb())
+ .containsOnly(webhooks)
+ .extracting(Row::getUuid)
+ .doesNotContainNull();
+ assertNoMoreWebhookProperties();
+ }
- Map<String, Object> migrated = dbTester.selectFirst("select * from webhooks");
- assertThat(migrated.get("UUID")).isNotNull();
- assertThat(migrated.get("NAME")).isEqualTo("a webhook");
- assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
- assertThat(migrated.get("PROJECT_UUID")).isNull();
- assertThat(migrated.get("ORGANIZATION_UUID")).isEqualTo(uuid);
- assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
- assertThat(migrated.get("CREATED_AT")).isEqualTo(system2.now());
- assertThat(migrated.get("UPDATED_AT")).isEqualTo(system2.now());
+ @DataProvider
+ public static Object[][] numberOfGlobalWebhooksToMigration() {
+ return new Object[][] {
+ {1},
+ {2},
+ {2 + new Random().nextInt(10)}
+ };
}
@Test
- public void should_migrate_one_project_webhook() throws SQLException {
- String organization = insertDefaultOrganization();
- String projectId = "156";
- String projectUuid = UuidFactoryFast.getInstance().create();
- ;
- insertProject(organization, projectId, projectUuid);
+ public void execute_deletes_inconsistent_properties_for_global_webhook() throws SQLException {
+ String defaultOrganizationUuid = insertDefaultOrganization();
+ insertGlobalWebhookProperties(4);
+ insertGlobalWebhookProperty(1, null, "no name", defaultOrganizationUuid);
+ insertGlobalWebhookProperty(2, "no url", null, defaultOrganizationUuid);
+ insertGlobalWebhookProperty(3, null, null, defaultOrganizationUuid);
+ Webhook webhook = insertGlobalWebhookProperty(4, "name", "url", defaultOrganizationUuid);
+
+ underTest.execute();
+
+ assertThat(selectWebhooksInDb()).containsOnly(new Row(webhook));
+ assertNoMoreWebhookProperties();
+ }
- insertProperty("sonar.webhooks.project", "1", projectId, system2.now());
- insertProperty("sonar.webhooks.project.1.name", "a webhook", projectId, system2.now());
- insertProperty("sonar.webhooks.project.1.url", "http://webhook.com", projectId, system2.now());
+ @Test
+ @UseDataProvider("DP_execute_migrates_any_number_of_webhooks_for_any_number_of_existing_project")
+ public void execute_migrates_any_number_of_webhooks_for_any_number_of_existing_project(int webhookCount, int projectCount) throws SQLException {
+ Project[] projects = IntStream.range(0, projectCount)
+ .mapToObj(i -> insertProject(ENABLED))
+ .toArray(Project[]::new);
+ Row[] rows = Arrays.stream(projects).flatMap(project -> {
+ insertProjectWebhookProperties(project, webhookCount);
+ return IntStream.range(1, webhookCount + 1)
+ .mapToObj(i -> insertProjectWebhookProperty(project, i, "name webhook " + i, "url webhook " + i))
+ .map(Row::new);
+ }).toArray(Row[]::new);
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
+ assertThat(selectWebhooksInDb()).containsOnly(rows);
+ assertNoMoreWebhookProperties();
+ }
- Map<String, Object> migrated = dbTester.selectFirst("select * from webhooks");
- assertThat(migrated.get("UUID")).isNotNull();
- assertThat(migrated.get("NAME")).isEqualTo("a webhook");
- assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
- assertThat(migrated.get("PROJECT_UUID")).isEqualTo(projectUuid);
- assertThat(migrated.get("ORGANIZATION_UUID")).isNull();
- assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
- assertThat(migrated.get("CREATED_AT")).isEqualTo(system2.now());
- assertThat(migrated.get("UPDATED_AT")).isEqualTo(system2.now());
+ @DataProvider
+ public static Object[][] DP_execute_migrates_any_number_of_webhooks_for_any_number_of_existing_project() {
+ Random random = new Random();
+ return new Object[][] {
+ {1, 1},
+ {2, 1},
+ {1, 2},
+ {2 + random.nextInt(5), 2 + random.nextInt(5)}
+ };
}
@Test
- public void should_migrate_global_webhooks() throws SQLException {
- insertDefaultOrganization();
- insertProperty("sonar.webhooks.global", "1,2", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.1.name", "a webhook", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.2.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
+ public void execute_delete_webhooks_of_non_existing_project() throws SQLException {
+ Project project = insertProject(ENABLED);
+ Project nonExistingProject = new Project(233, "foo");
+ Row[] rows = Stream.of(project, nonExistingProject)
+ .map(prj -> {
+ insertProjectWebhookProperties(prj, 1);
+ return insertProjectWebhookProperty(prj, 1, "name", "url");
+ })
+ .map(Row::new)
+ .toArray(Row[]::new);
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
+ assertThat(selectWebhooksInDb())
+ .containsOnly(Arrays.stream(rows).filter(r -> Objects.equals(r.projectUuid, project.uuid)).toArray(Row[]::new));
+ assertNoMoreWebhookProperties();
}
@Test
- public void should_migrate_only_valid_webhooks() throws SQLException {
- insertDefaultOrganization();
- insertProperty("sonar.webhooks.global", "1,2,3,4", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.3.name", "a webhook", null, parseLong(randomNumeric(7)));
- insertProperty("sonar.webhooks.global.3.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
- // nothing for 4
+ public void execute_delete_webhooks_of_disabled_project() throws SQLException {
+ Project project = insertProject(ENABLED);
+ Project nonExistingProject = insertProject(DISABLED);
+ Row[] rows = Stream.of(project, nonExistingProject)
+ .map(prj -> {
+ insertProjectWebhookProperties(prj, 1);
+ return insertProjectWebhookProperty(prj, 1, "name", "url");
+ })
+ .map(Row::new)
+ .toArray(Row[]::new);
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
+ assertThat(selectWebhooksInDb())
+ .containsOnly(Arrays.stream(rows).filter(r -> Objects.equals(r.projectUuid, project.uuid)).toArray(Row[]::new));
+ assertNoMoreWebhookProperties();
}
@Test
- public void should_migrate_project_webhooks() throws SQLException {
- String organization = insertDefaultOrganization();
- String projectId = "156";
- String projectUuid = UuidFactoryFast.getInstance().create();
- ;
- insertProject(organization, projectId, projectUuid);
-
- insertProperty("sonar.webhooks.project", "1,2", projectId, system2.now());
- insertProperty("sonar.webhooks.project.1.name", "a webhook", projectId, system2.now());
- insertProperty("sonar.webhooks.project.1.url", "http://webhook.com", projectId, system2.now());
- insertProperty("sonar.webhooks.project.2.name", "another webhook", projectId, system2.now());
- insertProperty("sonar.webhooks.project.2.url", "http://webhookhookhook.com", projectId, system2.now());
+ public void execute_deletes_inconsistent_properties_for_project_webhook() throws SQLException {
+ Project project = insertProject(ENABLED);
+ insertProjectWebhookProperties(project, 4);
+ insertProjectWebhookProperty(project, 1, null, "no name");
+ insertProjectWebhookProperty(project, 2, "no url", null);
+ insertProjectWebhookProperty(project, 3, null, null);
+ Webhook webhook = insertProjectWebhookProperty(project, 4, "name", "url");
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
+ assertThat(selectWebhooksInDb()).containsOnly(new Row(webhook));
+ assertNoMoreWebhookProperties();
}
@Test
- public void should_not_migrate_more_than_10_webhooks_per_project() throws SQLException {
+ @UseDataProvider("DP_execute_delete_webhooks_of_components_which_is_not_a_project")
+ public void execute_delete_webhooks_of_components_which_is_not_a_project(int webhookCount, String scope, String qualifier) throws SQLException {
+ Project project = insertComponent(scope, qualifier, ENABLED);
+ insertProjectWebhookProperties(project, webhookCount);
+ IntStream.range(1, webhookCount + 1)
+ .forEach(i -> insertProjectWebhookProperty(project, i, "name_" + i, "url_" + i));
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
+ assertThat(selectWebhooksInDb()).isEmpty();
+ assertNoMoreWebhookProperties();
+ }
+
+ @DataProvider
+ public static Object[][] DP_execute_delete_webhooks_of_components_which_is_not_a_project() {
+ String[] scopes = {Scopes.DIRECTORY, Scopes.FILE};
+ String[] qualifiers = {Qualifiers.VIEW, Qualifiers.SUBVIEW, Qualifiers.MODULE, Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE};
+ int[] webhookCounts = { 1, 2, 2 + new Random().nextInt(5)};
+ Object[][] res = new Object[scopes.length * qualifiers.length * webhookCounts.length][3];
+ int i = 0;
+ for (int webhookCount : webhookCounts) {
+ for (String scope : scopes) {
+ for (String qualifier : qualifiers) {
+ res[i][0] = webhookCount;
+ res[i][1] = scope;
+ res[i][2] = qualifier;
+ i++;
+ }
+ }
+ }
+ return res;
+ }
+
+ @Test
+ public void should_migrate_global_webhooks() throws SQLException {
+ insertDefaultOrganization();
+ insertProperty("sonar.webhooks.global", "1,2", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.1.name", "a webhook", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.2.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
+
+ underTest.execute();
+
+ assertNoMoreWebhookProperties();
+ assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
}
@Test
- public void should_not_migrate_more_than_10_global_webhooks() throws SQLException {
+ public void should_migrate_only_valid_webhooks() throws SQLException {
+ insertDefaultOrganization();
+ insertProperty("sonar.webhooks.global", "1,2,3,4", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.3.name", "a webhook", null, parseLong(randomNumeric(7)));
+ insertProperty("sonar.webhooks.global.3.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
+ // nothing for 4
underTest.execute();
- assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
- assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
+ assertNoMoreWebhookProperties();
+ assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
}
private void insertProperty(String key, @Nullable String value, @Nullable String resourceId, Long date) {
"id", randomNumeric(7),
"prop_key", valueOf(key),
"text_value", value,
- "is_empty", value.isEmpty() ? true : false,
+ "is_empty", value == null || value.isEmpty(),
"resource_id", resourceId == null ? null : valueOf(resourceId),
"created_at", valueOf(date));
}
return uuid;
}
- private void insertProject(String organizationUuid, String projectId, String projectUuid) {
+ private static long PROJECT_ID_GENERATOR = new Random().nextInt(343_343);
+
+ private Project insertProject(boolean enabled) {
+ return insertComponent(Scopes.PROJECT, Qualifiers.PROJECT, enabled);
+ }
+
+ private Project insertComponent(String scope, String qualifier, boolean enabled) {
+ long projectId = PROJECT_ID_GENERATOR++;
+ Project res = new Project(projectId, "prj_" + projectId);
dbTester.executeInsert(
"PROJECTS",
- "ID", projectId,
- "ORGANIZATION_UUID", organizationUuid,
- "UUID", projectUuid);
+ "ID", res.id,
+ "ORGANIZATION_UUID", randomAlphanumeric(15),
+ "UUID", res.uuid,
+ "ROOT_UUID", res.uuid,
+ "PROJECT_UUID", res.uuid,
+ "UUID_PATH", "." + res.uuid + ".",
+ "PRIVATE", new Random().nextBoolean(),
+ "SCOPE", scope,
+ "QUALIFIER", qualifier,
+ "ENABLED", enabled
+ );
+ return res;
+ }
+
+ private void insertGlobalWebhookProperties(int total) {
+ insertProperty("sonar.webhooks.global",
+ IntStream.range(0, total).map(i -> i + 1).mapToObj(String::valueOf).collect(Collectors.joining(",")),
+ null,
+ NOW);
+ }
+
+ private Webhook insertGlobalWebhookProperty(int i, @Nullable String name, @Nullable String url, String organizationUuid) {
+ long createdAt = NOW + new Random().nextInt(5_6532_999);
+ Webhook res = new Webhook(name, url, organizationUuid, null, createdAt);
+ if (name != null) {
+ insertProperty("sonar.webhooks.global." + i + ".name", name, null, createdAt);
+ }
+ if (url != null) {
+ insertProperty("sonar.webhooks.global." + i + ".url", url, null, createdAt);
+ }
+ return res;
+ }
+
+ private void insertProjectWebhookProperties(Project project, int total) {
+ insertProperty("sonar.webhooks.project",
+ IntStream.range(0, total).map(i -> i + 1).mapToObj(String::valueOf).collect(Collectors.joining(",")),
+ valueOf(project.id),
+ NOW);
+ }
+
+ private Webhook insertProjectWebhookProperty(Project project, int i, @Nullable String name, @Nullable String url) {
+ long createdAt = NOW + new Random().nextInt(5_6532_999);
+ Webhook res = new Webhook(name, url, null, project.uuid, createdAt);
+ if (name != null) {
+ insertProperty("sonar.webhooks.project." + i + ".name", name, valueOf(project.id), createdAt);
+ }
+ if (url != null) {
+ insertProperty("sonar.webhooks.project." + i + ".url", url, valueOf(project.id), createdAt);
+ }
+ return res;
+ }
+
+ private Stream<Row> selectWebhooksInDb() {
+ return dbTester.select("select * from webhooks").stream().map(Row::new);
+ }
+
+ private void assertNoMoreWebhookProperties() {
+ assertThat(dbTester.countSql("select count(*) from properties where prop_key like 'sonar.webhooks.%'"))
+ .isEqualTo(0);
+ }
+
+ private static final class Webhook {
+ @Nullable
+ private final String name;
+ @Nullable
+ private final String url;
+ @Nullable
+ private final String organizationUuid;
+ @Nullable
+ private final String projectUuid;
+ private final long createdAt;
+
+ private Webhook(@Nullable String name, @Nullable String url, @Nullable String organizationUuid, @Nullable String projectUuid, long createdAt) {
+ this.name = name;
+ this.url = url;
+ this.organizationUuid = organizationUuid;
+ this.projectUuid = projectUuid;
+ this.createdAt = createdAt;
+ }
+ }
+
+ private static class Row {
+ private final String uuid;
+ private final String name;
+ private final String url;
+ @Nullable
+ private final String organizationUuid;
+ @Nullable
+ private final String projectUuid;
+ private final long createdAt;
+ private final long updatedAt;
+
+ private Row(Map<String, Object> row) {
+ this.uuid = (String) row.get("UUID");
+ this.name = (String) row.get("NAME");
+ this.url = (String) row.get("URL");
+ this.organizationUuid = (String) row.get("ORGANIZATION_UUID");
+ this.projectUuid = (String) row.get("PROJECT_UUID");
+ this.createdAt = (Long) row.get("CREATED_AT");
+ this.updatedAt = (Long) row.get("UPDATED_AT");
+ }
+
+ private Row(Webhook webhook) {
+ this.uuid = "NOT KNOWN YET";
+ this.name = webhook.name;
+ this.url = webhook.url;
+ this.organizationUuid = webhook.organizationUuid;
+ this.projectUuid = webhook.projectUuid;
+ this.createdAt = webhook.createdAt;
+ this.updatedAt = webhook.createdAt;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Row row = (Row) o;
+ return createdAt == row.createdAt &&
+ updatedAt == row.updatedAt &&
+ Objects.equals(name, row.name) &&
+ Objects.equals(url, row.url) &&
+ Objects.equals(organizationUuid, row.organizationUuid) &&
+ Objects.equals(projectUuid, row.projectUuid);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, url, organizationUuid, projectUuid, createdAt, updatedAt);
+ }
+
+ @Override
+ public String toString() {
+ return "Row{" +
+ "uuid='" + uuid + '\'' +
+ ", name='" + name + '\'' +
+ ", url='" + url + '\'' +
+ ", organizationUuid='" + organizationUuid + '\'' +
+ ", projectUuid='" + projectUuid + '\'' +
+ ", createdAt=" + createdAt +
+ ", updatedAt=" + updatedAt +
+ '}';
+ }
+ }
+
+ private static final class Project {
+ private final long id;
+ private final String uuid;
+
+ private Project(long id, String uuid) {
+ this.id = id;
+ this.uuid = uuid;
+ }
}
}