]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8650 add organization parameter to api/projects/ghosts
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 27 Jan 2017 15:51:21 +0000 (16:51 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 31 Jan 2017 13:39:53 +0000 (14:39 +0100)
server/sonar-server/src/main/java/org/sonar/server/project/ws/GhostsAction.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/GhostsActionTest.java
server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json [deleted file]
sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java
sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java
sonar-db/src/test/java/org/sonar/db/component/ComponentDbTester.java
sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml [deleted file]

index 5371b6be118b5ffe2c2fefdc39e83112635372af..9f5b18afcc51b73dff5d048d267c2622b7e435c5 100644 (file)
@@ -32,30 +32,36 @@ import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.api.web.UserRole;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.MyBatis;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.server.es.SearchOptions;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.user.UserSession;
 
 import static com.google.common.collect.Sets.newHashSet;
 import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 
 public class GhostsAction implements ProjectsWsAction {
-  public static final String ACTION = "ghosts";
+  private static final String PARAM_ORGANIZATION = "organization";
+  private static final String ACTION = "ghosts";
   private static final Set<String> POSSIBLE_FIELDS = newHashSet("uuid", "key", "name", "creationDate");
 
   private final DbClient dbClient;
   private final UserSession userSession;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
 
-  public GhostsAction(DbClient dbClient, UserSession userSession) {
+  public GhostsAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider) {
     this.dbClient = dbClient;
     this.userSession = userSession;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
   }
 
   @Override
   public void define(WebService.NewController context) {
-    context
-      .createAction(ACTION)
+    WebService.NewAction action = context.createAction(ACTION);
+
+    action
       .setDescription("List ghost projects.<br /> Requires 'Administer System' permission.")
       .setResponseExample(Resources.getResource(getClass(), "projects-example-ghosts.json"))
       .setSince("5.2")
@@ -63,30 +69,46 @@ public class GhostsAction implements ProjectsWsAction {
       .addFieldsParam(POSSIBLE_FIELDS)
       .addSearchQuery("sonar", "names", "keys")
       .setHandler(this);
+
+    action.createParam(PARAM_ORGANIZATION)
+      .setDescription("the organization key")
+      .setRequired(false)
+      .setInternal(true)
+      .setSince("6.3");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    userSession.checkPermission(UserRole.ADMIN);
-    DbSession dbSession = dbClient.openSession(false);
+    userSession.checkLoggedIn();
+
     SearchOptions searchOptions = new SearchOptions()
       .setPage(request.mandatoryParamAsInt(Param.PAGE),
         request.mandatoryParamAsInt(Param.PAGE_SIZE));
     Set<String> desiredFields = fieldsToReturn(request.paramAsStrings(Param.FIELDS));
     String query = request.param(Param.TEXT_QUERY);
 
-    try {
-      long nbOfProjects = dbClient.componentDao().countGhostProjects(dbSession, query);
-      List<ComponentDto> projects = dbClient.componentDao().selectGhostProjects(dbSession, searchOptions.getOffset(), searchOptions.getLimit(), query);
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      OrganizationDto organization = getOrganization(dbSession, request);
+      userSession.checkOrganizationPermission(organization.getUuid(), UserRole.ADMIN);
+
+      long nbOfProjects = dbClient.componentDao().countGhostProjects(dbSession, organization.getUuid(), query);
+      List<ComponentDto> projects = dbClient.componentDao().selectGhostProjects(dbSession, organization.getUuid(), query,
+        searchOptions.getOffset(), searchOptions.getLimit());
       JsonWriter json = response.newJsonWriter().beginObject();
       writeProjects(json, projects, desiredFields);
       searchOptions.writeJson(json, nbOfProjects);
       json.endObject().close();
-    } finally {
-      MyBatis.closeQuietly(dbSession);
     }
   }
 
+  private OrganizationDto getOrganization(DbSession dbSession, Request request) {
+    String organizationKey = request.getParam(PARAM_ORGANIZATION)
+      .or(defaultOrganizationProvider.get()::getKey);
+    return checkFoundWithOptional(
+      dbClient.organizationDao().selectByKey(dbSession, organizationKey),
+      "No organization for key '%s'", organizationKey);
+  }
+
   private static void writeProjects(JsonWriter json, List<ComponentDto> projects, Set<String> fieldsToReturn) {
     json.name("projects");
     json.beginArray();
index 7784dd3e565fa66807b3d104fa668481dc06cde0..6d352a59f03e67c8edf2d338f0659f31e0a727ba 100644 (file)
 package org.sonar.server.project.ws;
 
 import com.google.common.io.Resources;
+import java.util.function.Consumer;
 import org.apache.commons.lang.StringUtils;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
@@ -35,98 +38,157 @@ import org.sonar.db.component.SnapshotDto;
 import org.sonar.db.component.SnapshotTesting;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.ws.WsTester;
-import org.sonar.test.JsonAssert;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
+import static org.sonar.test.JsonAssert.assertJson;
 
 public class GhostsActionTest {
 
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
-
   @Rule
   public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
 
-  DbClient dbClient = db.getDbClient();
-  WsTester ws = new WsTester(new ProjectsWs(new GhostsAction(dbClient, userSessionRule)));
+  private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
+  private DbClient dbClient = db.getDbClient();
+  private WsActionTester underTest = new WsActionTester(new GhostsAction(dbClient, userSessionRule, defaultOrganizationProvider));
+
+  @Test
+  public void verify_definition() {
+    WebService.Action action = underTest.getDef();
+    assertThat(action.description()).isEqualTo("List ghost projects.<br /> Requires 'Administer System' permission.");
+    assertThat(action.since()).isEqualTo("5.2");
+    assertThat(action.isInternal()).isFalse();
+
+    assertThat(action.params()).hasSize(5);
+
+    Param organization = action.param("organization");
+    assertThat(organization.description()).isEqualTo("the organization key");
+    assertThat(organization.since()).isEqualTo("6.3");
+    assertThat(organization.isRequired()).isFalse();
+    assertThat(organization.isInternal()).isTrue();
+  }
 
   @Test
   public void ghost_projects_without_analyzed_projects() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
-    insertNewGhostProject("1");
-    insertNewGhostProject("2");
-    insertNewActiveProject("3");
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto ghost1 = insertGhostProject(organization);
+    ComponentDto ghost2 = insertGhostProject(organization);
+    ComponentDto activeProject = insertActiveProject(organization);
+    userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
 
-    WsTester.Result result = ws.newGetRequest("api/projects", "ghosts").execute();
+    TestResponse result = underTest.newRequest()
+      .setParam("organization", organization.getKey())
+      .execute();
 
-    result.assertJson(getClass(), "all-projects.json");
-    assertThat(result.outputAsString()).doesNotContain("analyzed-uuid-3");
+    String json = result.getInput();
+    assertJson(json).isSimilarTo("{" +
+      "  \"projects\": [" +
+      "    {" +
+      "      \"uuid\": \"" + ghost1.uuid() + "\"," +
+      "      \"key\": \"" + ghost1.key() + "\"," +
+      "      \"name\": \"" + ghost1.name() + "\"" +
+      "    }," +
+      "    {" +
+      "      \"uuid\": \"" + ghost2.uuid() + "\"," +
+      "      \"key\": \"" + ghost2.key() + "\"," +
+      "      \"name\": \"" + ghost2.name() + "\"" +
+      "    }" +
+      "  ]" +
+      "}");
+    assertThat(json).doesNotContain(activeProject.uuid());
   }
 
   @Test
   public void ghost_projects_with_correct_pagination() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+    OrganizationDto organization = db.organizations().insert();
     for (int i = 1; i <= 10; i++) {
-      insertNewGhostProject(String.valueOf(i));
+      int count = i;
+      insertGhostProject(organization, dto -> dto.setKey("ghost-key-" + count));
     }
+    userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
 
-    WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+    TestResponse result = underTest.newRequest()
+      .setParam("organization", organization.getKey())
       .setParam(Param.PAGE, "3")
       .setParam(Param.PAGE_SIZE, "4")
       .execute();
 
-    result.assertJson(getClass(), "pagination.json");
-    assertThat(StringUtils.countMatches(result.outputAsString(), "ghost-uuid-")).isEqualTo(2);
+    String json = result.getInput();
+    assertJson(json).isSimilarTo("{" +
+      "  \"p\": 3," +
+      "  \"ps\": 4," +
+      "  \"total\": 10" +
+      "}");
+    assertThat(StringUtils.countMatches(json, "ghost-key-")).isEqualTo(2);
   }
 
   @Test
   public void ghost_projects_with_chosen_fields() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
-    insertNewGhostProject("1");
+    OrganizationDto organization = db.organizations().insert();
+    insertGhostProject(organization);
+    userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
 
-    WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+    TestResponse result = underTest.newRequest()
+      .setParam("organization", organization.getKey())
       .setParam(Param.FIELDS, "name")
       .execute();
 
-    assertThat(result.outputAsString()).contains("uuid", "name")
+    assertThat(result.getInput())
+      .contains("uuid", "name")
       .doesNotContain("key")
       .doesNotContain("creationDate");
   }
 
   @Test
   public void ghost_projects_with_partial_query_on_name() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+    OrganizationDto organization = db.organizations().insert();
+    insertGhostProject(organization, dto -> dto.setName("ghost-name-10"));
+    insertGhostProject(organization, dto -> dto.setName("ghost-name-11"));
+    insertGhostProject(organization, dto -> dto.setName("ghost-name-20"));
 
-    insertNewGhostProject("10");
-    insertNewGhostProject("11");
-    insertNewGhostProject("2");
+    userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
 
-    WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+    TestResponse result = underTest.newRequest()
+      .setParam("organization", organization.getKey())
       .setParam(Param.TEXT_QUERY, "name-1")
       .execute();
 
-    assertThat(result.outputAsString()).contains("ghost-name-10", "ghost-name-11")
+    assertThat(result.getInput())
+      .contains("ghost-name-10", "ghost-name-11")
       .doesNotContain("ghost-name-2");
   }
 
   @Test
   public void ghost_projects_with_partial_query_on_key() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+    OrganizationDto organization = db.organizations().insert();
+    insertGhostProject(organization, dto -> dto.setKey("ghost-key-1"));
 
-    insertNewGhostProject("1");
+    userSessionRule.login().addOrganizationPermission(organization, SYSTEM_ADMIN);
 
-    WsTester.Result result = ws.newGetRequest("api/projects", "ghosts")
+    TestResponse result = underTest.newRequest()
+      .setParam("organization", organization.getKey())
       .setParam(Param.TEXT_QUERY, "GHOST-key")
       .execute();
 
-    assertThat(result.outputAsString()).contains("ghost-key-1");
+    assertThat(result.getInput())
+      .contains("ghost-key-1");
   }
 
   @Test
   public void ghost_projects_base_on_json_example() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     OrganizationDto organizationDto = db.organizations().insert();
     ComponentDto hBaseProject = ComponentTesting.newProjectDto(organizationDto, "ce4c03d6-430f-40a9-b777-ad877c00aa4d")
       .setKey("org.apache.hbas:hbase")
