From b3ca2478b4cb39787d1be6e2e91463b9869b8908 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Fri, 6 Jul 2012 17:09:30 +0200 Subject: [PATCH] SONAR-3618 support old versions of Views plugin --- .../security/ApplyProjectRolesDecorator.java | 12 ++- .../DefaultResourcePermissioning.java | 32 ++++++- .../ApplyProjectRolesDecoratorTest.java | 48 +++++----- .../DefaultResourcePermissioningTest.java | 44 +++++++++ .../org/sonar/api/resources/ResourceType.java | 5 + .../sonar/api/resources/ResourceTypes.java | 27 +++++- .../sonar/api/resources/ResourceTypeTest.java | 9 ++ .../api/resources/ResourceTypesTest.java | 91 ++++++++++--------- .../WEB-INF/app/views/roles/projects.html.erb | 2 +- 9 files changed, 196 insertions(+), 74 deletions(-) diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/ApplyProjectRolesDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/ApplyProjectRolesDecorator.java index 1e6a5519782..4e6fc53d52b 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/ApplyProjectRolesDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/ApplyProjectRolesDecorator.java @@ -19,16 +19,22 @@ */ package org.sonar.plugins.core.security; +import com.google.common.collect.ImmutableSet; +import org.slf4j.LoggerFactory; import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.resources.Project; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceTypes; import org.sonar.api.security.ResourcePermissioning; +import java.util.Set; + public class ApplyProjectRolesDecorator implements Decorator { private final ResourcePermissioning resourcePermissioning; + private final Set QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.VIEW, Qualifiers.SUBVIEW); public ApplyProjectRolesDecorator(ResourcePermissioning resourcePermissioning) { this.resourcePermissioning = resourcePermissioning; @@ -40,15 +46,13 @@ public class ApplyProjectRolesDecorator implements Decorator { public void decorate(Resource resource, DecoratorContext context) { if (shouldDecorateResource(resource)) { + LoggerFactory.getLogger(ApplyProjectRolesDecorator.class).info("Grant default permissions to {}", resource.getKey()); resourcePermissioning.grantDefaultRoles(resource); } } private boolean shouldDecorateResource(Resource resource) { - return resource.getId() != null && isProject(resource) && !resourcePermissioning.hasRoles(resource); + return resource.getId() != null && QUALIFIERS.contains(resource.getQualifier()) && !resourcePermissioning.hasRoles(resource); } - private boolean isProject(Resource resource) { - return Qualifiers.PROJECT.equals(resource.getQualifier()) || Qualifiers.VIEW.equals(resource.getQualifier()); - } } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/DefaultResourcePermissioning.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/DefaultResourcePermissioning.java index 15777e3075c..123869b49d0 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/DefaultResourcePermissioning.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/DefaultResourcePermissioning.java @@ -19,11 +19,13 @@ */ package org.sonar.plugins.core.security; +import com.google.common.annotations.VisibleForTesting; import org.apache.ibatis.session.SqlSession; import org.sonar.api.BatchExtension; import org.sonar.api.Properties; import org.sonar.api.Property; import org.sonar.api.config.Settings; +import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; import org.sonar.api.security.DefaultGroups; import org.sonar.api.security.ResourcePermissioning; @@ -143,7 +145,9 @@ public class DefaultResourcePermissioning implements ResourcePermissioning, Batc private void grantDefaultRoles(Resource resource, String role, SqlSession session) { UserMapper userMapper = session.getMapper(UserMapper.class); RoleMapper roleMapper = session.getMapper(RoleMapper.class); - String[] groupNames = settings.getStringArrayBySeparator("sonar.role." + role + "." + resource.getQualifier() + ".defaultGroups", ","); + + String strategy = getStrategy(resource); + String[] groupNames = settings.getStringArrayBySeparator("sonar.role." + role + "." + strategy + ".defaultGroups", ","); for (String groupName : groupNames) { GroupRoleDto groupRole = new GroupRoleDto().setRole(role).setResourceId(new Long(resource.getId())); if (DefaultGroups.isAnyone(groupName)) { @@ -156,7 +160,7 @@ public class DefaultResourcePermissioning implements ResourcePermissioning, Batc } } - String[] logins = settings.getStringArrayBySeparator("sonar.role." + role + "." + resource.getQualifier() + ".defaultUsers", ","); + String[] logins = settings.getStringArrayBySeparator("sonar.role." + role + "." + strategy + ".defaultUsers", ","); for (String login : logins) { UserDto user = userMapper.selectUserByLogin(login); if (user != null) { @@ -164,4 +168,28 @@ public class DefaultResourcePermissioning implements ResourcePermissioning, Batc } } } + + /** + * This is workaround to support old versions of the Views plugin. + * If the Views plugin does not define default permissions, then the standard permissions are re-used for new views. + */ + @VisibleForTesting + String getStrategy(Resource resource) { + String qualifier = resource.getQualifier(); + String result; + if (Qualifiers.PROJECT.equals(qualifier)) { + result = qualifier; + + } else if (hasRoleSettings(UserRole.ADMIN, qualifier) || hasRoleSettings(UserRole.USER, qualifier) || hasRoleSettings(UserRole.CODEVIEWER, qualifier)) { + result = qualifier; + } else { + result = Qualifiers.PROJECT; + } + return result; + } + + private boolean hasRoleSettings(String role, String qualifier) { + return settings.getString("sonar.role." + role + "." + qualifier + ".defaultGroups") != null + || settings.getString("sonar.role." + role + "." + qualifier + ".defaultUsers") != null; + } } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/ApplyProjectRolesDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/ApplyProjectRolesDecoratorTest.java index 1541f7eb6b8..424e2d97e0b 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/ApplyProjectRolesDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/ApplyProjectRolesDecoratorTest.java @@ -1,22 +1,22 @@ /* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2012 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ +* Sonar, open source software quality management tool. +* Copyright (C) 2008-2012 SonarSource +* mailto:contact AT sonarsource DOT com +* +* Sonar 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. +* +* Sonar 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 Sonar; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 +*/ package org.sonar.plugins.core.security; import org.junit.Before; @@ -24,6 +24,7 @@ import org.junit.Test; import org.sonar.api.resources.Project; import org.sonar.api.security.ResourcePermissioning; +import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.*; public class ApplyProjectRolesDecoratorTest { @@ -32,13 +33,18 @@ public class ApplyProjectRolesDecoratorTest { private ApplyProjectRolesDecorator decorator; @Before - public void before() { + public void init() { resourcePermissioning = mock(ResourcePermissioning.class); decorator = new ApplyProjectRolesDecorator(resourcePermissioning); } @Test - public void doNotApplySecurityWhenExistingPermissions() { + public void alwaysExecute() { + assertThat(decorator.shouldExecuteOnProject(new Project("project"))).isTrue(); + } + + @Test + public void doNotGrantDefaultRolesWhenExistingPermissions() { Project project = new Project("project"); project.setId(10); when(resourcePermissioning.hasRoles(project)).thenReturn(true); @@ -61,7 +67,7 @@ public class ApplyProjectRolesDecoratorTest { } @Test - public void applySecurityWhenNoPermissions() { + public void grantDefaultRolesWhenNoPermissions() { Project project = new Project("project"); project.setId(10); when(resourcePermissioning.hasRoles(project)).thenReturn(false); diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/DefaultResourcePermissioningTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/DefaultResourcePermissioningTest.java index e671f9bdc19..a67277fc376 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/DefaultResourcePermissioningTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/security/DefaultResourcePermissioningTest.java @@ -20,14 +20,19 @@ package org.sonar.plugins.core.security; import org.junit.Test; +import org.sonar.api.Properties; +import org.sonar.api.Property; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; import org.sonar.api.security.DefaultGroups; import org.sonar.core.persistence.AbstractDaoTestCase; import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class DefaultResourcePermissioningTest extends AbstractDaoTestCase { @@ -129,4 +134,43 @@ public class DefaultResourcePermissioningTest extends AbstractDaoTestCase { // does not exist assertThat(permissioning.hasRoles(new Project("not_found"))).isFalse(); } + + @Test + public void use_default_project_roles_when_old_version_of_views_plugin() { + DefaultResourcePermissioning permissioning = new DefaultResourcePermissioning(new Settings(), getMyBatis()); + Resource view = mock(Resource.class); + when(view.getQualifier()).thenReturn(Qualifiers.VIEW); + + assertThat(permissioning.getStrategy(view)).isEqualTo(Qualifiers.PROJECT); + } + + @Test + public void use_existing_view_roles() { + Settings settings = new Settings(); + settings.setProperty("sonar.role.admin.VW.defaultUsers", "sonar-administrators"); + + DefaultResourcePermissioning permissioning = new DefaultResourcePermissioning(settings, getMyBatis()); + Resource view = mock(Resource.class); + when(view.getQualifier()).thenReturn(Qualifiers.VIEW); + + assertThat(permissioning.getStrategy(view)).isEqualTo(Qualifiers.VIEW); + } + + @Test + public void use_existing_default_view_roles() { + Settings settings = new Settings(new PropertyDefinitions(RecentViewPlugin.class)); + + DefaultResourcePermissioning permissioning = new DefaultResourcePermissioning(settings, getMyBatis()); + Resource view = mock(Resource.class); + when(view.getQualifier()).thenReturn(Qualifiers.VIEW); + + assertThat(permissioning.getStrategy(view)).isEqualTo(Qualifiers.VIEW); + } + + @Properties({ + @Property(key = "sonar.role.user.VW.defaultUsers", defaultValue = "sonar-users", name = "") + }) + static class RecentViewPlugin { + + } } \ No newline at end of file diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java index e5444ca5452..c876d9f75b5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceType.java @@ -174,6 +174,11 @@ public final class ResourceType { return hasSourceCode; } + public boolean hasProperty(String key) { + Preconditions.checkNotNull(key); + return properties.containsKey(key); + } + /** * Returns the value of the property for this resource type. * diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java index 306042d40d0..5eb933c725b 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/ResourceTypes.java @@ -21,6 +21,7 @@ package org.sonar.api.resources; import com.google.common.annotations.Beta; import com.google.common.base.Function; +import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; @@ -44,7 +45,7 @@ public final class ResourceTypes implements BatchComponent, ServerComponent { public static final Predicate AVAILABLE_FOR_FILTERS = new Predicate() { public boolean apply(@Nullable ResourceType input) { - return input != null && Boolean.TRUE.equals(input.getBooleanProperty("availableForFilters")); + return input != null && input.getBooleanProperty("availableForFilters"); } }; @@ -83,6 +84,30 @@ public final class ResourceTypes implements BatchComponent, ServerComponent { return Collections2.filter(typeByQualifier.values(), predicate); } + public Collection getAllWithPropertyKey(final String propertyKey) { + return Collections2.filter(typeByQualifier.values(), new Predicate() { + public boolean apply(@Nullable ResourceType input) { + return input != null && input.hasProperty(propertyKey); + } + }); + } + + public Collection getAllWithPropertyValue(final String propertyKey, final String propertyValue) { + return Collections2.filter(typeByQualifier.values(), new Predicate() { + public boolean apply(@Nullable ResourceType input) { + return input != null && Objects.equal(propertyValue, input.getStringProperty(propertyKey)); + } + }); + } + + public Collection getAllWithPropertyValue(final String propertyKey, final boolean propertyValue) { + return Collections2.filter(typeByQualifier.values(), new Predicate() { + public boolean apply(@Nullable ResourceType input) { + return input != null && input.getBooleanProperty(propertyKey) == propertyValue; + } + }); + } + public List getChildrenQualifiers(String qualifier) { ResourceTypeTree tree = getTree(qualifier); if (tree != null) { diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTest.java index 998017b75f8..5d3559e9100 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypeTest.java @@ -98,4 +98,13 @@ public class ResourceTypeTest { ResourceType def = ResourceType.builder("qualifier").build(); assertThat(def.getBooleanProperty("availableForFilters")).isFalse(); } + + @Test + public void hasProperty() { + ResourceType def = ResourceType.builder("qualifier").build(); + assertThat(def.hasProperty("foo")).isFalse(); + + def = ResourceType.builder("qualifier").setProperty("foo", "bar").build(); + assertThat(def.hasProperty("foo")).isTrue(); + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java index 6c34aa65a38..eeb96d7402f 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/ResourceTypesTest.java @@ -25,98 +25,99 @@ import org.junit.Test; import java.util.Collection; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNull.nullValue; -import static org.junit.Assert.assertThat; -import static org.junit.internal.matchers.IsCollectionContaining.hasItem; -import static org.junit.internal.matchers.IsCollectionContaining.hasItems; +import static org.fest.assertions.Assertions.assertThat; public class ResourceTypesTest { private ResourceTypeTree viewsTree = ResourceTypeTree.builder() - .addType(ResourceType.builder(Qualifiers.VIEW).setProperty("availableForFilters", "true").build()) - .addType(ResourceType.builder(Qualifiers.SUBVIEW).build()) - .addRelations(Qualifiers.VIEW, Qualifiers.SUBVIEW) - .addRelations(Qualifiers.SUBVIEW, Qualifiers.PROJECT) - .build(); + .addType(ResourceType.builder(Qualifiers.VIEW).setProperty("availableForFilters", "true").build()) + .addType(ResourceType.builder(Qualifiers.SUBVIEW).build()) + .addRelations(Qualifiers.VIEW, Qualifiers.SUBVIEW) + .addRelations(Qualifiers.SUBVIEW, Qualifiers.PROJECT) + .build(); private ResourceTypeTree defaultTree = ResourceTypeTree.builder() - .addType(ResourceType.builder(Qualifiers.PROJECT).setProperty("availableForFilters", "true").build()) - .addType(ResourceType.builder(Qualifiers.DIRECTORY).build()) - .addType(ResourceType.builder(Qualifiers.FILE).build()) - .addRelations(Qualifiers.PROJECT, Qualifiers.DIRECTORY) - .addRelations(Qualifiers.DIRECTORY, Qualifiers.FILE) - .build(); + .addType(ResourceType.builder(Qualifiers.PROJECT).setProperty("availableForFilters", "true").build()) + .addType(ResourceType.builder(Qualifiers.DIRECTORY).build()) + .addType(ResourceType.builder(Qualifiers.FILE).build()) + .addRelations(Qualifiers.PROJECT, Qualifiers.DIRECTORY) + .addRelations(Qualifiers.DIRECTORY, Qualifiers.FILE) + .build(); - private ResourceTypes types = new ResourceTypes(new ResourceTypeTree[] {viewsTree, defaultTree}); + private ResourceTypes types = new ResourceTypes(new ResourceTypeTree[]{viewsTree, defaultTree}); @Test public void get() { - assertThat(types.get(Qualifiers.PROJECT).getQualifier(), is(Qualifiers.PROJECT)); + assertThat(types.get(Qualifiers.PROJECT).getQualifier()).isEqualTo(Qualifiers.PROJECT); // does not return null - assertThat(types.get("xxx").getQualifier(), is("xxx")); + assertThat(types.get("xxx").getQualifier()).isEqualTo("xxx"); } @Test public void getAll() { - assertThat(types.getAll().size(), is(5)); - assertThat(qualifiers(types.getAll()), hasItems( - Qualifiers.PROJECT, Qualifiers.DIRECTORY, Qualifiers.FILE, Qualifiers.VIEW, Qualifiers.SUBVIEW)); + assertThat(qualifiers(types.getAll())).containsOnly(Qualifiers.PROJECT, Qualifiers.DIRECTORY, Qualifiers.FILE, Qualifiers.VIEW, Qualifiers.SUBVIEW); } @Test public void getAll_predicate() { Collection forFilters = types.getAll(ResourceTypes.AVAILABLE_FOR_FILTERS); - assertThat(forFilters.size(), is(2)); - assertThat(qualifiers(forFilters), hasItems(Qualifiers.PROJECT, Qualifiers.VIEW)); + assertThat(qualifiers(forFilters)).containsOnly(Qualifiers.PROJECT, Qualifiers.VIEW).doesNotHaveDuplicates(); + } + + @Test + public void getAllWithPropertyKey() { + assertThat(qualifiers(types.getAllWithPropertyKey("availableForFilters"))).containsOnly(Qualifiers.VIEW, Qualifiers.PROJECT); + } + + @Test + public void getAllWithPropertyValue() { + assertThat(qualifiers(types.getAllWithPropertyValue("availableForFilters", "true"))).containsOnly(Qualifiers.VIEW, Qualifiers.PROJECT); + assertThat(qualifiers(types.getAllWithPropertyValue("availableForFilters", true))).containsOnly(Qualifiers.VIEW, Qualifiers.PROJECT); + assertThat(qualifiers(types.getAllWithPropertyValue("availableForFilters", false))).containsOnly(Qualifiers.SUBVIEW, Qualifiers.DIRECTORY, Qualifiers.FILE); } @Test public void getChildrenQualifiers() { - assertThat(types.getChildrenQualifiers(Qualifiers.PROJECT).size(), is(1)); - assertThat(types.getChildrenQualifiers(Qualifiers.PROJECT), hasItem(Qualifiers.DIRECTORY)); - assertThat(types.getChildrenQualifiers(Qualifiers.SUBVIEW), hasItem(Qualifiers.PROJECT)); - assertThat(types.getChildrenQualifiers("xxx").isEmpty(), is(true)); - assertThat(types.getChildrenQualifiers(Qualifiers.FILE).isEmpty(), is(true)); + assertThat(types.getChildrenQualifiers(Qualifiers.PROJECT)).containsExactly(Qualifiers.DIRECTORY); + assertThat(types.getChildrenQualifiers(Qualifiers.SUBVIEW)).containsExactly(Qualifiers.PROJECT); + assertThat(types.getChildrenQualifiers("xxx")).isEmpty(); + assertThat(types.getChildrenQualifiers(Qualifiers.FILE)).isEmpty(); } @Test public void getChildren() { - assertThat(qualifiers(types.getChildren(Qualifiers.PROJECT)), hasItem(Qualifiers.DIRECTORY)); - assertThat(qualifiers(types.getChildren(Qualifiers.SUBVIEW)), hasItem(Qualifiers.PROJECT)); + assertThat(qualifiers(types.getChildren(Qualifiers.PROJECT))).contains(Qualifiers.DIRECTORY); + assertThat(qualifiers(types.getChildren(Qualifiers.SUBVIEW))).contains(Qualifiers.PROJECT); } @Test public void getLeavesQualifiers() { - assertThat(types.getLeavesQualifiers(Qualifiers.PROJECT).size(), is(1)); - assertThat(types.getLeavesQualifiers(Qualifiers.PROJECT), hasItem(Qualifiers.FILE)); + assertThat(types.getLeavesQualifiers(Qualifiers.PROJECT)).containsExactly(Qualifiers.FILE); - assertThat(types.getLeavesQualifiers(Qualifiers.DIRECTORY).size(), is(1)); - assertThat(types.getLeavesQualifiers(Qualifiers.DIRECTORY), hasItem(Qualifiers.FILE)); + assertThat(types.getLeavesQualifiers(Qualifiers.DIRECTORY)).containsExactly(Qualifiers.FILE); - assertThat(types.getLeavesQualifiers(Qualifiers.VIEW).size(), is(1)); - assertThat(types.getLeavesQualifiers(Qualifiers.VIEW), hasItem(Qualifiers.PROJECT)); + assertThat(types.getLeavesQualifiers(Qualifiers.VIEW)).containsExactly(Qualifiers.PROJECT); - assertThat(types.getLeavesQualifiers("xxx").size(), is(0)); + assertThat(types.getLeavesQualifiers("xxx")).isEmpty(); } @Test public void getTree() { - assertThat(qualifiers(types.getTree(Qualifiers.VIEW).getTypes()), hasItems(Qualifiers.VIEW, Qualifiers.SUBVIEW)); - assertThat(types.getTree("xxx"), nullValue()); + assertThat(qualifiers(types.getTree(Qualifiers.VIEW).getTypes())).containsOnly(Qualifiers.VIEW, Qualifiers.SUBVIEW).doesNotHaveDuplicates(); + assertThat(types.getTree("xxx")).isNull(); } @Test(expected = IllegalStateException.class) public void failOnDuplicatedQualifier() { ResourceTypeTree tree1 = ResourceTypeTree.builder() - .addType(ResourceType.builder("foo").build()) - .build(); + .addType(ResourceType.builder("foo").build()) + .build(); ResourceTypeTree tree2 = ResourceTypeTree.builder() - .addType(ResourceType.builder("foo").build()) - .build(); + .addType(ResourceType.builder("foo").build()) + .build(); - new ResourceTypes(new ResourceTypeTree[] {tree1, tree2}); + new ResourceTypes(new ResourceTypeTree[]{tree1, tree2}); } static Collection qualifiers(Collection types) { diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb index 70d25884d47..2875542762f 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb @@ -51,7 +51,7 @@ - Role: Code viewers
Ability to view source code of a project. + Role: Code Viewers
Ability to view source code of a project. <%= default_project_users('codeviewer', @qualifier).map{|u| h u.name}.join(', ') -%> (<%= link_to "select", {:action => 'edit_default_project_users', :role => 'codeviewer', :redirect => 'projects', :qualifier => @qualifier}, :class => 'link-action', :id => 'selectu-codeviewer' -%>) -- 2.39.5