]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19372 add anticipated_transitions table
authorSteve Marion <unknown>
Thu, 20 Jul 2023 13:17:54 +0000 (15:17 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Aug 2023 20:03:03 +0000 (20:03 +0000)
12 files changed:
server/sonar-db-dao/src/it/java/org/sonar/db/issue/AnticipatedTransitionDaoIT.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/CreateAnticipatedTransitionsTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/CreateAnticipatedTransitionsTableTest.java [new file with mode: 0644]

diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/AnticipatedTransitionDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/AnticipatedTransitionDaoIT.java
new file mode 100644 (file)
index 0000000..417455f
--- /dev/null
@@ -0,0 +1,48 @@
+package org.sonar.db.issue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AnticipatedTransitionDaoIT {
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private final AnticipatedTransitionDao underTest = db.getDbClient().anticipatedTransitionDao();
+
+  @Test
+  public void select_anticipated_transition() {
+    final String projectUuid = "project147852";
+    String atUuid = "uuid_123456";
+    AnticipatedTransitionDto transition = new AnticipatedTransitionDto(
+      atUuid,
+      projectUuid,
+      "userUuid",
+      "transition",
+      "status",
+      "comment",
+      1,
+      "message",
+      "lineHash",
+      "ruleKey");
+
+    // insert one
+    underTest.insert(db.getSession(), transition);
+
+    // select all
+    var anticipatedTransitionDtos = underTest.selectByProjectUuid(db.getSession(), projectUuid);
+    assertThat(anticipatedTransitionDtos).hasSize(1);
+    assertThat(anticipatedTransitionDtos.get(0))
+      .extracting("uuid").isEqualTo(atUuid);
+
+    // delete one
+    underTest.delete(db.getSession(), atUuid);
+
+    // select all
+    var anticipatedTransitionDtosDeleted = underTest.selectByProjectUuid(db.getSession(), projectUuid);
+    assertThat(anticipatedTransitionDtosDeleted).isEmpty();
+  }
+}
index d350c97108f7cb321734191517aef3988af76f48..3ba6682347bc30c6ca0677b281eab53bceea3506 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.db.entity.EntityDao;
 import org.sonar.db.es.EsQueueDao;
 import org.sonar.db.event.EventComponentChangeDao;
 import org.sonar.db.event.EventDao;
+import org.sonar.db.issue.AnticipatedTransitionDao;
 import org.sonar.db.issue.IssueChangeDao;
 import org.sonar.db.issue.IssueDao;
 import org.sonar.db.measure.LiveMeasureDao;
@@ -106,6 +107,7 @@ public class DaoModule extends Module {
     // =====================================================================
     ActiveRuleDao.class,
     AnalysisPropertiesDao.class,
+    AnticipatedTransitionDao.class,
     AuthorizationDao.class,
     ApplicationProjectsDao.class,
     AuditDao.class,
index f1b9fa30b15ac1cf545e34b56518adc95e8b40d0..47d6813b54990b853db9cbe562affc73c82f59a3 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.db.entity.EntityDao;
 import org.sonar.db.es.EsQueueDao;
 import org.sonar.db.event.EventComponentChangeDao;
 import org.sonar.db.event.EventDao;
+import org.sonar.db.issue.AnticipatedTransitionDao;
 import org.sonar.db.issue.IssueChangeDao;
 import org.sonar.db.issue.IssueDao;
 import org.sonar.db.measure.LiveMeasureDao;
@@ -180,6 +181,7 @@ public class DbClient {
   private final ScimUserDao scimUserDao;
   private final ScimGroupDao scimGroupDao;
   private final EntityDao entityDao;
+  private final AnticipatedTransitionDao anticipatedTransitionDao;
 
   private final ReportScheduleDao reportScheduleDao;
   private final ReportSubscriptionDao reportSubscriptionDao;
@@ -270,6 +272,7 @@ public class DbClient {
     entityDao = getDao(map, EntityDao.class);
     reportScheduleDao = getDao(map, ReportScheduleDao.class);
     reportSubscriptionDao = getDao(map, ReportSubscriptionDao.class);
+    anticipatedTransitionDao = getDao(map, AnticipatedTransitionDao.class);
   }
 
   public DbSession openSession(boolean batch) {
@@ -590,12 +593,16 @@ public class DbClient {
     return entityDao;
   }
 
-  public ReportScheduleDao reportScheduleDao(){
+  public ReportScheduleDao reportScheduleDao() {
     return reportScheduleDao;
   }
 
   public ReportSubscriptionDao reportSubscriptionDao() {
     return reportSubscriptionDao;
   }
+
+  public AnticipatedTransitionDao anticipatedTransitionDao() {
+    return anticipatedTransitionDao;
+  }
 }
 
index 16752dbcb0a502dda106518352a4fad794b663d1..4d670509d6763ff08ba8790bebe029cfba9199b1 100644 (file)
@@ -70,6 +70,8 @@ import org.sonar.db.es.EsQueueMapper;
 import org.sonar.db.event.EventComponentChangeMapper;
 import org.sonar.db.event.EventDto;
 import org.sonar.db.event.EventMapper;
+import org.sonar.db.issue.AnticipatedTransitionDto;
+import org.sonar.db.issue.AnticipatedTransitionMapper;
 import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueChangeMapper;
 import org.sonar.db.issue.IssueDto;
@@ -194,6 +196,7 @@ public class MyBatis {
     confBuilder.loadAlias("ActiveRule", ActiveRuleDto.class);
     confBuilder.loadAlias("ActiveRuleParam", ActiveRuleParamDto.class);
     confBuilder.loadAlias("ApplicationProject", ApplicationProjectDto.class);
+    confBuilder.loadAlias("AnticipatedTransition", AnticipatedTransitionDto.class);
     confBuilder.loadAlias("CeTaskCharacteristic", CeTaskCharacteristicDto.class);
     confBuilder.loadAlias("Component", ComponentDto.class);
     confBuilder.loadAlias("DuplicationUnit", DuplicationUnitDto.class);
@@ -256,6 +259,7 @@ public class MyBatis {
       AlmPatMapper.class,
       AlmSettingMapper.class,
       AnalysisPropertiesMapper.class,
+      AnticipatedTransitionMapper.class,
       ApplicationProjectsMapper.class,
       AuditMapper.class,
       AuthorizationMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionDao.java
new file mode 100644 (file)
index 0000000..6deb8be
--- /dev/null
@@ -0,0 +1,24 @@
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class AnticipatedTransitionDao implements Dao {
+
+  public void insert(DbSession session, AnticipatedTransitionDto transition) {
+    mapper(session).insert(transition);
+  }
+
+  public void delete(DbSession session, String uuid) {
+    mapper(session).delete(uuid);
+  }
+
+  public List<AnticipatedTransitionDto> selectByProjectUuid(DbSession session, String projectUuid) {
+    return mapper(session).selectByProjectUuid(projectUuid);
+  }
+
+  private static AnticipatedTransitionMapper mapper(DbSession session) {
+    return session.getMapper(AnticipatedTransitionMapper.class);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionDto.java
new file mode 100644 (file)
index 0000000..7611d8d
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import javax.annotation.Nullable;
+
+public class AnticipatedTransitionDto {
+  private String uuid;
+  private String projectUuid;
+  private String userUuid;
+  private String transition;
+  private String status;
+  private String comment;
+  private Integer line;
+  private String message;
+  private String lineHash;
+  private String ruleKey;
+  // TODO: private String filePath
+  // TODO: private Instant createdAt
+
+
+  public AnticipatedTransitionDto(
+    String uuid,
+    String projectUuid,
+    String userUuid,
+    String transition,
+    String status,
+    @Nullable String comment,
+    @Nullable Integer line,
+    @Nullable String message,
+    @Nullable String lineHash,
+    String ruleKey) {
+    this.uuid = uuid;
+    this.projectUuid = projectUuid;
+    this.userUuid = userUuid;
+    this.transition = transition;
+    this.status = status;
+    this.comment = comment;
+    this.line = line;
+    this.message = message;
+    this.lineHash = lineHash;
+    this.ruleKey = ruleKey;
+  }
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public void setUuid(String uuid) {
+    this.uuid = uuid;
+  }
+
+  public String getProjectUuid() {
+    return projectUuid;
+  }
+
+  public void setProjectUuid(String projectUuid) {
+    this.projectUuid = projectUuid;
+  }
+
+  public String getUserUuid() {
+    return userUuid;
+  }
+
+  public void setUserUuid(String userUuid) {
+    this.userUuid = userUuid;
+  }
+
+  public String getTransition() {
+    return transition;
+  }
+
+  public void setTransition(String transition) {
+    this.transition = transition;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public String getComment() {
+    return comment;
+  }
+
+  public void setComment(String comment) {
+    this.comment = comment;
+  }
+
+  public Integer getLine() {
+    return line;
+  }
+
+  public void setLine(Integer line) {
+    this.line = line;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public void setMessage(String message) {
+    this.message = message;
+  }
+
+  public String getLineHash() {
+    return lineHash;
+  }
+
+  public void setLineHash(String lineHash) {
+    this.lineHash = lineHash;
+  }
+
+  public String getRuleKey() {
+    return ruleKey;
+  }
+
+  public void setRuleKey(String ruleKey) {
+    this.ruleKey = ruleKey;
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/AnticipatedTransitionMapper.java
new file mode 100644 (file)
index 0000000..dc83835
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.issue;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface AnticipatedTransitionMapper {
+  void insert(AnticipatedTransitionDto anticipatedTransitionDto);
+
+  void delete(@Param("uuid") String uuid);
+
+  List<AnticipatedTransitionDto> selectByProjectUuid(@Param("projectUuid") String projectUuid);
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/AnticipatedTransitionMapper.xml
new file mode 100644 (file)
index 0000000..3160d2d
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+
+<mapper namespace="org.sonar.db.issue.AnticipatedTransitionMapper">
+
+  <sql id="anticipatedTransitionsColumns">
+    at.uuid as uuid,
+    at.project_uuid as projectUuid,
+    at.user_uuid as userUuid,
+    at.transition as transition,
+    at.status as status,
+    at.transition_comment as "comment",
+    at.line as line,
+    at.message as message,
+    at.line_hash as lineHash,
+    at.rule_key as ruleKey
+  </sql>
+
+  <insert id="insert" useGeneratedKeys="false" parameterType="AnticipatedTransition">
+    INSERT INTO anticipated_transitions (uuid, project_uuid, user_uuid, transition, status,
+    transition_comment, line, message, line_hash, rule_key)
+    VALUES (#{uuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{userUuid,jdbcType=VARCHAR},
+    #{transition,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{comment,jdbcType=VARCHAR},
+    #{line,jdbcType=INTEGER}, #{message,jdbcType=VARCHAR}, #{lineHash,jdbcType=VARCHAR}, #{ruleKey,jdbcType=VARCHAR})
+  </insert>
+
+  <delete id="delete" parameterType="string">
+    delete from anticipated_transitions where uuid=#{uuid}
+  </delete>
+
+  <select id="selectByProjectUuid" parameterType="string" resultType="AnticipatedTransition">
+    select
+    <include refid="anticipatedTransitionsColumns"/>
+    from anticipated_transitions at
+    where at.project_uuid=#{projectUuid,jdbcType=VARCHAR}
+  </select>
+</mapper>
index f09fbe6fb2dda348f94f6fdd6ed113db28f39451..1ac97309e112012b22a4f238185c048d762fc3bb 100644 (file)
@@ -75,6 +75,20 @@ CREATE TABLE "ANALYSIS_PROPERTIES"(
 ALTER TABLE "ANALYSIS_PROPERTIES" ADD CONSTRAINT "PK_ANALYSIS_PROPERTIES" PRIMARY KEY("UUID");
 CREATE INDEX "ANALYSIS_PROPERTIES_ANALYSIS" ON "ANALYSIS_PROPERTIES"("ANALYSIS_UUID" NULLS FIRST);
 
+CREATE TABLE "ANTICIPATED_TRANSITIONS"(
+    "UUID" CHARACTER VARYING(40) NOT NULL,
+    "PROJECT_UUID" CHARACTER VARYING(40) NOT NULL,
+    "USER_UUID" CHARACTER VARYING(255) NOT NULL,
+    "TRANSITION" CHARACTER VARYING(20) NOT NULL,
+    "STATUS" CHARACTER VARYING(20) NOT NULL,
+    "TRANSITION_COMMENT" CHARACTER VARYING(4000),
+    "LINE" INTEGER,
+    "MESSAGE" CHARACTER VARYING(4000),
+    "LINE_HASH" CHARACTER VARYING(255),
+    "RULE_KEY" CHARACTER VARYING(200) NOT NULL
+);
+ALTER TABLE "ANTICIPATED_TRANSITIONS" ADD CONSTRAINT "PK_ANTICIPATED_TRANSITIONS" PRIMARY KEY("UUID");
+
 CREATE TABLE "APP_BRANCH_PROJECT_BRANCH"(
     "UUID" CHARACTER VARYING(40) NOT NULL,
     "APPLICATION_UUID" CHARACTER VARYING(40) NOT NULL,
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/CreateAnticipatedTransitionsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/CreateAnticipatedTransitionsTable.java
new file mode 100644 (file)
index 0000000..336a522
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v102;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.CreateTableBuilder;
+import org.sonar.server.platform.db.migration.step.CreateTableChange;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.IntegerColumnDef.newIntegerColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.MAX_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.USER_UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class CreateAnticipatedTransitionsTable extends CreateTableChange {
+  static final String ANTICIPATED_TRANSITIONS_TABLE_NAME = "anticipated_transitions";
+
+  public CreateAnticipatedTransitionsTable(Database db) {
+    super(db, ANTICIPATED_TRANSITIONS_TABLE_NAME);
+  }
+
+  @Override
+  public void execute(DdlChange.Context context, String tableName) throws SQLException {
+    context.execute(new CreateTableBuilder(getDialect(), tableName)
+      .addPkColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(UUID_SIZE).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("project_uuid").setIsNullable(false).setLimit(UUID_SIZE).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("user_uuid").setIsNullable(false).setLimit(USER_UUID_SIZE).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("transition").setIsNullable(false).setLimit(20).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("status").setIsNullable(false).setLimit(20).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("transition_comment").setLimit(MAX_SIZE).build())
+      .addColumn(newIntegerColumnDefBuilder().setColumnName("line").build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("message").setLimit(MAX_SIZE).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("line_hash").setLimit(255).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("rule_key").setIsNullable(false).setLimit(200).build())
+      .build());
+  }
+}
index 491e15e7e5c89f7a2b8a912c33a66a08bab1ec1a..72853abfe41226400fe364e8f14693e1ca592159 100644 (file)
@@ -82,6 +82,7 @@ public class DbVersion102 implements DbVersion {
       .add(10_2_027, "Populate column 'created_at_temp' in 'components' table", PopulateCreatedAtTempInComponents.class)
       .add(10_2_028, "Drop column 'created_at' in 'components' table", DropCreatedAtInComponents.class)
       .add(10_2_029, "Rename column 'created_at_temp' to 'created_at' in 'components' table", RenameCreatedAtTempInComponents.class)
-    ;
+
+      .add(10_2_030, "Create table 'anticipated_transitions'", CreateAnticipatedTransitionsTable.class);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/CreateAnticipatedTransitionsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/CreateAnticipatedTransitionsTableTest.java
new file mode 100644 (file)
index 0000000..ece98e0
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v102;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.MAX_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.USER_UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.version.v102.CreateAnticipatedTransitionsTable.ANTICIPATED_TRANSITIONS_TABLE_NAME;
+
+public class CreateAnticipatedTransitionsTableTest {
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createEmpty();
+
+  private final DdlChange createAnticipatedTransitionsTable = new CreateAnticipatedTransitionsTable(db.database());
+
+  @Test
+  public void migration_should_create_a_table() throws SQLException {
+    db.assertTableDoesNotExist(ANTICIPATED_TRANSITIONS_TABLE_NAME);
+
+    createAnticipatedTransitionsTable.execute();
+
+    db.assertTableExists(ANTICIPATED_TRANSITIONS_TABLE_NAME);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "uuid", Types.VARCHAR, UUID_SIZE, false);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "project_uuid", Types.VARCHAR, UUID_SIZE, false);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "user_uuid", Types.VARCHAR, USER_UUID_SIZE, false);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "transition", Types.VARCHAR, 20, false);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "status", Types.VARCHAR, 20, false);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "transition_comment", Types.VARCHAR, MAX_SIZE, true);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "line", Types.INTEGER, 11, true);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "message", Types.VARCHAR, MAX_SIZE, true);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "line_hash", Types.VARCHAR, 255, true);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "rule_key", Types.VARCHAR, 200, false);
+    db.assertColumnDefinition(ANTICIPATED_TRANSITIONS_TABLE_NAME, "file_path", Types.VARCHAR, 1500, false);
+    db.assertPrimaryKey(ANTICIPATED_TRANSITIONS_TABLE_NAME, "pk_anticipated_transitions", "uuid");
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    db.assertTableDoesNotExist(ANTICIPATED_TRANSITIONS_TABLE_NAME);
+
+    createAnticipatedTransitionsTable.execute();
+    // re-entrant
+    createAnticipatedTransitionsTable.execute();
+
+    db.assertTableExists(ANTICIPATED_TRANSITIONS_TABLE_NAME);
+  }
+}