| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.server.query.group; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static com.google.common.truth.Truth8.assertThat; |
| import static com.google.common.truth.TruthJUnit.assume; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| import static java.util.stream.Collectors.toList; |
| import static org.junit.Assert.fail; |
| |
| import com.google.common.base.CharMatcher; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.entities.AccountGroup; |
| import com.google.gerrit.entities.InternalGroup; |
| import com.google.gerrit.extensions.api.GerritApi; |
| import com.google.gerrit.extensions.api.accounts.AccountInput; |
| import com.google.gerrit.extensions.api.groups.GroupInput; |
| import com.google.gerrit.extensions.api.groups.Groups.QueryRequest; |
| import com.google.gerrit.extensions.common.AccountInfo; |
| import com.google.gerrit.extensions.common.GroupInfo; |
| import com.google.gerrit.extensions.restapi.BadRequestException; |
| import com.google.gerrit.index.IndexConfig; |
| import com.google.gerrit.index.QueryOptions; |
| import com.google.gerrit.index.Schema; |
| import com.google.gerrit.index.query.FieldBundle; |
| import com.google.gerrit.lifecycle.LifecycleManager; |
| import com.google.gerrit.server.AnonymousUser; |
| import com.google.gerrit.server.CurrentUser; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.ServerInitiated; |
| import com.google.gerrit.server.account.AccountCache; |
| import com.google.gerrit.server.account.AccountManager; |
| import com.google.gerrit.server.account.Accounts; |
| import com.google.gerrit.server.account.AccountsUpdate; |
| import com.google.gerrit.server.account.AuthRequest; |
| import com.google.gerrit.server.account.GroupCache; |
| import com.google.gerrit.server.config.AllProjectsName; |
| import com.google.gerrit.server.group.db.GroupDelta; |
| import com.google.gerrit.server.group.db.GroupsUpdate; |
| import com.google.gerrit.server.index.group.GroupField; |
| import com.google.gerrit.server.index.group.GroupIndex; |
| import com.google.gerrit.server.index.group.GroupIndexCollection; |
| import com.google.gerrit.server.schema.SchemaCreator; |
| import com.google.gerrit.server.util.ManualRequestContext; |
| import com.google.gerrit.server.util.OneOffRequestContext; |
| import com.google.gerrit.server.util.RequestContext; |
| import com.google.gerrit.server.util.ThreadLocalRequestContext; |
| import com.google.gerrit.testing.GerritServerTests; |
| import com.google.gerrit.testing.GerritTestName; |
| import com.google.inject.Inject; |
| import com.google.inject.Injector; |
| import com.google.inject.Provider; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Optional; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| @Ignore |
| public abstract class AbstractQueryGroupsTest extends GerritServerTests { |
| @Rule public final GerritTestName testName = new GerritTestName(); |
| |
| @Inject protected Accounts accounts; |
| |
| @Inject @ServerInitiated protected Provider<AccountsUpdate> accountsUpdate; |
| |
| @Inject protected AccountCache accountCache; |
| |
| @Inject protected AccountManager accountManager; |
| |
| @Inject protected GerritApi gApi; |
| |
| @Inject protected IdentifiedUser.GenericFactory userFactory; |
| |
| @Inject private Provider<AnonymousUser> anonymousUser; |
| |
| @Inject protected SchemaCreator schemaCreator; |
| |
| @Inject protected ThreadLocalRequestContext requestContext; |
| |
| @Inject protected OneOffRequestContext oneOffRequestContext; |
| |
| @Inject protected AllProjectsName allProjects; |
| |
| @Inject protected GroupCache groupCache; |
| |
| @Inject @ServerInitiated protected Provider<GroupsUpdate> groupsUpdateProvider; |
| |
| @Inject protected GroupIndexCollection indexes; |
| |
| @Inject protected AuthRequest.Factory authRequestFactory; |
| |
| @Inject private GroupIndexCollection groupIndexes; |
| |
| protected LifecycleManager lifecycle; |
| protected Injector injector; |
| protected AccountInfo currentUserInfo; |
| protected CurrentUser user; |
| |
| protected abstract Injector createInjector(); |
| |
| protected void validateAssumptions() {} |
| |
| @Before |
| public void setUpInjector() throws Exception { |
| lifecycle = new LifecycleManager(); |
| injector = createInjector(); |
| lifecycle.add(injector); |
| injector.injectMembers(this); |
| lifecycle.start(); |
| initAfterLifecycleStart(); |
| setUpDatabase(); |
| validateAssumptions(); |
| } |
| |
| @After |
| public void cleanUp() { |
| lifecycle.stop(); |
| } |
| |
| protected void setUpDatabase() throws Exception { |
| schemaCreator.create(); |
| |
| Account.Id userId = |
| createAccountOutsideRequestContext("user", "User", "user@example.com", true); |
| user = userFactory.create(userId); |
| requestContext.setContext(newRequestContext(userId)); |
| currentUserInfo = gApi.accounts().id(userId.get()).get(); |
| } |
| |
| protected void initAfterLifecycleStart() throws Exception {} |
| |
| protected RequestContext newRequestContext(Account.Id requestUserId) { |
| final CurrentUser requestUser = userFactory.create(requestUserId); |
| return () -> requestUser; |
| } |
| |
| protected void setAnonymous() { |
| requestContext.setContext(anonymousUser::get); |
| } |
| |
| @After |
| public void tearDownInjector() { |
| if (lifecycle != null) { |
| lifecycle.stop(); |
| } |
| if (requestContext != null) { |
| requestContext.setContext(null); |
| } |
| } |
| |
| @Test |
| public void byUuid() throws Exception { |
| assertQuery("uuid:6d70856bc40ded50f2585c4c0f7e179f3544a272"); |
| assertQuery("uuid:non-existing"); |
| |
| GroupInfo group = createGroup(name("group")); |
| assertQuery("uuid:" + group.id, group); |
| |
| GroupInfo admins = gApi.groups().id("Administrators").get(); |
| assertQuery("uuid:" + admins.id, admins); |
| } |
| |
| @Test |
| public void byName() throws Exception { |
| assertQuery("name:non-existing"); |
| |
| GroupInfo group = createGroup(name("Group")); |
| assertQuery("name:" + group.name, group); |
| assertQuery("name:" + group.name.toLowerCase(Locale.US)); |
| |
| // only exact match |
| GroupInfo groupWithHyphen = createGroup(name("group-with-hyphen")); |
| createGroup(name("group-no-match-with-hyphen")); |
| assertQuery("name:" + groupWithHyphen.name, groupWithHyphen); |
| } |
| |
| @Test |
| public void byInname() throws Exception { |
| String namePart = testName.getSanitizedMethodName(); |
| namePart = CharMatcher.is('_').removeFrom(namePart); |
| |
| GroupInfo group1 = createGroup("group-" + namePart); |
| GroupInfo group2 = createGroup("group-" + namePart + "-2"); |
| GroupInfo group3 = createGroup("group-" + namePart + "3"); |
| assertQuery("inname:" + namePart, group1, group2, group3); |
| assertQuery("inname:" + namePart.toUpperCase(Locale.US), group1, group2, group3); |
| assertQuery("inname:" + namePart.toLowerCase(Locale.US), group1, group2, group3); |
| } |
| |
| @Test |
| public void byDescription() throws Exception { |
| GroupInfo group1 = createGroupWithDescription(name("group1"), "This is a test group."); |
| GroupInfo group2 = createGroupWithDescription(name("group2"), "ANOTHER TEST GROUP."); |
| createGroupWithDescription(name("group3"), "Maintainers of project foo."); |
| assertQuery("description:test", group1, group2); |
| |
| assertQuery("description:non-existing"); |
| |
| BadRequestException thrown = |
| assertThrows(BadRequestException.class, () -> assertQuery("description:\"\"")); |
| assertThat(thrown).hasMessageThat().contains("description operator requires a value"); |
| } |
| |
| @Test |
| public void byOwner() throws Exception { |
| GroupInfo ownerGroup = createGroup(name("owner-group")); |
| GroupInfo group = createGroupWithOwner(name("group"), ownerGroup); |
| createGroup(name("group2")); |
| |
| assertQuery("owner:" + group.id); |
| |
| // ownerGroup owns itself |
| assertQuery("owner:" + ownerGroup.id, group, ownerGroup); |
| assertQuery("owner:" + ownerGroup.name, group, ownerGroup); |
| } |
| |
| @Test |
| public void byIsVisibleToAll() throws Exception { |
| assertQuery("is:visibletoall"); |
| |
| GroupInfo groupThatIsVisibleToAll = |
| createGroupThatIsVisibleToAll(name("group-that-is-visible-to-all")); |
| createGroup(name("group")); |
| |
| assertQuery("is:visibletoall", groupThatIsVisibleToAll); |
| } |
| |
| @Test |
| public void byMember() throws Exception { |
| assume().that(getSchemaVersion() >= 4).isTrue(); |
| |
| AccountInfo user1 = createAccount("user1", "User1", "user1@example.com"); |
| AccountInfo user2 = createAccount("user2", "User2", "user2@example.com"); |
| |
| GroupInfo group1 = createGroup(name("group1"), user1); |
| GroupInfo group2 = createGroup(name("group2"), user2); |
| GroupInfo group3 = createGroup(name("group3"), user1); |
| |
| assertQuery("member:" + user1.name, group1, group3); |
| assertQuery("member:" + user1.email, group1, group3); |
| |
| gApi.groups().id(group3.id).removeMembers(user1.username); |
| gApi.groups().id(group2.id).addMembers(user1.username); |
| |
| assertQuery("member:" + user1.name, group1, group2); |
| } |
| |
| @Test |
| public void bySubgroups() throws Exception { |
| assume().that(getSchemaVersion() >= 4).isTrue(); |
| |
| GroupInfo superParentGroup = createGroup(name("superParentGroup")); |
| GroupInfo parentGroup1 = createGroup(name("parentGroup1")); |
| GroupInfo parentGroup2 = createGroup(name("parentGroup2")); |
| GroupInfo subGroup = createGroup(name("subGroup")); |
| |
| gApi.groups().id(superParentGroup.id).addGroups(parentGroup1.id, parentGroup2.id); |
| gApi.groups().id(parentGroup1.id).addGroups(subGroup.id); |
| gApi.groups().id(parentGroup2.id).addGroups(subGroup.id); |
| |
| assertQuery("subgroup:" + subGroup.id, parentGroup1, parentGroup2); |
| assertQuery("subgroup:" + parentGroup1.id, superParentGroup); |
| |
| gApi.groups().id(superParentGroup.id).addGroups(subGroup.id); |
| gApi.groups().id(parentGroup1.id).removeGroups(subGroup.id); |
| |
| assertQuery("subgroup:" + subGroup.id, superParentGroup, parentGroup2); |
| } |
| |
| @Test |
| public void byDefaultField() throws Exception { |
| GroupInfo group1 = createGroup(name("foo-group")); |
| GroupInfo group2 = createGroup(name("group2")); |
| GroupInfo group3 = |
| createGroupWithDescription( |
| name("group3"), "decription that contains foo and the UUID of group2: " + group2.id); |
| |
| assertQuery("non-existing"); |
| assertQuery("foo", group1, group3); |
| assertQuery(group2.id, group2, group3); |
| } |
| |
| @Test |
| public void withLimit() throws Exception { |
| GroupInfo group1 = createGroup(name("group1")); |
| GroupInfo group2 = createGroup(name("group2")); |
| GroupInfo group3 = createGroup(name("group3")); |
| |
| String query = "uuid:" + group1.id + " OR uuid:" + group2.id + " OR uuid:" + group3.id; |
| List<GroupInfo> result = assertQuery(query, group1, group2, group3); |
| assertThat(result.get(result.size() - 1)._moreGroups).isNull(); |
| |
| result = assertQuery(newQuery(query).withLimit(2), result.subList(0, 2)); |
| assertThat(result.get(result.size() - 1)._moreGroups).isTrue(); |
| } |
| |
| @Test |
| public void withStart() throws Exception { |
| GroupInfo group1 = createGroup(name("group1")); |
| GroupInfo group2 = createGroup(name("group2")); |
| GroupInfo group3 = createGroup(name("group3")); |
| |
| String query = "uuid:" + group1.id + " OR uuid:" + group2.id + " OR uuid:" + group3.id; |
| List<GroupInfo> result = assertQuery(query, group1, group2, group3); |
| |
| assertQuery(newQuery(query).withStart(1), result.subList(1, 3)); |
| } |
| |
| @Test |
| public void withStartCannotBeLessThanZero() throws Exception { |
| GroupInfo group1 = createGroup(name("group1")); |
| assertFailingQuery( |
| newQuery("uuid:" + group1.id).withStart(-1), "'start' parameter cannot be less than zero"); |
| } |
| |
| @Test |
| public void sortedByUuid() throws Exception { |
| GroupInfo group1 = createGroup(name("group1")); |
| GroupInfo group2 = createGroup(name("group2")); |
| GroupInfo group3 = createGroup(name("group3")); |
| |
| String query = "uuid:" + group1.id + " OR uuid:" + group2.id + " OR uuid:" + group3.id; |
| // assertQuery sorts the expected groups by UUID |
| assertQuery(newQuery(query), group1, group2, group3); |
| } |
| |
| @Test |
| public void asAnonymous() throws Exception { |
| GroupInfo group = createGroup(name("group")); |
| |
| setAnonymous(); |
| assertQuery("uuid:" + group.id); |
| } |
| |
| // reindex permissions are tested by {@link GroupsIT#reindexPermissions} |
| @Test |
| public void reindex() throws Exception { |
| GroupInfo group1 = createGroupWithDescription(name("group"), "barX"); |
| |
| // update group in the database so that group index is stale |
| String newDescription = "barY"; |
| AccountGroup.UUID groupUuid = AccountGroup.uuid(group1.id); |
| GroupDelta groupDelta = GroupDelta.builder().setDescription(newDescription).build(); |
| groupsUpdateProvider.get().updateGroupInNoteDb(groupUuid, groupDelta); |
| |
| assertQuery("description:" + group1.description, group1); |
| assertQuery("description:" + newDescription); |
| |
| gApi.groups().id(group1.id).index(); |
| assertQuery("description:" + group1.description); |
| assertQuery("description:" + newDescription, group1); |
| } |
| |
| @Test |
| public void rawDocument() throws Exception { |
| GroupInfo group1 = createGroup(name("group1")); |
| AccountGroup.UUID uuid = AccountGroup.uuid(group1.id); |
| |
| Optional<FieldBundle> rawFields = |
| indexes |
| .getSearchIndex() |
| .getRaw( |
| uuid, |
| QueryOptions.create( |
| IndexConfig.fromConfig(config).build(), |
| 0, |
| 10, |
| indexes.getSearchIndex().getSchema().getStoredFields())); |
| |
| assertThat(rawFields).isPresent(); |
| assertThat(rawFields.get().getValue(GroupField.UUID_FIELD_SPEC)).isEqualTo(uuid.get()); |
| } |
| |
| @Test |
| public void byDeletedGroup() throws Exception { |
| GroupInfo group = createGroup(name("group")); |
| AccountGroup.UUID uuid = AccountGroup.uuid(group.id); |
| String query = "uuid:" + uuid; |
| assertQuery(query, group); |
| |
| for (GroupIndex index : groupIndexes.getWriteIndexes()) { |
| index.delete(uuid); |
| } |
| assertQuery(query); |
| } |
| |
| private Account.Id createAccountOutsideRequestContext( |
| String username, String fullName, String email, boolean active) throws Exception { |
| try (ManualRequestContext ctx = oneOffRequestContext.open()) { |
| Account.Id id = |
| accountManager.authenticate(authRequestFactory.createForUser(username)).getAccountId(); |
| if (email != null) { |
| accountManager.link(id, authRequestFactory.createForEmail(email)); |
| } |
| accountsUpdate |
| .get() |
| .update( |
| "Update Test Account", |
| id, |
| u -> { |
| u.setFullName(fullName).setPreferredEmail(email).setActive(active); |
| }); |
| return id; |
| } |
| } |
| |
| protected AccountInfo createAccount(String username, String fullName, String email) |
| throws Exception { |
| String uniqueName = name(username); |
| AccountInput accountInput = new AccountInput(); |
| accountInput.username = uniqueName; |
| accountInput.name = fullName; |
| accountInput.email = email; |
| return gApi.accounts().create(accountInput).get(); |
| } |
| |
| protected GroupInfo createGroup(String name, AccountInfo... members) throws Exception { |
| return createGroupWithDescription(name, null, members); |
| } |
| |
| protected GroupInfo createGroupWithDescription( |
| String name, String description, AccountInfo... members) throws Exception { |
| GroupInput in = new GroupInput(); |
| in.name = name; |
| in.description = description; |
| in.members = |
| Arrays.asList(members).stream().map(a -> String.valueOf(a._accountId)).collect(toList()); |
| return gApi.groups().create(in).get(); |
| } |
| |
| protected GroupInfo createGroupWithOwner(String name, GroupInfo ownerGroup) throws Exception { |
| GroupInput in = new GroupInput(); |
| in.name = name; |
| in.ownerId = ownerGroup.id; |
| return gApi.groups().create(in).get(); |
| } |
| |
| protected GroupInfo createGroupThatIsVisibleToAll(String name) throws Exception { |
| GroupInput in = new GroupInput(); |
| in.name = name; |
| in.visibleToAll = true; |
| return gApi.groups().create(in).get(); |
| } |
| |
| protected GroupInfo getGroup(AccountGroup.UUID uuid) throws Exception { |
| return gApi.groups().id(uuid.get()).get(); |
| } |
| |
| protected List<GroupInfo> assertQuery(Object query, GroupInfo... groups) throws Exception { |
| return assertQuery(newQuery(query), groups); |
| } |
| |
| protected List<GroupInfo> assertQuery(QueryRequest query, GroupInfo... groups) throws Exception { |
| return assertQuery(query, Arrays.asList(groups)); |
| } |
| |
| protected List<GroupInfo> assertQuery(QueryRequest query, List<GroupInfo> groups) |
| throws Exception { |
| List<GroupInfo> result = query.get(); |
| Iterable<String> uuids = uuids(result); |
| assertWithMessage(format(query, result, groups)) |
| .that(uuids) |
| .containsExactlyElementsIn(uuids(groups)) |
| .inOrder(); |
| return result; |
| } |
| |
| protected void assertFailingQuery(QueryRequest query, String expectedMessage) throws Exception { |
| try { |
| assertQuery(query); |
| fail("expected BadRequestException for query '" + query + "'"); |
| } catch (BadRequestException e) { |
| assertThat(e.getMessage()).isEqualTo(expectedMessage); |
| } |
| } |
| |
| protected QueryRequest newQuery(Object query) { |
| return gApi.groups().query(query.toString()); |
| } |
| |
| protected String format( |
| QueryRequest query, List<GroupInfo> actualGroups, List<GroupInfo> expectedGroups) { |
| StringBuilder b = new StringBuilder(); |
| b.append("query '").append(query.getQuery()).append("' with expected groups "); |
| b.append(format(expectedGroups)); |
| b.append(" and result "); |
| b.append(format(actualGroups)); |
| return b.toString(); |
| } |
| |
| protected String format(Iterable<GroupInfo> groups) { |
| StringBuilder b = new StringBuilder(); |
| b.append("["); |
| Iterator<GroupInfo> it = groups.iterator(); |
| while (it.hasNext()) { |
| GroupInfo g = it.next(); |
| b.append("{") |
| .append(g.id) |
| .append(", ") |
| .append("name=") |
| .append(g.name) |
| .append(", ") |
| .append("groupId=") |
| .append(g.groupId) |
| .append(", ") |
| .append("url=") |
| .append(g.url) |
| .append(", ") |
| .append("ownerId=") |
| .append(g.ownerId) |
| .append(", ") |
| .append("owner=") |
| .append(g.owner) |
| .append(", ") |
| .append("description=") |
| .append(g.description) |
| .append(", ") |
| .append("visibleToAll=") |
| .append(toBoolean(g.options.visibleToAll)) |
| .append("}"); |
| if (it.hasNext()) { |
| b.append(", "); |
| } |
| } |
| b.append("]"); |
| return b.toString(); |
| } |
| |
| protected static boolean toBoolean(Boolean b) { |
| return b == null ? false : b; |
| } |
| |
| protected static Iterable<String> ids(GroupInfo... groups) { |
| return uuids(Arrays.asList(groups)); |
| } |
| |
| protected static Iterable<String> uuids(List<GroupInfo> groups) { |
| return groups.stream().map(g -> g.id).sorted().collect(toList()); |
| } |
| |
| protected String name(String name) { |
| if (name == null) { |
| return null; |
| } |
| |
| return name + "_" + testName.getSanitizedMethodName(); |
| } |
| |
| protected int getSchemaVersion() { |
| return getSchema().getVersion(); |
| } |
| |
| protected Schema<InternalGroup> getSchema() { |
| return indexes.getSearchIndex().getSchema(); |
| } |
| } |