@@ -134,38 +196,62 @@ public class GhostsActionTest {
       .setCreatedAt(DateUtils.parseDateTime("2015-03-04T23:03:44+0100"));
     dbClient.componentDao().insert(db.getSession(), hBaseProject);
     dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(hBaseProject)
-      .setStatus(SnapshotDto.STATUS_UNPROCESSED));
+      .setStatus(STATUS_UNPROCESSED));
     ComponentDto roslynProject = ComponentTesting.newProjectDto(organizationDto, "c526ef20-131b-4486-9357-063fa64b5079")
       .setKey("com.microsoft.roslyn:roslyn")
       .setName("Roslyn")
       .setCreatedAt(DateUtils.parseDateTime("2013-03-04T23:03:44+0100"));
     dbClient.componentDao().insert(db.getSession(), roslynProject);
     dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(roslynProject)
-      .setStatus(SnapshotDto.STATUS_UNPROCESSED));
+      .setStatus(STATUS_UNPROCESSED));
     db.getSession().commit();
+    userSessionRule.login().addOrganizationPermission(organizationDto, SYSTEM_ADMIN);
 
-    WsTester.Result result = ws.newGetRequest("api/projects", "ghosts").execute();
+    TestResponse result = underTest.newRequest()
+      .setParam("organization", organizationDto.getKey())
+      .execute();
 
