blob: 0075121ed032533ae7f0d55a65ef63446bb9211d [file] [log] [blame]
// Copyright (C) 2016 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.account;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
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.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.accounts.Accounts.QueryRequest;
import com.google.gerrit.extensions.api.changes.ReviewerInput;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ListAccountsOption;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
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.GerritPersonIdent;
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.AccountConfig;
import com.google.gerrit.server.account.AccountDelta;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountState;
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.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gerrit.server.index.account.AccountIndexer;
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.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@Ignore
public abstract class AbstractQueryAccountsTest 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 AccountIndexer accountIndexer;
@Inject protected AccountManager accountManager;
@Inject protected GerritApi gApi;
@Inject @GerritPersonIdent Provider<PersonIdent> serverIdent;
@Inject protected IdentifiedUser.GenericFactory userFactory;
@Inject private Provider<AnonymousUser> anonymousUser;
@Inject protected SchemaCreator schemaCreator;
@Inject protected ThreadLocalRequestContext requestContext;
@Inject protected OneOffRequestContext oneOffRequestContext;
@Inject protected Provider<InternalAccountQuery> queryProvider;
@Inject protected AllProjectsName allProjects;
@Inject protected AllUsersName allUsers;
@Inject protected GitRepositoryManager repoManager;
@Inject protected AccountIndexCollection indexes;
@Inject protected ExternalIds externalIds;
@Inject private ExternalIdKeyFactory externalIdKeyFactory;
@Inject protected AuthRequest.Factory authRequestFactory;
protected LifecycleManager lifecycle;
protected Injector injector;
protected AccountInfo currentUserInfo;
protected CurrentUser admin;
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 adminId = createAccount("admin", "Administrator", "admin@example.com", true);
admin = userFactory.create(adminId);
setRequestContextForUser(adminId);
currentUserInfo = gApi.accounts().id(adminId.get()).get();
}
protected void initAfterLifecycleStart() throws Exception {}
protected RequestContext newRequestContext(Account.Id requestUserId) {
final CurrentUser requestUser = userFactory.create(requestUserId);
return () -> requestUser;
}
protected void setAnonymous() {
@SuppressWarnings("unused")
var unused = requestContext.setContext(anonymousUser::get);
}
@After
public void tearDownInjector() {
if (lifecycle != null) {
lifecycle.stop();
}
@SuppressWarnings("unused")
var unused = requestContext.setContext(null);
}
@Test
public void byId() throws Exception {
AccountInfo user = newAccount("user");
assertQuery("9999999");
assertQuery(currentUserInfo._accountId, currentUserInfo);
assertQuery(user._accountId, user);
}
@Test
public void bySelf() throws Exception {
assertQuery("self", currentUserInfo);
}
@Test
public void byEmail() throws Exception {
AccountInfo user1 = newAccountWithEmail("user1", name("user1@example.com"));
String domain = name("test.com");
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
String prefix = name("prefix");
AccountInfo user4 = newAccountWithEmail("user4", prefix + "user4@example.com");
AccountInfo user5 = newAccountWithEmail("user5", name("user5MixedCase@example.com"));
assertQuery("notexisting@example.com");
assertQuery(currentUserInfo.email, currentUserInfo);
assertQuery("email:" + currentUserInfo.email, currentUserInfo);
assertQuery(user1.email, user1);
assertQuery("email:" + user1.email, user1);
assertQuery(domain, user2, user3);
assertQuery("email:" + prefix, user4);
assertQuery(user5.email, user5);
assertQuery("email:" + user5.email, user5);
assertQuery("email:" + user5.email.toUpperCase(Locale.US), user5);
}
@Test
public void bySecondaryEmail() throws Exception {
String prefix = name("secondary");
String domain = name("test.com");
String secondaryEmail = prefix + "@" + domain;
AccountInfo user1 = newAccountWithEmail("user1", name("user1@example.com"));
addEmails(user1, secondaryEmail);
AccountInfo user2 = newAccountWithEmail("user2", name("user2@example.com"));
addEmails(user2, name("other@" + domain));
assertQuery(secondaryEmail, user1);
assertQuery("email:" + secondaryEmail, user1);
assertQuery("email:" + prefix, user1);
assertQuery(domain, user1, user2);
}
@Test
public void byEmailWithoutModifyAccountCapability() throws Exception {
String preferredEmail = name("primary@example.com");
String secondaryEmail = name("secondary@example.com");
AccountInfo user1 = newAccountWithEmail("user1", preferredEmail);
addEmails(user1, secondaryEmail);
AccountInfo user2 = newAccount("user");
setRequestContextForUser(Account.id(user2._accountId));
assertQuery(preferredEmail, user1);
assertQuery(secondaryEmail);
assertQuery("email:" + preferredEmail, user1);
assertQuery("email:" + secondaryEmail);
}
@Test
public void byUsername() throws Exception {
assume().that(hasIndexByUsername()).isTrue();
AccountInfo user1 = newAccount("myuser");
assertQuery("notexisting");
assertQuery("Not Existing");
assertQuery(user1.username, user1);
assertQuery("username:" + user1.username, user1);
assertQuery("username:" + user1.username.toUpperCase(Locale.US), user1);
}
@Test
public void isActive() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccount("user3", "user3@" + domain, false);
AccountInfo user4 = newAccount("user4", "user4@" + domain, false);
// by default only active accounts are returned
assertQuery(domain, user1, user2);
assertQuery("name:" + domain, user1, user2);
assertQuery("is:active name:" + domain, user1, user2);
assertQuery("is:inactive name:" + domain, user3, user4);
}
@Test
public void byName() throws Exception {
AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
AccountInfo user3 = newAccountWithFullName("user3", "Mr Selfish");
assertQuery("notexisting");
assertQuery("Not Existing");
assertQuery(quote(user1.name), user1);
assertQuery("name:" + quote(user1.name), user1);
assertQuery("John", user1);
assertQuery("john", user1);
assertQuery("Doe", user1);
assertQuery("doe", user1);
assertQuery("DOE", user1);
assertQuery("Jo Do", user1);
assertQuery("jo do", user1);
assertQuery("self", currentUserInfo, user3);
assertQuery("me", currentUserInfo);
assertQuery("name:John", user1);
assertQuery("name:john", user1);
assertQuery("name:Doe", user1);
assertQuery("name:doe", user1);
assertQuery("name:DOE", user1);
assertQuery("name:self", user3);
assertQuery(quote(user2.name), user2);
assertQuery("name:" + quote(user2.name), user2);
}
@Test
public void byNameWithoutModifyAccountCapability() throws Exception {
AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
AccountInfo user3 = newAccount("user");
setRequestContextForUser(Account.id(user3._accountId));
assertQuery("notexisting");
assertQuery("Not Existing");
// by full name works with any index version
assertQuery(quote(user1.name), user1);
assertQuery("name:" + quote(user1.name), user1);
assertQuery(quote(user2.name), user2);
assertQuery("name:" + quote(user2.name), user2);
// by self/me works with any index version
assertQuery("self", user3);
assertQuery("me", user3);
assertQuery("John", user1);
assertQuery("john", user1);
assertQuery("Doe", user1);
assertQuery("doe", user1);
assertQuery("DOE", user1);
assertQuery("Jo Do", user1);
assertQuery("jo do", user1);
assertQuery("name:John", user1);
assertQuery("name:john", user1);
assertQuery("name:Doe", user1);
assertQuery("name:doe", user1);
assertQuery("name:DOE", user1);
}
@Test
public void byCansee() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("account1", "account1@" + domain);
AccountInfo user2 = newAccountWithEmail("account2", "account2@" + domain);
AccountInfo user3 = newAccountWithEmail("account3", "account3@" + domain);
Project.NameKey p = createProject(name("p"));
ChangeInfo c = createChange(p);
assertQuery("name:" + domain + " cansee:" + c.changeId, user1, user2, user3);
GroupInfo group = createGroup(name("group"), user1, user2);
blockRead(p, group);
assertQuery("name:" + domain + " cansee:" + c.changeId, user3);
}
@Test
public void byCanSee_privateChange() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("account1", "account1@" + domain);
AccountInfo user2 = newAccountWithEmail("account2", "account2@" + domain);
AccountInfo user3 = newAccountWithEmail("account3", "account3@" + domain);
AccountInfo user4 = newAccountWithEmail("account4", "account4@" + domain);
Project.NameKey p = createProject(name("p"));
// Create the change as User1
setRequestContextForUser(Account.id(user1._accountId));
ChangeInfo c = createPrivateChange(p);
assertThat(c.owner).isEqualTo(user1);
// Add user2 as a reviewer, user3 as a CC, and leave user4 dangling.
addReviewer(c.changeId, user2.email, ReviewerState.REVIEWER);
addReviewer(c.changeId, user3.email, ReviewerState.CC);
// Request as the owner
setRequestContextForUser(Account.id(user1._accountId));
assertQuery("cansee:" + c.changeId, user1, user2, user3);
// Request as the reviewer
setRequestContextForUser(Account.id(user2._accountId));
assertQuery("cansee:" + c.changeId, user1, user2, user3);
// Request as the CC
setRequestContextForUser(Account.id(user3._accountId));
assertQuery("cansee:" + c.changeId, user1, user2, user3);
// Request as an account not in {owner, reviewer, CC}
setRequestContextForUser(Account.id(user4._accountId));
BadRequestException exception =
assertThrows(BadRequestException.class, () -> newQuery("cansee:" + c.changeId).get());
assertThat(exception)
.hasMessageThat()
.isEqualTo(String.format("change %s not found", c.changeId));
}
@Test
public void byWatchedProject() throws Exception {
Project.NameKey p = createProject(name("p"));
Project.NameKey p2 = createProject(name("p2"));
AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
AccountInfo user3 = newAccountWithFullName("user3", "Mr Selfish");
assertThat(queryProvider.get().byWatchedProject(p)).isEmpty();
watch(user1, p, null);
assertAccounts(queryProvider.get().byWatchedProject(p), user1);
watch(user2, p, "keyword");
assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
watch(user3, p2, "keyword");
watch(user3, allProjects, "keyword");
assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
assertAccounts(queryProvider.get().byWatchedProject(p2), user3);
assertAccounts(queryProvider.get().byWatchedProject(allProjects), user3);
}
@Test
public void byDeletedAccount() throws Exception {
AccountInfo user = newAccountWithFullName("jdoe", "John Doe");
Account.Id userId = Account.Id.tryParse(user._accountId.toString()).get();
assertQuery("John", user);
for (AccountIndex index : indexes.getWriteIndexes()) {
index.delete(userId);
}
assertQuery("John");
}
@Test
public void withLimit() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
List<AccountInfo> result = assertQuery(domain, user1, user2, user3);
assertThat(Iterables.getLast(result)._moreAccounts).isNull();
result = assertQuery(newQuery(domain).withLimit(2), result.subList(0, 2));
assertThat(Iterables.getLast(result)._moreAccounts).isTrue();
}
@Test
public void withStart() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
List<AccountInfo> result = assertQuery(domain, user1, user2, user3);
assertQuery(newQuery(domain).withStart(1), result.subList(1, 3));
}
@Test
public void withStartCannotBeLessThanZero() throws Exception {
assertFailingQuery(
newQuery("self").withStart(-1), "'start' parameter cannot be less than zero");
}
@Test
public void sortedByFullname() throws Exception {
String appendix = name("name");
// Use an account creation order that ensures that sorting by fullname differs from sorting by
// account ID.
AccountInfo userFoo = newAccountWithFullName("user1", "foo-" + appendix);
AccountInfo userBar = newAccountWithFullName("user2", "bar-" + appendix);
AccountInfo userBaz = newAccountWithFullName("user3", "baz-" + appendix);
assertThat(userFoo._accountId).isLessThan(userBar._accountId);
assertThat(userBar._accountId).isLessThan(userBaz._accountId);
String query = "name:" + userFoo.name + " OR name:" + userBar.name + " OR name:" + userBaz.name;
// Must request details to populate fullname in the results. If fullname is not set sorting by
// fullname is not possible.
assertQuery(newQuery(query).withOption(ListAccountsOption.DETAILS), userBar, userBaz, userFoo);
}
@Test
public void sortedByPreferredEmail() throws Exception {
String appendix = name("name");
// Use an account creation order that ensures that sorting by preferred email differs from
// sorting by account ID. Use the same fullname for all accounts so that sorting must be done by
// preferred email.
AccountInfo userFoo3 =
newAccount("user3", "foo-" + appendix, "foo3-" + appendix + "@example.com", true);
AccountInfo userFoo1 =
newAccount("user1", "foo-" + appendix, "foo1-" + appendix + "@example.com", true);
AccountInfo userFoo2 =
newAccount("user2", "foo-" + appendix, "foo2-" + appendix + "@example.com", true);
assertThat(userFoo3._accountId).isLessThan(userFoo1._accountId);
assertThat(userFoo1._accountId).isLessThan(userFoo2._accountId);
String query =
"name:" + userFoo1.name + " OR name:" + userFoo2.name + " OR name:" + userFoo3.name;
// Must request details to populate fullname and preferred email in the results. If fullname and
// preferred email are not set sorting by fullname and preferred email is not possible. Since
// all 3 accounts have the same fullname we expect sorting by preferred email.
assertQuery(
newQuery(query).withOption(ListAccountsOption.DETAILS), userFoo1, userFoo2, userFoo3);
}
@Test
public void sortedById() throws Exception {
String appendix = name("name");
// Each new account gets a higher account ID. Create the accounts in an order that sorting by
// fullname differs from sorting by accout ID.
AccountInfo userFoo = newAccountWithFullName("user1", "foo-" + appendix);
AccountInfo userBar = newAccountWithFullName("user2", "bar-" + appendix);
AccountInfo userBaz = newAccountWithFullName("user3", "baz-" + appendix);
assertThat(userFoo._accountId).isLessThan(userBar._accountId);
assertThat(userBar._accountId).isLessThan(userBaz._accountId);
String query = "name:" + userFoo.name + " OR name:" + userBar.name + " OR name:" + userBaz.name;
// Normally sorting is done by fullname and preferred email, but if no details are requested
// fullname and preferred email are not set and then sorting is done by account ID.
assertQuery(newQuery(query), userFoo, userBar, userBaz);
}
@Test
public void withDetails() throws Exception {
AccountInfo user1 = newAccount("myuser", "My User", "my.user@example.com", true);
List<AccountInfo> result = assertQuery(getDefaultSearch(user1), user1);
AccountInfo ai = result.get(0);
assertThat(ai._accountId).isEqualTo(user1._accountId);
assertThat(ai.name).isNull();
assertThat(ai.username).isNull();
assertThat(ai.email).isNull();
assertThat(ai.avatars).isNull();
result =
assertQuery(
newQuery(getDefaultSearch(user1)).withOption(ListAccountsOption.DETAILS), user1);
ai = result.get(0);
assertThat(ai._accountId).isEqualTo(user1._accountId);
assertThat(ai.name).isEqualTo(user1.name);
assertThat(ai.username).isEqualTo(user1.username);
assertThat(ai.email).isEqualTo(user1.email);
assertThat(ai.avatars).isNull();
}
@Test
public void withSecondaryEmails() throws Exception {
AccountInfo user1 = newAccount("myuser", "My User", "my.user@example.com", true);
String[] secondaryEmails = new String[] {"bar@example.com", "foo@example.com"};
addEmails(user1, secondaryEmails);
List<AccountInfo> result = assertQuery(getDefaultSearch(user1), user1);
assertThat(result.get(0).secondaryEmails).isNull();
result = assertQuery(newQuery(getDefaultSearch(user1)).withSuggest(true), user1);
assertThat(result.get(0).secondaryEmails)
.containsExactlyElementsIn(Arrays.asList(secondaryEmails))
.inOrder();
result =
assertQuery(
newQuery(getDefaultSearch(user1)).withOption(ListAccountsOption.DETAILS), user1);
assertThat(result.get(0).secondaryEmails).isNull();
result =
assertQuery(
newQuery(getDefaultSearch(user1)).withOption(ListAccountsOption.ALL_EMAILS), user1);
assertThat(result.get(0).secondaryEmails)
.containsExactlyElementsIn(Arrays.asList(secondaryEmails))
.inOrder();
result =
assertQuery(
newQuery(getDefaultSearch(user1))
.withOptions(ListAccountsOption.DETAILS, ListAccountsOption.ALL_EMAILS),
user1);
assertThat(result.get(0).secondaryEmails)
.containsExactlyElementsIn(Arrays.asList(secondaryEmails))
.inOrder();
}
@Test
public void withSecondaryEmailsWithoutModifyAccountCapability() throws Exception {
AccountInfo user = newAccount("myuser", "My User", "other@example.com", true);
AccountInfo otherUser = newAccount("otheruser", "Other User", "abc@example.com", true);
String[] secondaryEmails = new String[] {"dfg@example.com", "hij@example.com"};
addEmails(otherUser, secondaryEmails);
setRequestContextForUser(Account.id(user._accountId));
List<AccountInfo> result = newQuery(getDefaultSearch(otherUser)).withSuggest(true).get();
assertThat(result.get(0).secondaryEmails).isNull();
assertThrows(
AuthException.class,
() ->
newQuery(getDefaultSearch(otherUser)).withOption(ListAccountsOption.ALL_EMAILS).get());
}
@Test
public void asAnonymous() throws Exception {
AccountInfo user1 = newAccount("user1", "user1@gerrit.com", /*active=*/ true);
setAnonymous();
assertQuery("9999999");
assertQuery("self");
assertQuery("email:" + user1.email, user1);
}
// reindex permissions are tested by {@link AccountIT#reindexPermissions}
@Test
public void reindex() throws Exception {
AccountInfo user1 = newAccountWithFullName("tester", "Test Usre");
// update account without reindex so that account index is stale
Account.Id accountId = Account.id(user1._accountId);
String newName = "Test User";
try (Repository repo = repoManager.openRepository(allUsers)) {
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsers, repo);
PersonIdent ident = serverIdent.get();
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
new AccountConfig(accountId, allUsers, repo)
.load()
.setAccountDelta(AccountDelta.builder().setFullName(newName).build())
.commit(md);
}
// Querying for the account here will not result in a stale document because
// we load AccountStates from the cache after reading documents from the index
// which means we always read fresh data when matching.
//
// Reindex account document
gApi.accounts().id(user1._accountId).index();
assertQuery("name:" + quote(user1.name));
assertQuery("name:" + quote(newName), user1);
}
@Test
public void rawDocument() throws Exception {
AccountInfo userInfo = gApi.accounts().id(admin.getAccountId().get()).get();
Schema<AccountState> schema = indexes.getSearchIndex().getSchema();
Optional<FieldBundle> rawFields =
indexes
.getSearchIndex()
.getRaw(
Account.id(userInfo._accountId),
QueryOptions.create(
config != null
? IndexConfig.fromConfig(config).build()
: IndexConfig.createDefault(),
0,
1,
schema.getStoredFields()));
assertThat(rawFields).isPresent();
if (schema.hasField(AccountField.ID_FIELD_SPEC)) {
assertThat(rawFields.get().getValue(AccountField.ID_FIELD_SPEC))
.isEqualTo(userInfo._accountId);
} else {
assertThat(Integer.valueOf(rawFields.get().<String>getValue(AccountField.ID_STR_FIELD_SPEC)))
.isEqualTo(userInfo._accountId);
}
List<AccountExternalIdInfo> externalIdInfos = gApi.accounts().self().getExternalIds();
List<ByteArrayWrapper> blobs = new ArrayList<>();
for (AccountExternalIdInfo info : externalIdInfos) {
Optional<ExternalId> extId = externalIds.get(externalIdKeyFactory.parse(info.identity));
assertThat(extId).isPresent();
blobs.add(new ByteArrayWrapper(AccountField.serializeExternalId(extId.get())));
}
// Some installations do not store EXTERNAL_ID_STATE_SPEC
if (!schema.hasField(AccountField.EXTERNAL_ID_STATE_SPEC)) {
return;
}
Iterable<byte[]> externalIdStates =
rawFields.get().<Iterable<byte[]>>getValue(AccountField.EXTERNAL_ID_STATE_SPEC);
assertThat(externalIdStates).hasSize(blobs.size());
assertThat(Streams.stream(externalIdStates).map(b -> new ByteArrayWrapper(b)).collect(toList()))
.containsExactlyElementsIn(blobs);
}
private String getDefaultSearch(AccountInfo user) {
return hasIndexByUsername() ? user.username : user.name;
}
/**
* Returns 'true' is {@link AccountField#USERNAME_FIELD} is indexed.
*
* <p>Some installations do not index {@link AccountField#USERNAME_FIELD}, since they do not use
* {@link ExternalId#SCHEME_USERNAME}
*/
private boolean hasIndexByUsername() {
Schema<AccountState> schema = indexes.getSearchIndex().getSchema();
return schema.hasField(AccountField.USERNAME_SPEC);
}
protected AccountInfo newAccount(String username) throws Exception {
return newAccountWithEmail(username, null);
}
protected AccountInfo newAccountWithEmail(String username, String email) throws Exception {
return newAccount(username, email, true);
}
protected AccountInfo newAccountWithFullName(String username, String fullName) throws Exception {
return newAccount(username, fullName, null, true);
}
protected AccountInfo newAccount(String username, String email, boolean active) throws Exception {
return newAccount(username, null, email, active);
}
protected AccountInfo newAccount(String username, String fullName, String email, boolean active)
throws Exception {
String uniqueName = name(username);
try {
gApi.accounts().id(uniqueName).get();
fail("user " + uniqueName + " already exists");
} catch (ResourceNotFoundException e) {
// expected: user does not exist yet
}
Account.Id id = createAccount(uniqueName, fullName, email, active);
return gApi.accounts().id(id.get()).get();
}
protected Project.NameKey createProject(String name) throws RestApiException {
ProjectInput in = new ProjectInput();
in.name = name;
in.createEmptyCommit = true;
gApi.projects().create(in);
return Project.nameKey(name);
}
protected void blockRead(Project.NameKey project, GroupInfo group) throws RestApiException {
ProjectAccessInput in = new ProjectAccessInput();
in.add = new HashMap<>();
AccessSectionInfo a = new AccessSectionInfo();
PermissionInfo p = new PermissionInfo(null, null);
p.rules =
ImmutableMap.of(group.id, new PermissionRuleInfo(PermissionRuleInfo.Action.BLOCK, false));
a.permissions = ImmutableMap.of("read", p);
in.add = ImmutableMap.of("refs/*", a);
gApi.projects().name(project.get()).access(in);
}
protected ChangeInfo createPrivateChange(Project.NameKey project) throws RestApiException {
ChangeInput in = new ChangeInput();
in.subject = "A change";
in.project = project.get();
in.branch = "master";
in.isPrivate = true;
return gApi.changes().create(in).get();
}
protected ChangeInfo createChange(Project.NameKey project) throws RestApiException {
ChangeInput in = new ChangeInput();
in.subject = "A change";
in.project = project.get();
in.branch = "master";
return gApi.changes().create(in).get();
}
protected void addReviewer(String changeId, String email, ReviewerState state)
throws RestApiException {
ReviewerInput reviewerInput = new ReviewerInput();
reviewerInput.reviewer = email;
reviewerInput.state = state;
gApi.changes().id(changeId).addReviewer(reviewerInput);
}
protected GroupInfo createGroup(String name, AccountInfo... members) throws RestApiException {
GroupInput in = new GroupInput();
in.name = name;
in.members =
Arrays.asList(members).stream().map(a -> String.valueOf(a._accountId)).collect(toList());
return gApi.groups().create(in).get();
}
protected void watch(AccountInfo account, Project.NameKey project, String filter)
throws RestApiException {
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = project.get();
pwi.filter = filter;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
gApi.accounts().id(account._accountId).setWatchedProjects(projectsToWatch);
}
protected String quote(String s) {
return "\"" + s + "\"";
}
@Nullable
protected String name(String name) {
if (name == null) {
return null;
}
String suffix = testName.getSanitizedMethodName();
if (name.contains("@")) {
return name + "." + suffix;
}
return name + "_" + suffix;
}
private Account.Id createAccount(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;
}
}
private void setRequestContextForUser(Account.Id userId) {
@SuppressWarnings("unused")
var unused = requestContext.setContext(newRequestContext(userId));
}
private void addEmails(AccountInfo account, String... emails) throws Exception {
Account.Id id = Account.id(account._accountId);
for (String email : emails) {
accountManager.link(id, authRequestFactory.createForEmail(email));
}
accountIndexer.index(id);
}
protected QueryRequest newQuery(Object query) throws RestApiException {
return gApi.accounts().query(query.toString());
}
@CanIgnoreReturnValue
protected List<AccountInfo> assertQuery(Object query, AccountInfo... accounts) throws Exception {
return assertQuery(newQuery(query), accounts);
}
@CanIgnoreReturnValue
protected List<AccountInfo> assertQuery(QueryRequest query, AccountInfo... accounts)
throws Exception {
return assertQuery(query, Arrays.asList(accounts));
}
@CanIgnoreReturnValue
protected List<AccountInfo> assertQuery(QueryRequest query, List<AccountInfo> accounts)
throws Exception {
List<AccountInfo> result = query.get();
List<Integer> ids = ids(result);
assertWithMessage(format(query, result, accounts))
.that(ids)
.containsExactlyElementsIn(ids(accounts))
.inOrder();
return result;
}
protected void assertAccounts(List<AccountState> accounts, AccountInfo... expectedAccounts) {
assertThat(accounts.stream().map(a -> a.account().id().get()).collect(toList()))
.containsExactlyElementsIn(
Arrays.asList(expectedAccounts).stream().map(a -> a._accountId).collect(toList()));
}
private String format(
QueryRequest query, List<AccountInfo> actualIds, List<AccountInfo> expectedAccounts) {
StringBuilder b = new StringBuilder();
b.append("query '").append(query.getQuery()).append("' with expected accounts ");
b.append(format(expectedAccounts));
b.append(" and result ");
b.append(format(actualIds));
return b.toString();
}
private String format(Iterable<AccountInfo> accounts) {
StringBuilder b = new StringBuilder();
b.append("[");
Iterator<AccountInfo> it = accounts.iterator();
while (it.hasNext()) {
AccountInfo a = it.next();
b.append("{")
.append(a._accountId)
.append(", ")
.append("name=")
.append(a.name)
.append(", ")
.append("email=")
.append(a.email)
.append(", ")
.append("username=")
.append(a.username)
.append("}");
if (it.hasNext()) {
b.append(", ");
}
}
b.append("]");
return b.toString();
}
protected static List<Integer> ids(AccountInfo... accounts) {
return ids(Arrays.asList(accounts));
}
protected static List<Integer> ids(List<AccountInfo> accounts) {
return accounts.stream().map(a -> a._accountId).collect(toList());
}
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);
}
}
/** Boiler plate code to check two byte arrays for equality */
private static class ByteArrayWrapper {
private byte[] arr;
private ByteArrayWrapper(byte[] arr) {
this.arr = arr;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ByteArrayWrapper)) {
return false;
}
return Arrays.equals(arr, ((ByteArrayWrapper) other).arr);
}
@Override
public int hashCode() {
return Arrays.hashCode(arr);
}
}
}