-    JsonAssert.assertJson(result.outputAsString()).isSimilarTo(Resources.getResource(getClass(), "projects-example-ghosts.json"));
+    assertJson(result.getInput())
+      .isSimilarTo(Resources.getResource(getClass(), "projects-example-ghosts.json"));
   }
 
   @Test(expected = ForbiddenException.class)
   public void fail_if_does_not_have_sufficient_rights() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.USER, UserRole.ISSUE_ADMIN, UserRole.CODEVIEWER);
+    userSessionRule.login()
+      .addOrganizationPermission(db.getDefaultOrganization(), UserRole.USER)
+      .addOrganizationPermission(db.getDefaultOrganization(), UserRole.ISSUE_ADMIN)
+      .addOrganizationPermission(db.getDefaultOrganization(), UserRole.CODEVIEWER);
 
-    ws.newGetRequest("api/projects", "ghosts").execute();
+    underTest.newRequest().execute();
   }
 
-  private void insertNewGhostProject(String id) {
-    ComponentDto project = ComponentTesting
-      .newProjectDto(db.organizations().insert(), "ghost-uuid-" + id)
-      .setName("ghost-name-" + id)
-      .setKey("ghost-key-" + id);
-    dbClient.componentDao().insert(db.getSession(), project);
-    SnapshotDto snapshot = SnapshotTesting.newAnalysis(project)
-      .setStatus(SnapshotDto.STATUS_UNPROCESSED);
-    dbClient.snapshotDao().insert(db.getSession(), snapshot);
-    db.getSession().commit();
+  @Test
+  public void fail_with_NotFoundException_when_organization_with_specified_key_does_not_exist() {
+    TestRequest request = underTest.newRequest()
+        .setParam("organization", "foo");
+    userSessionRule.login();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("No organization for key 'foo'");
+
+    request.execute();
+  }
+
+  private ComponentDto insertGhostProject(OrganizationDto organization) {
+    return insertGhostProject(organization, dto -> {
+    });
+  }
+
+  private ComponentDto insertGhostProject(OrganizationDto organization, Consumer<ComponentDto> consumer) {
+    ComponentDto project = db.components().insertProject(organization, consumer);
+    db.components().insertSnapshot(project, dto -> dto.setStatus(STATUS_UNPROCESSED));
+    return project;
+  }
+
+  private ComponentDto insertActiveProject(OrganizationDto organization) {
+    ComponentDto project = db.components().insertProject(organization);
+    db.components().insertSnapshot(project, dto -> dto.setStatus(STATUS_PROCESSED));
+    return project;
   }
 
   private void insertNewActiveProject(String id) {
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/all-projects.json
deleted file mode 100644 (file)
index cd1aad1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "projects": [
-    {
-      "uuid": "ghost-uuid-1",
-      "key": "ghost-key-1",
-      "name": "ghost-name-1"
-    },
-    {
-      "uuid": "ghost-uuid-2",
-      "key": "ghost-key-2",
-      "name": "ghost-name-2"
-    }
-  ]
-}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json b/server/sonar-server/src/test/resources/org/sonar/server/project/ws/GhostsActionTest/pagination.json
deleted file mode 100644 (file)
index 8967d5c..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "p": 3,
-  "ps": 4,
-  "total": 10
-}
index d475e9ead20b82e2865869e312f6942fe85f2b4a..7deb692a59ab42fa5f470746f23323dc054b89c8 100644 (file)
@@ -41,7 +41,6 @@ import org.sonar.db.DbSession;
 import org.sonar.db.RowNotFoundException;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Maps.newHashMapWithExpectedSize;
 import static java.util.Collections.emptyList;
 import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.StringUtils.isBlank;
@@ -263,20 +262,12 @@ public class ComponentDao implements Dao {
     return DatabaseUtils.buildLikeValue(textQuery.toUpperCase(Locale.ENGLISH), BEFORE_AND_AFTER);
   }
 
-  public List<ComponentDto> selectGhostProjects(DbSession session, int offset, int limit, @Nullable String query) {
-    Map<String, Object> parameters = newHashMapWithExpectedSize(2);
-    addProjectQualifier(parameters);
-    addPartialQueryParameterIfNotNull(parameters, query);
-
-    return mapper(session).selectGhostProjects(parameters, new RowBounds(offset, limit));
+  public List<ComponentDto> selectGhostProjects(DbSession session, String organizationUuid, @Nullable String query, int offset, int limit) {
+    return mapper(session).selectGhostProjects(organizationUuid, queryParameterFrom(query), new RowBounds(offset, limit));
   }
 
-  public long countGhostProjects(DbSession session, @Nullable String query) {
-    Map<String, Object> parameters = newHashMapWithExpectedSize(2);
-    addProjectQualifier(parameters);
-    addPartialQueryParameterIfNotNull(parameters, query);
-
-    return mapper(session).countGhostProjects(parameters);
+  public long countGhostProjects(DbSession session, String organizationUuid, @Nullable String query) {
+    return mapper(session).countGhostProjects(organizationUuid, queryParameterFrom(query));
   }
 
   /**
@@ -317,12 +308,16 @@ public class ComponentDao implements Dao {
   private static void addPartialQueryParameterIfNotNull(Map<String, Object> parameters, @Nullable String keyOrNameFilter) {
     // TODO rely on resource_index table and match exactly the key
     if (keyOrNameFilter != null) {
-      parameters.put("query", "%" + keyOrNameFilter.toUpperCase(Locale.ENGLISH) + "%");
+      parameters.put("query", queryParameterFrom(keyOrNameFilter));
     }
   }
 
-  private static void addProjectQualifier(Map<String, Object> parameters) {
-    parameters.put("qualifier", Qualifiers.PROJECT);
+  @CheckForNull
+  private static String queryParameterFrom(@Nullable String keyOrNameFilter) {
+    if (keyOrNameFilter != null) {
+      return "%" + keyOrNameFilter.toUpperCase(Locale.ENGLISH) + "%";
+    }
+    return null;
   }
 
   public void insert(DbSession session, ComponentDto item) {
index 2cddadffbd43ba201728beb7b31efddb22410061..23bda21a9e97209cf88fd1d7ba16521246597f04 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.db.component;
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -122,14 +121,14 @@ public interface ComponentMapper {
 
   int countProvisioned(@Param("organizationUuid") String organizationUuid, @Nullable @Param("keyOrNameLike") String keyOrNameLike, @Param("qualifiers") Set<String> qualifiers);
 
-  List<ComponentDto> selectGhostProjects(Map<String, Object> parameters, RowBounds rowBounds);
+  List<ComponentDto> selectGhostProjects(@Param("organizationUuid") String organizationUuid, @Nullable @Param("query") String query, RowBounds rowBounds);
+
+  long countGhostProjects(@Param("organizationUuid") String organizationUuid, @Nullable @Param("query") String query);
 
   List<ComponentDto> selectComponentsHavingSameKeyOrderedById(String key);
 
   List<ComponentDto> selectProjectsByNameQuery(@Param("nameQuery") @Nullable String nameQuery, @Param("includeModules") boolean includeModules);
 
-  long countGhostProjects(Map<String, Object> parameters);
-
   void selectForIndexing(@Param("projectUuid") @Nullable String projectUuid, ResultHandler handler);
 
   void insert(ComponentDto componentDto);
index b8a97b3df49c20ece469f090485cb5deecea8910..79b19813fffd66ef4d79ef88ea303c135b264fdd 100644 (file)
 
   <select id="selectGhostProjects" parameterType="map" resultType="Component">
     select distinct
-    <include refid="componentColumns"/>
+      <include refid="componentColumns"/>
     from projects p
-    <include refid="ghostClauses"/>
+      <include refid="ghostProjectClauses"/>
   </select>
 
   <select id="countGhostProjects" parameterType="map" resultType="long">
-    select count(p.id)
+    select
+      count(distinct p.id)
     from projects p
-    <include refid="ghostClauses"/>
+      <include refid="ghostProjectClauses"/>
   </select>
 
-  <sql id="ghostClauses">
+  <sql id="ghostProjectClauses">
     inner join snapshots s1 on s1.component_uuid = p.uuid and s1.status='U'
     left join snapshots s2 on s2.component_uuid = p.uuid and s2.status='P'
     where
     s2.id is null
-    and p.qualifier=#{qualifier,jdbcType=VARCHAR}
+    and p.qualifier='TRK'
     and p.copy_component_uuid is null
     <if test="query!=null">
       and (
index 646a54be31a467d72913981c5b51509541c70c7b..2ac7254b0ce525c50cf2671ed16a91d7b3372b10 100644 (file)
@@ -49,6 +49,7 @@ import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.guava.api.Assertions.assertThat;
 import static org.sonar.db.component.ComponentTesting.newDeveloper;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newProjectCopy;
@@ -596,13 +597,39 @@ public class ComponentDaoTest {
 
   @Test
   public void select_ghost_projects() {
-    db.prepareDbUnit(getClass(), "select_ghost_projects.xml");
-
-    List<ComponentDto> result = underTest.selectGhostProjects(dbSession, 0, 10, null);
+    OrganizationDto organization = db.organizations().insert();
 
-    assertThat(result).hasSize(1);
-    assertThat(result.get(0).key()).isEqualTo("org.ghost.project");
-    assertThat(underTest.countGhostProjects(dbSession, null)).isEqualTo(1);
+    // ghosts because has at least one snapshot with status U but none with status P
+    ComponentDto ghostProject = db.components().insertProject(organization);
+    db.components().insertSnapshot(ghostProject, dto -> dto.setStatus("U"));
+    db.components().insertSnapshot(ghostProject, dto -> dto.setStatus("U"));
+    ComponentDto ghostProject2 = db.components().insertProject(organization);
+    db.components().insertSnapshot(ghostProject2, dto -> dto.setStatus("U"));
+    ComponentDto disabledGhostProject = db.components().insertProject(dto -> dto.setEnabled(false));
+    db.components().insertSnapshot(disabledGhostProject, dto -> dto.setStatus("U"));
+
+    ComponentDto project1 = db.components().insertProject(organization);
+    db.components().insertSnapshot(project1, dto -> dto.setStatus("P"));
+    db.components().insertSnapshot(project1, dto -> dto.setStatus("U"));
+    ComponentDto module = db.components().insertComponent(newModuleDto(project1));
+    ComponentDto dir = db.components().insertComponent(newDirectory(module, "foo"));
+    db.components().insertComponent(newFileDto(module, dir, "bar"));
+
+    ComponentDto provisionedProject = db.components().insertProject(organization);
+
+    // not a ghost because has at least one snapshot with status P
+    ComponentDto project2 = db.components().insertProject(organization);
+    db.components().insertSnapshot(project2, dto -> dto.setStatus("P"));
+
+    // not a ghost because it's not a project
+    ComponentDto view = db.components().insertView(organization);
+    db.components().insertSnapshot(view, dto -> dto.setStatus("U"));
+    db.components().insertComponent(newProjectCopy("do", project1, view));
+
+    assertThat(underTest.selectGhostProjects(dbSession, organization.getUuid(), null, 0, 10))
+      .extracting(ComponentDto::uuid)
+      .containsOnly(ghostProject.uuid(), ghostProject2.uuid(), disabledGhostProject.uuid());
+    assertThat(underTest.countGhostProjects(dbSession, organization.getUuid(), null)).isEqualTo(3);
   }
 
   @Test
index 67d438cb847286fc3200b0c82f572022e4925f3d..d8f93680b66f64d614f32c055e8f0307cfcb130f 100644 (file)
@@ -95,7 +95,7 @@ public class ComponentDbTester {
   }
 
   public ComponentDto insertView(OrganizationDto organizationDto) {
-    return insertComponentImpl(newView(organizationDto), dto -> {});
+    return insertComponentImpl(newView(organizationDto), noExtraConfiguration());
   }
 
   public ComponentDto insertView(OrganizationDto organizationDto, Consumer<ComponentDto> dtoPopulator) {
@@ -111,7 +111,7 @@ public class ComponentDbTester {
   }
 
   public ComponentDto insertDeveloper(String name, Consumer<ComponentDto> dtoPopulator) {
-    return insertComponentImpl(newDeveloper(db.getDefaultOrganization(), name), noExtraConfiguration());
+    return insertComponentImpl(newDeveloper(db.getDefaultOrganization(), name), dtoPopulator);
   }
 
   public ComponentDto insertDeveloper(String name) {
@@ -145,4 +145,16 @@ public class ComponentDbTester {
     db.commit();
     return snapshot;
   }
+
+  public SnapshotDto insertSnapshot(ComponentDto componentDto) {
+    return insertSnapshot(componentDto, noExtraConfiguration());
+  }
+
+  public SnapshotDto insertSnapshot(ComponentDto componentDto, Consumer<SnapshotDto> consumer) {
+    SnapshotDto snapshotDto = SnapshotTesting.newAnalysis(componentDto);
+    consumer.accept(snapshotDto);
+    SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, snapshotDto);
+    db.commit();
+    return snapshot;
+  }
 }
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml b/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/select_ghost_projects.xml
deleted file mode 100644 (file)
index f5ad3ef..0000000
+++ /dev/null
@@ -1,335 +0,0 @@
-<dataset>
-
-  <!-- Struts projects is authorized for all user -->
-  <group_roles id="1"
-               group_id="[null]"
-               resource_id="1"
-               role="user"
-               organization_uuid="org1"/>
-
-  <!-- Ghost project -->
-  <projects organization_uuid="org1"
-            id="42"
-            root_uuid="PPAA"
-            scope="PRJ"
-            qualifier="TRK"
-            kee="org.ghost.project"
-            name="Ghost Project"
-            uuid="PPAA"
-            uuid_path="NOT_USED"
-            project_uuid="PPAA"
-            module_uuid="[null]"
-            module_uuid_path="."
-            description="the description"
-            long_name="Ghost Project"
-            enabled="[true]"
-            language="[null]"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            path="[null]"
-            authorization_updated_at="123456789"/>
-
-  <!-- root project -->
-  <projects organization_uuid="org1"
-            id="1"
-            root_uuid="ABCD"
-            scope="PRJ"
-            qualifier="TRK"
-            kee="org.struts:struts"
-            deprecated_kee="org.struts:struts"
-            name="Struts"
-            uuid="ABCD"
-            uuid_path="NOT_USED"
-            project_uuid="ABCD"
-            module_uuid="[null]"
-            module_uuid_path=".ABCD."
-            description="the description"
-            long_name="Apache Struts"
-            enabled="[true]"
-            language="[null]"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            path="[null]"
-            authorization_updated_at="123456789"/>
-  <snapshots id="1"
-             uuid="u1"
-             component_uuid="ABCD"
-             status="P"
-             islast="[true]"
-             purge_status="[null]"
-             period1_mode="[null]"
-             period1_param="[null]"
-             period1_date="[null]"
-             period2_mode="[null]"
-             period2_param="[null]"
-             period2_date="[null]"
-             period3_mode="[null]"
-             period3_param="[null]"
-             period3_date="[null]"
-             period4_mode="[null]"
-             period4_param="[null]"
-             period4_date="[null]"
-             period5_mode="[null]"
-             period5_param="[null]"
-             period5_date="[null]"
-             created_at="1228222680000"
-             build_date="1228222680000"
-             version="[null]"
-             />
-  <snapshots id="10"
-             uuid="u10"
-             component_uuid="ABCD"
-             status="P"
-             islast="[false]"
-             purge_status="[null]"
-             period1_mode="[null]"
-             period1_param="[null]"
-             period1_date="[null]"
-             period2_mode="[null]"
-             period2_param="[null]"
-             period2_date="[null]"
-             period3_mode="[null]"
-             period3_param="[null]"
-             period3_date="[null]"
-             period4_mode="[null]"
-             period4_param="[null]"
-             period4_date="[null]"
-             period5_mode="[null]"
-             period5_param="[null]"
-             period5_date="[null]"
-             created_at="1228136280000"
-             build_date="1228136280000"
-             version="[null]"
-             />
-  <snapshots id="11"
-             uuid="u11"
-             component_uuid="PPAA"
-             status="U"
-             islast="[false]"
-             purge_status="[null]"
-             period1_mode="[null]"
-             period1_param="[null]"
-             period1_date="[null]"
-             period2_mode="[null]"
-             period2_param="[null]"
-             period2_date="[null]"
-             period3_mode="[null]"
-             period3_param="[null]"
-             period3_date="[null]"
-             period4_mode="[null]"
-             period4_param="[null]"
-             period4_date="[null]"
-             period5_mode="[null]"
-             period5_param="[null]"
-             period5_date="[null]"
-             created_at="1228136280000"
-             build_date="1228136280000"
-             version="[null]"
-             />
-
-  <!-- module -->
-  <projects organization_uuid="org1"
-            id="2"
-            root_uuid="ABCD"
-            kee="org.struts:struts-core"
-            name="Struts Core"
-            uuid="EFGH"
-            uuid_path="NOT_USED"
-            project_uuid="ABCD"
-            module_uuid="[null]"
-            module_uuid_path=".ABCD.EFGH."
-            scope="PRJ"
-            qualifier="BRC"
-            long_name="Struts Core"
-            description="[null]"
-            enabled="[true]"
-            language="[null]"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            authorization_updated_at="[null]"/>
-  <snapshots id="2"
-             uuid="u2"
-             component_uuid="EFGH"
-             status="P"
-             islast="[true]"
-             purge_status="[null]"
-             period1_mode="[null]"
-             period1_param="[null]"
-             period1_date="[null]"
-             period2_mode="[null]"
-             period2_param="[null]"
-             period2_date="[null]"
-             period3_mode="[null]"
-             period3_param="[null]"
-             period3_date="[null]"
-             period4_mode="[null]"
-             period4_param="[null]"
-             period4_date="[null]"
-             period5_mode="[null]"
-             period5_param="[null]"
-             period5_date="[null]"
-             created_at="1228222680000"
-             build_date="1228222680000"
-             version="[null]"
-             />
-
-  <!-- directory -->
-  <projects organization_uuid="org1"
-            long_name="org.struts"
-            id="3"
-            scope="DIR"
-            qualifier="DIR"
-            kee="org.struts:struts-core:src/org/struts"
-            uuid="GHIJ"
-            uuid_path="NOT_USED"
-            project_uuid="ABCD"
-            module_uuid="EFGH"
-            module_uuid_path=".ABCD.EFGH."
-            name="src/org/struts"
-            root_uuid="EFGH"
-            description="[null]"
-            enabled="[true]"
-            language="[null]"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            path="src/org/struts"
-            authorization_updated_at="[null]"/>
-  <snapshots id="3"
-             uuid="u3"
-             component_uuid="GHIJ"
-             status="P"
-             islast="[true]"
-             purge_status="[null]"
-             period1_mode="[null]"
-             period1_param="[null]"
-             period1_date="[null]"
-             period2_mode="[null]"
-             period2_param="[null]"
-             period2_date="[null]"
-             period3_mode="[null]"
-             period3_param="[null]"
-             period3_date="[null]"
-             period4_mode="[null]"
-             period4_param="[null]"
-             period4_date="[null]"
-             period5_mode="[null]"
-             period5_param="[null]"
-             period5_date="[null]"
-             created_at="1228222680000"
-             build_date="1228222680000"
-             version="[null]"
-             />
-
-  <!-- file -->
-  <projects organization_uuid="org1"
-            long_name="org.struts.RequestContext"
-            id="4"
-            scope="FIL"
-            qualifier="FIL"
-            kee="org.struts:struts-core:src/org/struts/RequestContext.java"
-            uuid="KLMN"
-            uuid_path="NOT_USED"
-            project_uuid="ABCD"
-            module_uuid="EFGH"
-            module_uuid_path=".ABCD.EFGH."
-            name="RequestContext.java"
-            root_uuid="EFGH"
-            description="[null]"
-            enabled="[true]"
-            language="java"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            path="src/org/struts/RequestContext.java"
-            authorization_updated_at="[null]"/>
-
-  <snapshots id="4"
-             uuid="u4"
-             component_uuid="KLMN"
-             status="P"
-             islast="[true]"
-             purge_status="[null]"
-             period1_mode="[null]"
-             period1_param="[null]"
-             period1_date="[null]"
-             period2_mode="[null]"
-             period2_param="[null]"
-             period2_date="[null]"
-             period3_mode="[null]"
-             period3_param="[null]"
-             period3_date="[null]"
-             period4_mode="[null]"
-             period4_param="[null]"
-             period4_date="[null]"
-             period5_mode="[null]"
-             period5_param="[null]"
-             period5_date="[null]"
-             created_at="1228222680000"
-             build_date="1228222680000"
-             version="[null]"
-             />
-
-  <!-- Disabled projects -->
-  <projects organization_uuid="org1"
-            id="10"
-            root_uuid="DCBA"
-            scope="PRJ"
-            qualifier="TRK"
-            kee="org.disabled.project"
-            name="Disabled Project"
-            uuid="DCBA"
-            uuid_path="NOT_USED"
-            project_uuid="DCBA"
-            module_uuid="[null]"
-            module_uuid_path="."
-            description="the description"
-            long_name="Disabled project"
-            enabled="[false]"
-            language="[null]"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            path="[null]"
-            authorization_updated_at="123456789"/>
-
-  <!-- Developer and technical project copy -->
-  <projects organization_uuid="org1"
-            id="11"
-            root_uuid="OPQR"
-            scope="PRJ"
-            qualifier="DEV"
-            kee="DEV:anakin@skywalker.name"
-            name="Anakin Skywalker"
-            uuid="OPQR"
-            uuid_path="NOT_USED"
-            project_uuid="OPQR"
-            module_uuid="[null]"
-            module_uuid_path=".OPQR."
-            description="the description"
-            long_name="Anakin Skywalker"
-            enabled="[true]"
-            language="[null]"
-            copy_component_uuid="[null]"
-            developer_uuid="[null]"
-            path="[null]"
-            authorization_updated_at="123456789"/>
-  <projects organization_uuid="org1"
-            id="12"
-            root_uuid="OPQR"
-            scope="PRJ"
-            qualifier="DEV_PRJ"
-            kee="DEV:anakin@skywalker.name:org.struts:struts"
-            name="Apache Struts"
-            uuid="STUV"
-            uuid_path="NOT_USED"
-            project_uuid="OPQR"
-            module_uuid="OPQR"
-            module_uuid_path=".OPQR."
-            description="the description"
-            long_name="Apache Struts"
-            enabled="[true]"
-            language="[null]"
-            copy_component_uuid="ABCD"
-            developer_uuid="OPQR"
-            path="[null]"
-            authorization_updated_at="123456789"/>
-
-</dataset>