
The UI has migrated to use the term 'repository' instead of 'project'. We are following through by supporting this as an additional query term in change search. This commit adapts the docs and adds tests for the new parameter. Change-Id: Ie167fd4081e2739f96cf6cff95cb4c70c7dcc431
3258 lines
122 KiB
Java
3258 lines
122 KiB
Java
// Copyright (C) 2013 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.change;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static com.google.common.truth.TruthJUnit.assume;
|
|
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
|
|
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
|
|
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
|
import static com.google.gerrit.server.project.testing.Util.allow;
|
|
import static com.google.gerrit.server.project.testing.Util.category;
|
|
import static com.google.gerrit.server.project.testing.Util.value;
|
|
import static com.google.gerrit.server.project.testing.Util.verified;
|
|
import static java.util.concurrent.TimeUnit.HOURS;
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
import static java.util.stream.Collectors.toList;
|
|
import static org.junit.Assert.fail;
|
|
|
|
import com.google.common.base.MoreObjects;
|
|
import com.google.common.collect.FluentIterable;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Streams;
|
|
import com.google.common.truth.ThrowableSubject;
|
|
import com.google.gerrit.common.Nullable;
|
|
import com.google.gerrit.common.TimeUtil;
|
|
import com.google.gerrit.common.data.LabelType;
|
|
import com.google.gerrit.common.data.Permission;
|
|
import com.google.gerrit.extensions.api.GerritApi;
|
|
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
|
import com.google.gerrit.extensions.api.changes.AssigneeInput;
|
|
import com.google.gerrit.extensions.api.changes.ChangeApi;
|
|
import com.google.gerrit.extensions.api.changes.Changes.QueryRequest;
|
|
import com.google.gerrit.extensions.api.changes.DraftInput;
|
|
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
|
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
|
|
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
|
|
import com.google.gerrit.extensions.api.changes.StarsInput;
|
|
import com.google.gerrit.extensions.api.groups.GroupInput;
|
|
import com.google.gerrit.extensions.api.projects.ConfigInput;
|
|
import com.google.gerrit.extensions.api.projects.ProjectInput;
|
|
import com.google.gerrit.extensions.client.InheritableBoolean;
|
|
import com.google.gerrit.extensions.client.ProjectWatchInfo;
|
|
import com.google.gerrit.extensions.client.ReviewerState;
|
|
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.ChangeMessageInfo;
|
|
import com.google.gerrit.extensions.common.CommentInfo;
|
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
|
import com.google.gerrit.index.FieldDef;
|
|
import com.google.gerrit.index.IndexConfig;
|
|
import com.google.gerrit.index.QueryOptions;
|
|
import com.google.gerrit.index.Schema;
|
|
import com.google.gerrit.lifecycle.LifecycleManager;
|
|
import com.google.gerrit.reviewdb.client.Account;
|
|
import com.google.gerrit.reviewdb.client.Branch;
|
|
import com.google.gerrit.reviewdb.client.Change;
|
|
import com.google.gerrit.reviewdb.client.Patch;
|
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
|
import com.google.gerrit.reviewdb.client.Project;
|
|
import com.google.gerrit.reviewdb.client.RefNames;
|
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
|
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
|
import com.google.gerrit.server.CurrentUser;
|
|
import com.google.gerrit.server.IdentifiedUser;
|
|
import com.google.gerrit.server.PatchSetUtil;
|
|
import com.google.gerrit.server.Sequences;
|
|
import com.google.gerrit.server.ServerInitiated;
|
|
import com.google.gerrit.server.StarredChangesUtil;
|
|
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.externalids.ExternalId;
|
|
import com.google.gerrit.server.change.ChangeInserter;
|
|
import com.google.gerrit.server.change.ChangeTriplet;
|
|
import com.google.gerrit.server.change.PatchSetInserter;
|
|
import com.google.gerrit.server.config.AllUsersName;
|
|
import com.google.gerrit.server.git.meta.MetaDataUpdate;
|
|
import com.google.gerrit.server.index.change.ChangeField;
|
|
import com.google.gerrit.server.index.change.ChangeIndexCollection;
|
|
import com.google.gerrit.server.index.change.ChangeIndexer;
|
|
import com.google.gerrit.server.index.change.IndexedChangeQuery;
|
|
import com.google.gerrit.server.index.change.StalenessChecker;
|
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
|
import com.google.gerrit.server.notedb.NoteDbChangeState;
|
|
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
|
import com.google.gerrit.server.project.ProjectCache;
|
|
import com.google.gerrit.server.project.ProjectConfig;
|
|
import com.google.gerrit.server.schema.SchemaCreator;
|
|
import com.google.gerrit.server.update.BatchUpdate;
|
|
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.DisabledReviewDb;
|
|
import com.google.gerrit.testing.GerritServerTests;
|
|
import com.google.gerrit.testing.InMemoryDatabase;
|
|
import com.google.gerrit.testing.InMemoryRepositoryManager;
|
|
import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
|
|
import com.google.gerrit.testing.TestTimeUtil;
|
|
import com.google.gwtorm.server.SchemaFactory;
|
|
import com.google.inject.Inject;
|
|
import com.google.inject.Injector;
|
|
import com.google.inject.Provider;
|
|
import com.google.inject.util.Providers;
|
|
import java.sql.Timestamp;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.TimeUnit;
|
|
import org.eclipse.jgit.junit.TestRepository;
|
|
import org.eclipse.jgit.lib.ObjectId;
|
|
import org.eclipse.jgit.lib.ObjectInserter;
|
|
import org.eclipse.jgit.lib.ObjectReader;
|
|
import org.eclipse.jgit.lib.PersonIdent;
|
|
import org.eclipse.jgit.lib.Ref;
|
|
import org.eclipse.jgit.lib.RefUpdate;
|
|
import org.eclipse.jgit.revwalk.RevCommit;
|
|
import org.eclipse.jgit.revwalk.RevWalk;
|
|
import org.eclipse.jgit.util.SystemReader;
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Ignore;
|
|
import org.junit.Test;
|
|
|
|
@Ignore
|
|
public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|
@Inject protected Accounts accounts;
|
|
@Inject protected AccountCache accountCache;
|
|
@Inject @ServerInitiated protected Provider<AccountsUpdate> accountsUpdate;
|
|
@Inject protected AccountManager accountManager;
|
|
@Inject protected AllUsersName allUsersName;
|
|
@Inject protected BatchUpdate.Factory updateFactory;
|
|
@Inject protected ChangeInserter.Factory changeFactory;
|
|
@Inject protected ChangeQueryBuilder queryBuilder;
|
|
@Inject protected GerritApi gApi;
|
|
@Inject protected IdentifiedUser.GenericFactory userFactory;
|
|
@Inject protected ChangeIndexCollection indexes;
|
|
@Inject protected ChangeIndexer indexer;
|
|
@Inject protected IndexConfig indexConfig;
|
|
@Inject protected InMemoryRepositoryManager repoManager;
|
|
@Inject protected Provider<InternalChangeQuery> queryProvider;
|
|
@Inject protected ChangeNotes.Factory notesFactory;
|
|
@Inject protected OneOffRequestContext oneOffRequestContext;
|
|
@Inject protected PatchSetInserter.Factory patchSetFactory;
|
|
@Inject protected PatchSetUtil psUtil;
|
|
@Inject protected ChangeNotes.Factory changeNotesFactory;
|
|
@Inject protected Provider<ChangeQueryProcessor> queryProcessorProvider;
|
|
@Inject protected SchemaCreator schemaCreator;
|
|
@Inject protected SchemaFactory<ReviewDb> schemaFactory;
|
|
@Inject protected Sequences seq;
|
|
@Inject protected ThreadLocalRequestContext requestContext;
|
|
@Inject protected ProjectCache projectCache;
|
|
@Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
|
|
@Inject protected IdentifiedUser.GenericFactory identifiedUserFactory;
|
|
|
|
// Only for use in setting up/tearing down injector; other users should use schemaFactory.
|
|
@Inject private InMemoryDatabase inMemoryDatabase;
|
|
|
|
protected Injector injector;
|
|
protected LifecycleManager lifecycle;
|
|
protected ReviewDb db;
|
|
protected Account.Id userId;
|
|
protected CurrentUser user;
|
|
|
|
private String systemTimeZone;
|
|
|
|
// These queries must be kept in sync with PolyGerrit:
|
|
// polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
|
|
|
|
protected static final String DASHBOARD_WORK_IN_PROGRESS_QUERY = "is:open owner:${user} is:wip";
|
|
protected static final String DASHBOARD_OUTGOING_QUERY =
|
|
"is:open owner:${user} -is:wip -is:ignored";
|
|
protected static final String DASHBOARD_INCOMING_QUERY =
|
|
"is:open -owner:${user} -is:wip -is:ignored (reviewer:${user} OR assignee:${user})";
|
|
protected static final String DASHBOARD_RECENTLY_CLOSED_QUERY =
|
|
"is:closed -is:ignored (-is:wip OR owner:self) "
|
|
+ "(owner:${user} OR reviewer:${user} OR assignee:${user})";
|
|
|
|
protected abstract Injector createInjector();
|
|
|
|
@Before
|
|
public void setUpInjector() throws Exception {
|
|
lifecycle = new LifecycleManager();
|
|
injector = createInjector();
|
|
lifecycle.add(injector);
|
|
injector.injectMembers(this);
|
|
lifecycle.start();
|
|
initAfterLifecycleStart();
|
|
setUpDatabase();
|
|
}
|
|
|
|
@After
|
|
public void cleanUp() {
|
|
lifecycle.stop();
|
|
db.close();
|
|
}
|
|
|
|
protected void initAfterLifecycleStart() throws Exception {}
|
|
|
|
protected void setUpDatabase() throws Exception {
|
|
try (ReviewDb underlyingDb = inMemoryDatabase.getDatabase().open()) {
|
|
schemaCreator.create(underlyingDb);
|
|
}
|
|
db = schemaFactory.open();
|
|
|
|
userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
|
|
String email = "user@example.com";
|
|
accountsUpdate
|
|
.get()
|
|
.update(
|
|
"Add Email",
|
|
userId,
|
|
u -> u.addExternalId(ExternalId.createEmail(userId, email)).setPreferredEmail(email));
|
|
resetUser();
|
|
}
|
|
|
|
protected RequestContext newRequestContext(Account.Id requestUserId) {
|
|
final CurrentUser requestUser = userFactory.create(requestUserId);
|
|
return new RequestContext() {
|
|
@Override
|
|
public CurrentUser getUser() {
|
|
return requestUser;
|
|
}
|
|
|
|
@Override
|
|
public Provider<ReviewDb> getReviewDbProvider() {
|
|
return Providers.of(db);
|
|
}
|
|
};
|
|
}
|
|
|
|
protected void resetUser() {
|
|
user = userFactory.create(userId);
|
|
requestContext.setContext(newRequestContext(userId));
|
|
}
|
|
|
|
@After
|
|
public void tearDownInjector() {
|
|
if (lifecycle != null) {
|
|
lifecycle.stop();
|
|
}
|
|
requestContext.setContext(null);
|
|
if (db != null) {
|
|
db.close();
|
|
}
|
|
InMemoryDatabase.drop(inMemoryDatabase);
|
|
}
|
|
|
|
@Before
|
|
public void setTimeForTesting() {
|
|
resetTimeWithClockStep(1, SECONDS);
|
|
}
|
|
|
|
private void resetTimeWithClockStep(long clockStep, TimeUnit clockStepUnit) {
|
|
systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
|
|
// TODO(dborowitz): Figure out why tests fail when stubbing out
|
|
// SystemReader.
|
|
TestTimeUtil.resetWithClockStep(clockStep, clockStepUnit);
|
|
SystemReader.setInstance(null);
|
|
}
|
|
|
|
@After
|
|
public void resetTime() {
|
|
TestTimeUtil.useSystemTime();
|
|
System.setProperty("user.timezone", systemTimeZone);
|
|
}
|
|
|
|
@Test
|
|
public void byId() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
assertQuery("12345");
|
|
assertQuery(change1.getId().get(), change1);
|
|
assertQuery(change2.getId().get(), change2);
|
|
}
|
|
|
|
@Test
|
|
public void byKey() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change = insert(repo, newChange(repo));
|
|
String key = change.getKey().get();
|
|
|
|
assertQuery("I0000000000000000000000000000000000000000");
|
|
for (int i = 0; i <= 36; i++) {
|
|
String q = key.substring(0, 41 - i);
|
|
assertQuery(q, change);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void byTriplet() throws Exception {
|
|
TestRepository<Repo> repo = createProject("iabcde");
|
|
Change change = insert(repo, newChangeForBranch(repo, "branch"));
|
|
String k = change.getKey().get();
|
|
|
|
assertQuery("iabcde~branch~" + k, change);
|
|
assertQuery("change:iabcde~branch~" + k, change);
|
|
assertQuery("iabcde~refs/heads/branch~" + k, change);
|
|
assertQuery("change:iabcde~refs/heads/branch~" + k, change);
|
|
assertQuery("iabcde~branch~" + k.substring(0, 10), change);
|
|
assertQuery("change:iabcde~branch~" + k.substring(0, 10), change);
|
|
|
|
assertQuery("foo~bar");
|
|
assertThatQueryException("change:foo~bar").hasMessageThat().isEqualTo("Invalid change format");
|
|
assertQuery("otherrepo~branch~" + k);
|
|
assertQuery("change:otherrepo~branch~" + k);
|
|
assertQuery("iabcde~otherbranch~" + k);
|
|
assertQuery("change:iabcde~otherbranch~" + k);
|
|
assertQuery("iabcde~branch~I0000000000000000000000000000000000000000");
|
|
assertQuery("change:iabcde~branch~I0000000000000000000000000000000000000000");
|
|
}
|
|
|
|
@Test
|
|
public void byStatus() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
|
|
Change change1 = insert(repo, ins1);
|
|
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.MERGED);
|
|
Change change2 = insert(repo, ins2);
|
|
|
|
assertQuery("status:new", change1);
|
|
assertQuery("status:NEW", change1);
|
|
assertQuery("is:new", change1);
|
|
assertQuery("status:merged", change2);
|
|
assertQuery("is:merged", change2);
|
|
assertQuery("status:draft");
|
|
assertQuery("is:draft");
|
|
}
|
|
|
|
@Test
|
|
public void byStatusOpen() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
|
|
Change change1 = insert(repo, ins1);
|
|
insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
|
|
|
|
Change[] expected = new Change[] {change1};
|
|
assertQuery("status:open", expected);
|
|
assertQuery("status:OPEN", expected);
|
|
assertQuery("status:o", expected);
|
|
assertQuery("status:op", expected);
|
|
assertQuery("status:ope", expected);
|
|
assertQuery("status:pending", expected);
|
|
assertQuery("status:PENDING", expected);
|
|
assertQuery("status:p", expected);
|
|
assertQuery("status:pe", expected);
|
|
assertQuery("status:pen", expected);
|
|
assertQuery("is:open", expected);
|
|
assertQuery("is:pending", expected);
|
|
}
|
|
|
|
@Test
|
|
public void byStatusClosed() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.MERGED);
|
|
Change change1 = insert(repo, ins1);
|
|
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.ABANDONED);
|
|
Change change2 = insert(repo, ins2);
|
|
insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
|
|
|
|
Change[] expected = new Change[] {change2, change1};
|
|
assertQuery("status:closed", expected);
|
|
assertQuery("status:CLOSED", expected);
|
|
assertQuery("status:c", expected);
|
|
assertQuery("status:cl", expected);
|
|
assertQuery("status:clo", expected);
|
|
assertQuery("status:clos", expected);
|
|
assertQuery("status:close", expected);
|
|
assertQuery("status:closed", expected);
|
|
assertQuery("is:closed", expected);
|
|
}
|
|
|
|
@Test
|
|
public void byStatusAbandoned() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.MERGED);
|
|
insert(repo, ins1);
|
|
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.ABANDONED);
|
|
Change change1 = insert(repo, ins2);
|
|
insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
|
|
|
|
assertQuery("status:abandoned", change1);
|
|
assertQuery("status:ABANDONED", change1);
|
|
assertQuery("is:abandoned", change1);
|
|
}
|
|
|
|
@Test
|
|
public void byStatusPrefix() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
|
|
Change change1 = insert(repo, ins1);
|
|
insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
|
|
|
|
assertQuery("status:n", change1);
|
|
assertQuery("status:ne", change1);
|
|
assertQuery("status:new", change1);
|
|
assertQuery("status:N", change1);
|
|
assertQuery("status:nE", change1);
|
|
assertQuery("status:neW", change1);
|
|
assertQuery("status:nx");
|
|
assertQuery("status:newx");
|
|
}
|
|
|
|
@Test
|
|
public void byPrivate() throws Exception {
|
|
if (getSchemaVersion() < 40) {
|
|
assertMissingField(ChangeField.PRIVATE);
|
|
assertFailingQuery(
|
|
"is:private", "'is:private' operator is not supported by change index version");
|
|
return;
|
|
}
|
|
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo), userId);
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
Change change2 = insert(repo, newChange(repo), user2);
|
|
|
|
// No private changes.
|
|
assertQuery("is:open", change2, change1);
|
|
assertQuery("is:private");
|
|
|
|
gApi.changes().id(change1.getChangeId()).setPrivate(true, null);
|
|
|
|
// Change1 is not private, but should be still visible to its owner.
|
|
assertQuery("is:open", change1, change2);
|
|
assertQuery("is:private", change1);
|
|
|
|
// Switch request context to user2.
|
|
requestContext.setContext(newRequestContext(user2));
|
|
assertQuery("is:open", change2);
|
|
assertQuery("is:private");
|
|
}
|
|
|
|
@Test
|
|
public void byWip() throws Exception {
|
|
if (getSchemaVersion() < 42) {
|
|
assertMissingField(ChangeField.WIP);
|
|
assertFailingQuery("is:wip", "'is:wip' operator is not supported by change index version");
|
|
return;
|
|
}
|
|
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo), userId);
|
|
|
|
assertQuery("is:open", change1);
|
|
assertQuery("is:wip");
|
|
|
|
gApi.changes().id(change1.getChangeId()).setWorkInProgress();
|
|
|
|
assertQuery("is:wip", change1);
|
|
|
|
gApi.changes().id(change1.getChangeId()).setReadyForReview();
|
|
|
|
assertQuery("is:wip");
|
|
}
|
|
|
|
@Test
|
|
public void excludeWipChangeFromReviewersDashboardsBeforeSchema42() throws Exception {
|
|
assume().that(getSchemaVersion()).isLessThan(42);
|
|
|
|
assertMissingField(ChangeField.WIP);
|
|
assertFailingQuery("is:wip", "'is:wip' operator is not supported by change index version");
|
|
|
|
Account.Id user1 = createAccount("user1");
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChangeWorkInProgress(repo), userId);
|
|
assertQuery("reviewer:" + user1, change1);
|
|
gApi.changes().id(change1.getChangeId()).setWorkInProgress();
|
|
assertQuery("reviewer:" + user1, change1);
|
|
}
|
|
|
|
@Test
|
|
public void excludeWipChangeFromReviewersDashboards() throws Exception {
|
|
assume().that(getSchemaVersion()).isAtLeast(42);
|
|
|
|
Account.Id user1 = createAccount("user1");
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChangeWorkInProgress(repo), userId);
|
|
|
|
assertQuery("is:wip", change1);
|
|
assertQuery("reviewer:" + user1);
|
|
|
|
gApi.changes().id(change1.getChangeId()).setReadyForReview();
|
|
assertQuery("is:wip");
|
|
assertQuery("reviewer:" + user1);
|
|
|
|
gApi.changes().id(change1.getChangeId()).setWorkInProgress();
|
|
assertQuery("is:wip", change1);
|
|
assertQuery("reviewer:" + user1);
|
|
}
|
|
|
|
@Test
|
|
public void byStartedBeforeSchema44() throws Exception {
|
|
assume().that(getSchemaVersion()).isLessThan(44);
|
|
assertMissingField(ChangeField.STARTED);
|
|
assertFailingQuery(
|
|
"is:started", "'is:started' operator is not supported by change index version");
|
|
}
|
|
|
|
@Test
|
|
public void byStarted() throws Exception {
|
|
assume().that(getSchemaVersion()).isAtLeast(44);
|
|
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChangeWorkInProgress(repo));
|
|
|
|
assertQuery("is:started");
|
|
|
|
gApi.changes().id(change1.getChangeId()).setReadyForReview();
|
|
assertQuery("is:started", change1);
|
|
|
|
gApi.changes().id(change1.getChangeId()).setWorkInProgress();
|
|
assertQuery("is:started", change1);
|
|
}
|
|
|
|
private void assertReviewers(Collection<AccountInfo> reviewers, Object... expected)
|
|
throws Exception {
|
|
if (expected.length == 0) {
|
|
assertThat(reviewers).isNull();
|
|
return;
|
|
}
|
|
|
|
// Convert AccountInfos to strings, either account ID or email.
|
|
List<String> reviewerIds =
|
|
reviewers
|
|
.stream()
|
|
.map(
|
|
ai -> {
|
|
if (ai._accountId != null) {
|
|
return ai._accountId.toString();
|
|
}
|
|
return ai.email;
|
|
})
|
|
.collect(toList());
|
|
assertThat(reviewerIds).containsExactly(expected);
|
|
}
|
|
|
|
@Test
|
|
public void restorePendingReviewers() throws Exception {
|
|
assume().that(getSchemaVersion()).isAtLeast(44);
|
|
assume().that(notesMigration.readChanges()).isTrue();
|
|
|
|
Project.NameKey project = new Project.NameKey("repo");
|
|
TestRepository<Repo> repo = createProject(project.get());
|
|
ConfigInput conf = new ConfigInput();
|
|
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
|
|
gApi.projects().name(project.get()).config(conf);
|
|
Change change1 = insert(repo, newChangeWorkInProgress(repo));
|
|
Account.Id user1 = createAccount("user1");
|
|
Account.Id user2 = createAccount("user2");
|
|
String email1 = "email1@example.com";
|
|
String email2 = "email2@example.com";
|
|
|
|
ReviewInput in =
|
|
ReviewInput.noScore()
|
|
.reviewer(user1.toString())
|
|
.reviewer(user2.toString(), ReviewerState.CC, false)
|
|
.reviewer(email1)
|
|
.reviewer(email2, ReviewerState.CC, false);
|
|
gApi.changes().id(change1.getId().get()).revision("current").review(in);
|
|
|
|
List<ChangeInfo> changeInfos =
|
|
assertQuery(newQuery("is:wip").withOption(DETAILED_LABELS), change1);
|
|
assertThat(changeInfos).isNotEmpty();
|
|
|
|
Map<ReviewerState, Collection<AccountInfo>> pendingReviewers =
|
|
changeInfos.get(0).pendingReviewers;
|
|
assertThat(pendingReviewers).isNotNull();
|
|
|
|
assertReviewers(
|
|
pendingReviewers.get(ReviewerState.REVIEWER), userId.toString(), user1.toString(), email1);
|
|
assertReviewers(pendingReviewers.get(ReviewerState.CC), user2.toString(), email2);
|
|
assertReviewers(pendingReviewers.get(ReviewerState.REMOVED));
|
|
|
|
// Pending reviewers may also be presented in the REMOVED state. Toggle the
|
|
// change to ready and then back to WIP and remove reviewers to produce.
|
|
assertThat(pendingReviewers.get(ReviewerState.REMOVED)).isNull();
|
|
gApi.changes().id(change1.getId().get()).setReadyForReview();
|
|
gApi.changes().id(change1.getId().get()).setWorkInProgress();
|
|
gApi.changes().id(change1.getId().get()).reviewer(user1.toString()).remove();
|
|
gApi.changes().id(change1.getId().get()).reviewer(user2.toString()).remove();
|
|
gApi.changes().id(change1.getId().get()).reviewer(email1).remove();
|
|
gApi.changes().id(change1.getId().get()).reviewer(email2).remove();
|
|
|
|
changeInfos = assertQuery(newQuery("is:wip").withOption(DETAILED_LABELS), change1);
|
|
assertThat(changeInfos).isNotEmpty();
|
|
|
|
pendingReviewers = changeInfos.get(0).pendingReviewers;
|
|
assertThat(pendingReviewers).isNotNull();
|
|
assertReviewers(pendingReviewers.get(ReviewerState.REVIEWER));
|
|
assertReviewers(pendingReviewers.get(ReviewerState.CC));
|
|
assertReviewers(
|
|
pendingReviewers.get(ReviewerState.REMOVED),
|
|
user1.toString(),
|
|
user2.toString(),
|
|
email1,
|
|
email2);
|
|
}
|
|
|
|
@Test
|
|
public void byCommit() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins = newChange(repo);
|
|
Change change = insert(repo, ins);
|
|
String sha = ins.getCommitId().name();
|
|
|
|
assertQuery("0000000000000000000000000000000000000000");
|
|
assertQuery("commit:0000000000000000000000000000000000000000");
|
|
for (int i = 0; i <= 36; i++) {
|
|
String q = sha.substring(0, 40 - i);
|
|
assertQuery(q, change);
|
|
assertQuery("commit:" + q, change);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void byOwner() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo), userId);
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
Change change2 = insert(repo, newChange(repo), user2);
|
|
|
|
assertQuery("is:owner", change1);
|
|
assertQuery("owner:" + userId.get(), change1);
|
|
assertQuery("owner:" + user2, change2);
|
|
|
|
String nameEmail = user.asIdentifiedUser().getNameEmail();
|
|
assertQuery("owner: \"" + nameEmail + "\"", change1);
|
|
}
|
|
|
|
@Test
|
|
public void byAuthorExact() throws Exception {
|
|
assume().that(getSchema().hasField(ChangeField.EXACT_AUTHOR)).isTrue();
|
|
byAuthorOrCommitterExact("author:");
|
|
}
|
|
|
|
@Test
|
|
public void byAuthorFullText() throws Exception {
|
|
byAuthorOrCommitterFullText("author:");
|
|
}
|
|
|
|
@Test
|
|
public void byCommitterExact() throws Exception {
|
|
assume().that(getSchema().hasField(ChangeField.EXACT_COMMITTER)).isTrue();
|
|
byAuthorOrCommitterExact("committer:");
|
|
}
|
|
|
|
@Test
|
|
public void byCommitterFullText() throws Exception {
|
|
byAuthorOrCommitterFullText("committer:");
|
|
}
|
|
|
|
private void byAuthorOrCommitterExact(String searchOperator) throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
|
|
PersonIdent john = new PersonIdent("John", "john@example.com");
|
|
PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
|
|
Change change1 = createChange(repo, johnDoe);
|
|
Change change2 = createChange(repo, john);
|
|
Change change3 = createChange(repo, doeSmith);
|
|
|
|
// Only email address.
|
|
assertQuery(searchOperator + "john.doe@example.com", change1);
|
|
assertQuery(searchOperator + "john@example.com", change2);
|
|
assertQuery(searchOperator + "Doe_SmIth@example.com", change3); // Case insensitive.
|
|
|
|
// Right combination of email address and name.
|
|
assertQuery(searchOperator + "\"John Doe <john.doe@example.com>\"", change1);
|
|
assertQuery(searchOperator + "\" John <john@example.com> \"", change2);
|
|
assertQuery(searchOperator + "\"doE SMITH <doe_smitH@example.com>\"", change3);
|
|
|
|
// Wrong combination of email address and name.
|
|
assertQuery(searchOperator + "\"John <john.doe@example.com>\"");
|
|
assertQuery(searchOperator + "\"Doe John <john@example.com>\"");
|
|
assertQuery(searchOperator + "\"Doe John <doe_smith@example.com>\"");
|
|
}
|
|
|
|
private void byAuthorOrCommitterFullText(String searchOperator) throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
|
|
PersonIdent john = new PersonIdent("John", "john@example.com");
|
|
PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
|
|
Change change1 = createChange(repo, johnDoe);
|
|
Change change2 = createChange(repo, john);
|
|
Change change3 = createChange(repo, doeSmith);
|
|
|
|
// By exact name.
|
|
assertQuery(searchOperator + "\"John Doe\"", change1);
|
|
assertQuery(searchOperator + "\"john\"", change2, change1);
|
|
assertQuery(searchOperator + "\"Doe smith\"", change3);
|
|
|
|
// By name part.
|
|
assertQuery(searchOperator + "Doe", change3, change1);
|
|
assertQuery(searchOperator + "smith", change3);
|
|
|
|
// By wrong combination.
|
|
assertQuery(searchOperator + "\"John Smith\"");
|
|
|
|
// By invalid query.
|
|
exception.expect(BadRequestException.class);
|
|
exception.expectMessage("invalid value");
|
|
// SchemaUtil.getNameParts will return an empty set for query only containing these characters.
|
|
assertQuery(searchOperator + "@.- /_");
|
|
}
|
|
|
|
private Change createChange(TestRepository<Repo> repo, PersonIdent person) throws Exception {
|
|
RevCommit commit =
|
|
repo.parseBody(repo.commit().message("message").author(person).committer(person).create());
|
|
return insert(repo, newChangeForCommit(repo, commit), null);
|
|
}
|
|
|
|
@Test
|
|
public void byOwnerIn() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo), userId);
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
Change change2 = insert(repo, newChange(repo), user2);
|
|
Change change3 = insert(repo, newChange(repo), user2);
|
|
gApi.changes().id(change3.getId().get()).current().review(ReviewInput.approve());
|
|
gApi.changes().id(change3.getId().get()).current().submit();
|
|
|
|
assertQuery("ownerin:Administrators", change1);
|
|
assertQuery("ownerin:\"Registered Users\"", change3, change2, change1);
|
|
assertQuery("ownerin:\"Registered Users\" project:repo", change3, change2, change1);
|
|
assertQuery("ownerin:\"Registered Users\" status:merged", change3);
|
|
}
|
|
|
|
@Test
|
|
public void byProject() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
TestRepository<Repo> repo2 = createProject("repo2");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertQuery("project:foo");
|
|
assertQuery("project:repo");
|
|
assertQuery("project:repo1", change1);
|
|
assertQuery("project:repo2", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byParentProject() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
TestRepository<Repo> repo2 = createProject("repo2", "repo1");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertQuery("parentproject:repo1", change2, change1);
|
|
assertQuery("parentproject:repo2", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byProjectPrefix() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
TestRepository<Repo> repo2 = createProject("repo2");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertQuery("projects:foo");
|
|
assertQuery("projects:repo1", change1);
|
|
assertQuery("projects:repo2", change2);
|
|
assertQuery("projects:repo", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byRepository() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
TestRepository<Repo> repo2 = createProject("repo2");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertQuery("repository:foo");
|
|
assertQuery("repository:repo");
|
|
assertQuery("repository:repo1", change1);
|
|
assertQuery("repository:repo2", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byParentRepository() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
TestRepository<Repo> repo2 = createProject("repo2", "repo1");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertQuery("parentrepository:repo1", change2, change1);
|
|
assertQuery("parentrepository:repo2", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byRepositoryPrefix() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
TestRepository<Repo> repo2 = createProject("repo2");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertQuery("repositories:foo");
|
|
assertQuery("repositories:repo1", change1);
|
|
assertQuery("repositories:repo2", change2);
|
|
assertQuery("repositories:repo", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byBranchAndRef() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChangeForBranch(repo, "master"));
|
|
Change change2 = insert(repo, newChangeForBranch(repo, "branch"));
|
|
|
|
assertQuery("branch:foo");
|
|
assertQuery("branch:master", change1);
|
|
assertQuery("branch:refs/heads/master", change1);
|
|
assertQuery("ref:master");
|
|
assertQuery("ref:refs/heads/master", change1);
|
|
assertQuery("branch:refs/heads/master", change1);
|
|
assertQuery("branch:branch", change2);
|
|
assertQuery("branch:refs/heads/branch", change2);
|
|
assertQuery("ref:branch");
|
|
assertQuery("ref:refs/heads/branch", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byTopic() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithTopic(repo, "feature1");
|
|
Change change1 = insert(repo, ins1);
|
|
|
|
ChangeInserter ins2 = newChangeWithTopic(repo, "feature2");
|
|
Change change2 = insert(repo, ins2);
|
|
|
|
ChangeInserter ins3 = newChangeWithTopic(repo, "Cherrypick-feature2");
|
|
Change change3 = insert(repo, ins3);
|
|
|
|
ChangeInserter ins4 = newChangeWithTopic(repo, "feature2-fixup");
|
|
Change change4 = insert(repo, ins4);
|
|
|
|
ChangeInserter ins5 = newChangeWithTopic(repo, "https://gerrit.local");
|
|
Change change5 = insert(repo, ins5);
|
|
|
|
ChangeInserter ins6 = newChangeWithTopic(repo, "git_gerrit_training");
|
|
Change change6 = insert(repo, ins6);
|
|
|
|
Change change_no_topic = insert(repo, newChange(repo));
|
|
|
|
assertQuery("intopic:foo");
|
|
assertQuery("intopic:feature1", change1);
|
|
assertQuery("intopic:feature2", change4, change3, change2);
|
|
assertQuery("topic:feature2", change2);
|
|
assertQuery("intopic:feature2", change4, change3, change2);
|
|
assertQuery("intopic:fixup", change4);
|
|
assertQuery("intopic:gerrit", change6, change5);
|
|
assertQuery("topic:\"\"", change_no_topic);
|
|
assertQuery("intopic:\"\"", change_no_topic);
|
|
}
|
|
|
|
@Test
|
|
public void byTopicRegex() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
|
|
ChangeInserter ins1 = newChangeWithTopic(repo, "feature1");
|
|
Change change1 = insert(repo, ins1);
|
|
|
|
ChangeInserter ins2 = newChangeWithTopic(repo, "Cherrypick-feature1");
|
|
Change change2 = insert(repo, ins2);
|
|
|
|
ChangeInserter ins3 = newChangeWithTopic(repo, "feature1-fixup");
|
|
Change change3 = insert(repo, ins3);
|
|
|
|
assertQuery("intopic:^feature1.*", change3, change1);
|
|
assertQuery("intopic:{^.*feature1$}", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byMessageExact() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 = repo.parseBody(repo.commit().message("one").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
assertQuery("message:foo");
|
|
assertQuery("message:one", change1);
|
|
assertQuery("message:two", change2);
|
|
}
|
|
|
|
@Test
|
|
public void fullTextWithNumbers() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 = repo.parseBody(repo.commit().message("12345 67890").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
RevCommit commit2 = repo.parseBody(repo.commit().message("12346 67891").create());
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
assertQuery("message:1234");
|
|
assertQuery("message:12345", change1);
|
|
assertQuery("message:12346", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byMessageMixedCase() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 = repo.parseBody(repo.commit().message("Hello gerrit").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
RevCommit commit2 = repo.parseBody(repo.commit().message("Hello Gerrit").create());
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
assertQuery("message:gerrit", change2, change1);
|
|
assertQuery("message:Gerrit", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byMessageSubstring() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 = repo.parseBody(repo.commit().message("https://gerrit.local").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
assertQuery("message:gerrit", change1);
|
|
}
|
|
|
|
@Test
|
|
public void byLabel() throws Exception {
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser"));
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins2 = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins3 = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins4 = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins5 = newChange(repo, null, null, null, null, false);
|
|
|
|
Change reviewMinus2Change = insert(repo, ins);
|
|
gApi.changes().id(reviewMinus2Change.getId().get()).current().review(ReviewInput.reject());
|
|
|
|
Change reviewMinus1Change = insert(repo, ins2);
|
|
gApi.changes().id(reviewMinus1Change.getId().get()).current().review(ReviewInput.dislike());
|
|
|
|
Change noLabelChange = insert(repo, ins3);
|
|
|
|
Change reviewPlus1Change = insert(repo, ins4);
|
|
gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
|
|
|
|
Change reviewPlus2Change = insert(repo, ins5);
|
|
gApi.changes().id(reviewPlus2Change.getId().get()).current().review(ReviewInput.approve());
|
|
|
|
Map<String, Short> m =
|
|
gApi.changes()
|
|
.id(reviewPlus1Change.getId().get())
|
|
.reviewer(user.getAccountId().toString())
|
|
.votes();
|
|
assertThat(m).hasSize(1);
|
|
assertThat(m).containsEntry("Code-Review", Short.valueOf((short) 1));
|
|
|
|
Map<Integer, Change> changes = new LinkedHashMap<>(5);
|
|
changes.put(2, reviewPlus2Change);
|
|
changes.put(1, reviewPlus1Change);
|
|
changes.put(0, noLabelChange);
|
|
changes.put(-1, reviewMinus1Change);
|
|
changes.put(-2, reviewMinus2Change);
|
|
|
|
assertQuery("label:Code-Review=-2", reviewMinus2Change);
|
|
assertQuery("label:Code-Review-2", reviewMinus2Change);
|
|
assertQuery("label:Code-Review=-1", reviewMinus1Change);
|
|
assertQuery("label:Code-Review-1", reviewMinus1Change);
|
|
assertQuery("label:Code-Review=0", noLabelChange);
|
|
assertQuery("label:Code-Review=+1", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=1", reviewPlus1Change);
|
|
assertQuery("label:Code-Review+1", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+2", reviewPlus2Change);
|
|
assertQuery("label:Code-Review=2", reviewPlus2Change);
|
|
assertQuery("label:Code-Review+2", reviewPlus2Change);
|
|
|
|
assertQuery("label:Code-Review>-3", codeReviewInRange(changes, -2, 2));
|
|
assertQuery("label:Code-Review>=-2", codeReviewInRange(changes, -2, 2));
|
|
assertQuery("label:Code-Review>-2", codeReviewInRange(changes, -1, 2));
|
|
assertQuery("label:Code-Review>=-1", codeReviewInRange(changes, -1, 2));
|
|
assertQuery("label:Code-Review>-1", codeReviewInRange(changes, 0, 2));
|
|
assertQuery("label:Code-Review>=0", codeReviewInRange(changes, 0, 2));
|
|
assertQuery("label:Code-Review>0", codeReviewInRange(changes, 1, 2));
|
|
assertQuery("label:Code-Review>=1", codeReviewInRange(changes, 1, 2));
|
|
assertQuery("label:Code-Review>1", reviewPlus2Change);
|
|
assertQuery("label:Code-Review>=2", reviewPlus2Change);
|
|
assertQuery("label:Code-Review>2");
|
|
|
|
assertQuery("label:Code-Review<=2", codeReviewInRange(changes, -2, 2));
|
|
assertQuery("label:Code-Review<2", codeReviewInRange(changes, -2, 1));
|
|
assertQuery("label:Code-Review<=1", codeReviewInRange(changes, -2, 1));
|
|
assertQuery("label:Code-Review<1", codeReviewInRange(changes, -2, 0));
|
|
assertQuery("label:Code-Review<=0", codeReviewInRange(changes, -2, 0));
|
|
assertQuery("label:Code-Review<0", codeReviewInRange(changes, -2, -1));
|
|
assertQuery("label:Code-Review<=-1", codeReviewInRange(changes, -2, -1));
|
|
assertQuery("label:Code-Review<-1", reviewMinus2Change);
|
|
assertQuery("label:Code-Review<=-2", reviewMinus2Change);
|
|
assertQuery("label:Code-Review<-2");
|
|
|
|
assertQuery("label:Code-Review=+1,anotheruser");
|
|
assertQuery("label:Code-Review=+1,user", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+1,user=user", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+1,Administrators", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+1,group=Administrators", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+1,user=owner", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+1,owner", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+2,owner", reviewPlus2Change);
|
|
assertQuery("label:Code-Review=-2,owner", reviewMinus2Change);
|
|
}
|
|
|
|
@Test
|
|
public void byLabelMulti() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Project.NameKey project =
|
|
new Project.NameKey(repo.getRepository().getDescription().getRepositoryName());
|
|
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
|
|
|
LabelType verified =
|
|
category("Verified", value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
|
|
cfg.getLabelSections().put(verified.getName(), verified);
|
|
|
|
String heads = RefNames.REFS_HEADS + "*";
|
|
allow(cfg, Permission.forLabel(verified().getName()), -1, 1, REGISTERED_USERS, heads);
|
|
|
|
try (MetaDataUpdate md = metaDataUpdateFactory.create(project)) {
|
|
cfg.commit(md);
|
|
}
|
|
projectCache.evict(cfg.getProject());
|
|
|
|
ReviewInput reviewVerified = new ReviewInput().label("Verified", 1);
|
|
ChangeInserter ins = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins2 = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins3 = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins4 = newChange(repo, null, null, null, null, false);
|
|
ChangeInserter ins5 = newChange(repo, null, null, null, null, false);
|
|
|
|
// CR+1
|
|
Change reviewCRplus1 = insert(repo, ins);
|
|
gApi.changes().id(reviewCRplus1.getId().get()).current().review(ReviewInput.recommend());
|
|
|
|
// CR+2
|
|
Change reviewCRplus2 = insert(repo, ins2);
|
|
gApi.changes().id(reviewCRplus2.getId().get()).current().review(ReviewInput.approve());
|
|
|
|
// CR+1 VR+1
|
|
Change reviewCRplus1VRplus1 = insert(repo, ins3);
|
|
gApi.changes().id(reviewCRplus1VRplus1.getId().get()).current().review(ReviewInput.recommend());
|
|
gApi.changes().id(reviewCRplus1VRplus1.getId().get()).current().review(reviewVerified);
|
|
|
|
// CR+2 VR+1
|
|
Change reviewCRplus2VRplus1 = insert(repo, ins4);
|
|
gApi.changes().id(reviewCRplus2VRplus1.getId().get()).current().review(ReviewInput.approve());
|
|
gApi.changes().id(reviewCRplus2VRplus1.getId().get()).current().review(reviewVerified);
|
|
|
|
// VR+1
|
|
Change reviewVRplus1 = insert(repo, ins5);
|
|
gApi.changes().id(reviewVRplus1.getId().get()).current().review(reviewVerified);
|
|
|
|
assertQuery("label:Code-Review=+1", reviewCRplus1VRplus1, reviewCRplus1);
|
|
assertQuery(
|
|
"label:Code-Review>=+1",
|
|
reviewCRplus2VRplus1,
|
|
reviewCRplus1VRplus1,
|
|
reviewCRplus2,
|
|
reviewCRplus1);
|
|
assertQuery("label:Code-Review>=+2", reviewCRplus2VRplus1, reviewCRplus2);
|
|
|
|
assertQuery(
|
|
"label:Code-Review>=+1 label:Verified=+1", reviewCRplus2VRplus1, reviewCRplus1VRplus1);
|
|
assertQuery("label:Code-Review>=+2 label:Verified=+1", reviewCRplus2VRplus1);
|
|
}
|
|
|
|
@Test
|
|
public void byLabelNotOwner() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins = newChange(repo, null, null, null, null, false);
|
|
Account.Id user1 = createAccount("user1");
|
|
|
|
Change reviewPlus1Change = insert(repo, ins);
|
|
|
|
// post a review with user1
|
|
requestContext.setContext(newRequestContext(user1));
|
|
gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
|
|
|
|
assertQuery("label:Code-Review=+1,user=user1", reviewPlus1Change);
|
|
assertQuery("label:Code-Review=+1,owner");
|
|
}
|
|
|
|
private Change[] codeReviewInRange(Map<Integer, Change> changes, int start, int end) {
|
|
int size = 0;
|
|
Change[] range = new Change[end - start + 1];
|
|
for (int i : changes.keySet()) {
|
|
if (i >= start && i <= end) {
|
|
range[size] = changes.get(i);
|
|
size++;
|
|
}
|
|
}
|
|
return range;
|
|
}
|
|
|
|
private String createGroup(String name, String owner) throws Exception {
|
|
GroupInput in = new GroupInput();
|
|
in.name = name;
|
|
in.ownerId = owner;
|
|
gApi.groups().create(in);
|
|
return name;
|
|
}
|
|
|
|
private Account.Id createAccount(String name) throws Exception {
|
|
return accountManager.authenticate(AuthRequest.forUser(name)).getAccountId();
|
|
}
|
|
|
|
@Test
|
|
public void byLabelGroup() throws Exception {
|
|
Account.Id user1 = createAccount("user1");
|
|
createAccount("user2");
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
|
|
// create group and add users
|
|
String g1 = createGroup("group1", "Administrators");
|
|
String g2 = createGroup("group2", "Administrators");
|
|
gApi.groups().id(g1).addMembers("user1");
|
|
gApi.groups().id(g2).addMembers("user2");
|
|
|
|
// create a change
|
|
Change change1 = insert(repo, newChange(repo), user1);
|
|
|
|
// post a review with user1
|
|
requestContext.setContext(newRequestContext(user1));
|
|
gApi.changes()
|
|
.id(change1.getId().get())
|
|
.current()
|
|
.review(new ReviewInput().label("Code-Review", 1));
|
|
|
|
// verify that query with user1 will return results.
|
|
requestContext.setContext(newRequestContext(userId));
|
|
assertQuery("label:Code-Review=+1,group1", change1);
|
|
assertQuery("label:Code-Review=+1,group=group1", change1);
|
|
assertQuery("label:Code-Review=+1,user=user1", change1);
|
|
assertQuery("label:Code-Review=+1,user=user2");
|
|
assertQuery("label:Code-Review=+1,group=group2");
|
|
}
|
|
|
|
@Test
|
|
public void limit() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change last = null;
|
|
int n = 5;
|
|
for (int i = 0; i < n; i++) {
|
|
last = insert(repo, newChange(repo));
|
|
}
|
|
|
|
for (int i = 1; i <= n + 2; i++) {
|
|
int expectedSize;
|
|
Boolean expectedMoreChanges;
|
|
if (i < n) {
|
|
expectedSize = i;
|
|
expectedMoreChanges = true;
|
|
} else {
|
|
expectedSize = n;
|
|
expectedMoreChanges = null;
|
|
}
|
|
String q = "status:new limit:" + i;
|
|
List<ChangeInfo> results = newQuery(q).get();
|
|
assertThat(results).named(q).hasSize(expectedSize);
|
|
assertThat(results.get(results.size() - 1)._moreChanges)
|
|
.named(q)
|
|
.isEqualTo(expectedMoreChanges);
|
|
assertThat(results.get(0)._number).isEqualTo(last.getId().get());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void start() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
List<Change> changes = new ArrayList<>();
|
|
for (int i = 0; i < 2; i++) {
|
|
changes.add(insert(repo, newChange(repo)));
|
|
}
|
|
|
|
assertQuery("status:new", changes.get(1), changes.get(0));
|
|
assertQuery(newQuery("status:new").withStart(1), changes.get(0));
|
|
assertQuery(newQuery("status:new").withStart(2));
|
|
assertQuery(newQuery("status:new").withStart(3));
|
|
}
|
|
|
|
@Test
|
|
public void startWithLimit() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
List<Change> changes = new ArrayList<>();
|
|
for (int i = 0; i < 3; i++) {
|
|
changes.add(insert(repo, newChange(repo)));
|
|
}
|
|
|
|
assertQuery("status:new limit:2", changes.get(2), changes.get(1));
|
|
assertQuery(newQuery("status:new limit:2").withStart(1), changes.get(1), changes.get(0));
|
|
assertQuery(newQuery("status:new limit:2").withStart(2), changes.get(0));
|
|
assertQuery(newQuery("status:new limit:2").withStart(3));
|
|
}
|
|
|
|
@Test
|
|
public void maxPages() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change = insert(repo, newChange(repo));
|
|
|
|
QueryRequest query = newQuery("status:new").withLimit(10);
|
|
assertQuery(query, change);
|
|
assertQuery(query.withStart(1));
|
|
assertQuery(query.withStart(99));
|
|
assertThatQueryException(query.withStart(100))
|
|
.hasMessageThat()
|
|
.isEqualTo("Cannot go beyond page 10 of results");
|
|
assertQuery(query.withLimit(100).withStart(100));
|
|
}
|
|
|
|
@Test
|
|
public void updateOrder() throws Exception {
|
|
resetTimeWithClockStep(2, MINUTES);
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
List<ChangeInserter> inserters = new ArrayList<>();
|
|
List<Change> changes = new ArrayList<>();
|
|
for (int i = 0; i < 5; i++) {
|
|
inserters.add(newChange(repo));
|
|
changes.add(insert(repo, inserters.get(i)));
|
|
}
|
|
|
|
for (int i : ImmutableList.of(2, 0, 1, 4, 3)) {
|
|
gApi.changes()
|
|
.id(changes.get(i).getId().get())
|
|
.current()
|
|
.review(new ReviewInput().message("modifying " + i));
|
|
}
|
|
|
|
assertQuery(
|
|
"status:new",
|
|
changes.get(3),
|
|
changes.get(4),
|
|
changes.get(1),
|
|
changes.get(0),
|
|
changes.get(2));
|
|
}
|
|
|
|
@Test
|
|
public void updatedOrder() throws Exception {
|
|
resetTimeWithClockStep(1, SECONDS);
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChange(repo);
|
|
Change change1 = insert(repo, ins1);
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
assertThat(lastUpdatedMs(change1)).isLessThan(lastUpdatedMs(change2));
|
|
assertQuery("status:new", change2, change1);
|
|
|
|
gApi.changes().id(change1.getId().get()).topic("new-topic");
|
|
change1 = notesFactory.create(db, change1.getProject(), change1.getId()).getChange();
|
|
|
|
assertThat(lastUpdatedMs(change1)).isGreaterThan(lastUpdatedMs(change2));
|
|
assertThat(lastUpdatedMs(change1) - lastUpdatedMs(change2))
|
|
.isAtLeast(MILLISECONDS.convert(1, SECONDS));
|
|
|
|
// change1 moved to the top.
|
|
assertQuery("status:new", change1, change2);
|
|
}
|
|
|
|
@Test
|
|
public void filterOutMoreThanOnePageOfResults() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change = insert(repo, newChange(repo), userId);
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
for (int i = 0; i < 5; i++) {
|
|
insert(repo, newChange(repo), user2);
|
|
}
|
|
|
|
assertQuery("status:new ownerin:Administrators", change);
|
|
assertQuery("status:new ownerin:Administrators limit:2", change);
|
|
}
|
|
|
|
@Test
|
|
public void filterOutAllResults() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
for (int i = 0; i < 5; i++) {
|
|
insert(repo, newChange(repo), user2);
|
|
}
|
|
|
|
assertQuery("status:new ownerin:Administrators");
|
|
assertQuery("status:new ownerin:Administrators limit:2");
|
|
}
|
|
|
|
@Test
|
|
public void byFileExact() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit =
|
|
repo.parseBody(
|
|
repo.commit()
|
|
.message("one")
|
|
.add("dir/file1", "contents1")
|
|
.add("dir/file2", "contents2")
|
|
.create());
|
|
Change change = insert(repo, newChangeForCommit(repo, commit));
|
|
|
|
assertQuery("file:file");
|
|
assertQuery("file:dir", change);
|
|
assertQuery("file:file1", change);
|
|
assertQuery("file:file2", change);
|
|
assertQuery("file:dir/file1", change);
|
|
assertQuery("file:dir/file2", change);
|
|
}
|
|
|
|
@Test
|
|
public void byFileRegex() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit =
|
|
repo.parseBody(
|
|
repo.commit()
|
|
.message("one")
|
|
.add("dir/file1", "contents1")
|
|
.add("dir/file2", "contents2")
|
|
.create());
|
|
Change change = insert(repo, newChangeForCommit(repo, commit));
|
|
|
|
assertQuery("file:.*file.*");
|
|
assertQuery("file:^file.*"); // Whole path only.
|
|
assertQuery("file:^dir.file.*", change);
|
|
}
|
|
|
|
@Test
|
|
public void byPathExact() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit =
|
|
repo.parseBody(
|
|
repo.commit()
|
|
.message("one")
|
|
.add("dir/file1", "contents1")
|
|
.add("dir/file2", "contents2")
|
|
.create());
|
|
Change change = insert(repo, newChangeForCommit(repo, commit));
|
|
|
|
assertQuery("path:file");
|
|
assertQuery("path:dir");
|
|
assertQuery("path:file1");
|
|
assertQuery("path:file2");
|
|
assertQuery("path:dir/file1", change);
|
|
assertQuery("path:dir/file2", change);
|
|
}
|
|
|
|
@Test
|
|
public void byPathRegex() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit =
|
|
repo.parseBody(
|
|
repo.commit()
|
|
.message("one")
|
|
.add("dir/file1", "contents1")
|
|
.add("dir/file2", "contents2")
|
|
.create());
|
|
Change change = insert(repo, newChangeForCommit(repo, commit));
|
|
|
|
assertQuery("path:.*file.*");
|
|
assertQuery("path:^dir.file.*", change);
|
|
}
|
|
|
|
@Test
|
|
public void byComment() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins = newChange(repo);
|
|
Change change = insert(repo, ins);
|
|
|
|
ReviewInput input = new ReviewInput();
|
|
input.message = "toplevel";
|
|
ReviewInput.CommentInput commentInput = new ReviewInput.CommentInput();
|
|
commentInput.line = 1;
|
|
commentInput.message = "inline";
|
|
input.comments =
|
|
ImmutableMap.<String, List<ReviewInput.CommentInput>>of(
|
|
Patch.COMMIT_MSG, ImmutableList.<ReviewInput.CommentInput>of(commentInput));
|
|
gApi.changes().id(change.getId().get()).current().review(input);
|
|
|
|
Map<String, List<CommentInfo>> comments =
|
|
gApi.changes().id(change.getId().get()).current().comments();
|
|
assertThat(comments).hasSize(1);
|
|
CommentInfo comment = Iterables.getOnlyElement(comments.get(Patch.COMMIT_MSG));
|
|
assertThat(comment.message).isEqualTo(commentInput.message);
|
|
ChangeMessageInfo lastMsg =
|
|
Iterables.getLast(gApi.changes().id(change.getId().get()).get().messages, null);
|
|
assertThat(lastMsg.message).isEqualTo("Patch Set 1:\n\n(1 comment)\n\n" + input.message);
|
|
|
|
assertQuery("comment:foo");
|
|
assertQuery("comment:toplevel", change);
|
|
assertQuery("comment:inline", change);
|
|
}
|
|
|
|
@Test
|
|
public void byAge() throws Exception {
|
|
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
|
|
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
long startMs = TestTimeUtil.START.toEpochMilli();
|
|
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
|
|
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
|
|
|
|
// Stop time so age queries use the same endpoint.
|
|
TestTimeUtil.setClockStep(0, MILLISECONDS);
|
|
TestTimeUtil.setClock(new Timestamp(startMs + 2 * thirtyHoursInMs));
|
|
long nowMs = TimeUtil.nowMs();
|
|
|
|
assertThat(lastUpdatedMs(change2) - lastUpdatedMs(change1)).isEqualTo(thirtyHoursInMs);
|
|
assertThat(nowMs - lastUpdatedMs(change2)).isEqualTo(thirtyHoursInMs);
|
|
assertThat(TimeUtil.nowMs()).isEqualTo(nowMs);
|
|
|
|
assertQuery("-age:1d");
|
|
assertQuery("-age:" + (30 * 60 - 1) + "m");
|
|
assertQuery("-age:2d", change2);
|
|
assertQuery("-age:3d", change2, change1);
|
|
assertQuery("age:3d");
|
|
assertQuery("age:2d", change1);
|
|
assertQuery("age:1d", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byBeforeUntil() throws Exception {
|
|
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
|
|
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
long startMs = TestTimeUtil.START.toEpochMilli();
|
|
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
|
|
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
|
|
TestTimeUtil.setClockStep(0, MILLISECONDS);
|
|
|
|
for (String predicate : Lists.newArrayList("before:", "until:")) {
|
|
assertQuery(predicate + "2009-09-29");
|
|
assertQuery(predicate + "2009-09-30");
|
|
assertQuery(predicate + "\"2009-09-30 16:59:00 -0400\"");
|
|
assertQuery(predicate + "\"2009-09-30 20:59:00 -0000\"");
|
|
assertQuery(predicate + "\"2009-09-30 20:59:00\"");
|
|
assertQuery(predicate + "\"2009-09-30 17:02:00 -0400\"", change1);
|
|
assertQuery(predicate + "\"2009-10-01 21:02:00 -0000\"", change1);
|
|
assertQuery(predicate + "\"2009-10-01 21:02:00\"", change1);
|
|
assertQuery(predicate + "2009-10-01", change1);
|
|
assertQuery(predicate + "2009-10-03", change2, change1);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void byAfterSince() throws Exception {
|
|
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
|
|
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
long startMs = TestTimeUtil.START.toEpochMilli();
|
|
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
|
|
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
|
|
TestTimeUtil.setClockStep(0, MILLISECONDS);
|
|
|
|
for (String predicate : Lists.newArrayList("after:", "since:")) {
|
|
assertQuery(predicate + "2009-10-03");
|
|
assertQuery(predicate + "\"2009-10-01 20:59:59 -0400\"", change2);
|
|
assertQuery(predicate + "\"2009-10-01 20:59:59 -0000\"", change2);
|
|
assertQuery(predicate + "2009-10-01", change2);
|
|
assertQuery(predicate + "2009-09-30", change2, change1);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void bySize() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
|
|
// added = 3, deleted = 0, delta = 3
|
|
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "foo\n\foo\nfoo").create());
|
|
// added = 0, deleted = 2, delta = 2
|
|
RevCommit commit2 = repo.parseBody(repo.commit().parent(commit1).add("file1", "foo").create());
|
|
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
assertQuery("added:>4");
|
|
assertQuery("-added:<=4");
|
|
|
|
assertQuery("added:3", change1);
|
|
assertQuery("-(added:<3 OR added>3)", change1);
|
|
|
|
assertQuery("added:>2", change1);
|
|
assertQuery("-added:<=2", change1);
|
|
|
|
assertQuery("added:>=3", change1);
|
|
assertQuery("-added:<3", change1);
|
|
|
|
assertQuery("added:<1", change2);
|
|
assertQuery("-added:>=1", change2);
|
|
|
|
assertQuery("added:<=0", change2);
|
|
assertQuery("-added:>0", change2);
|
|
|
|
assertQuery("deleted:>3");
|
|
assertQuery("-deleted:<=3");
|
|
|
|
assertQuery("deleted:2", change2);
|
|
assertQuery("-(deleted:<2 OR deleted>2)", change2);
|
|
|
|
assertQuery("deleted:>1", change2);
|
|
assertQuery("-deleted:<=1", change2);
|
|
|
|
assertQuery("deleted:>=2", change2);
|
|
assertQuery("-deleted:<2", change2);
|
|
|
|
assertQuery("deleted:<1", change1);
|
|
assertQuery("-deleted:>=1", change1);
|
|
|
|
assertQuery("deleted:<=0", change1);
|
|
|
|
for (String str : Lists.newArrayList("delta:", "size:")) {
|
|
assertQuery(str + "<2");
|
|
assertQuery(str + "3", change1);
|
|
assertQuery(str + ">2", change1);
|
|
assertQuery(str + ">=3", change1);
|
|
assertQuery(str + "<3", change2);
|
|
assertQuery(str + "<=2", change2);
|
|
}
|
|
}
|
|
|
|
private List<Change> setUpHashtagChanges() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
HashtagsInput in = new HashtagsInput();
|
|
in.add = ImmutableSet.of("foo");
|
|
gApi.changes().id(change1.getId().get()).setHashtags(in);
|
|
|
|
in.add = ImmutableSet.of("foo", "bar", "a tag");
|
|
gApi.changes().id(change2.getId().get()).setHashtags(in);
|
|
|
|
return ImmutableList.of(change1, change2);
|
|
}
|
|
|
|
@Test
|
|
public void byHashtagWithNoteDb() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isTrue();
|
|
List<Change> changes = setUpHashtagChanges();
|
|
assertQuery("hashtag:foo", changes.get(1), changes.get(0));
|
|
assertQuery("hashtag:bar", changes.get(1));
|
|
assertQuery("hashtag:\"a tag\"", changes.get(1));
|
|
assertQuery("hashtag:\"a tag \"", changes.get(1));
|
|
assertQuery("hashtag:\" a tag \"", changes.get(1));
|
|
assertQuery("hashtag:\"#a tag\"", changes.get(1));
|
|
assertQuery("hashtag:\"# #a tag\"", changes.get(1));
|
|
}
|
|
|
|
@Test
|
|
public void byHashtagWithoutNoteDb() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isFalse();
|
|
|
|
notesMigration.setWriteChanges(true);
|
|
notesMigration.setReadChanges(true);
|
|
db.close();
|
|
db = schemaFactory.open();
|
|
List<Change> changes;
|
|
try {
|
|
changes = setUpHashtagChanges();
|
|
notesMigration.setWriteChanges(false);
|
|
notesMigration.setReadChanges(false);
|
|
} finally {
|
|
db.close();
|
|
}
|
|
db = schemaFactory.open();
|
|
for (Change c : changes) {
|
|
indexer.index(db, c); // Reindex without hashtag field.
|
|
}
|
|
assertQuery("hashtag:foo");
|
|
assertQuery("hashtag:bar");
|
|
assertQuery("hashtag:\" bar \"");
|
|
assertQuery("hashtag:\"a tag\"");
|
|
assertQuery("hashtag:\" a tag \"");
|
|
assertQuery("hashtag:#foo");
|
|
assertQuery("hashtag:\"# #foo\"");
|
|
}
|
|
|
|
@Test
|
|
public void byDefault() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
|
|
Change change1 = insert(repo, newChange(repo));
|
|
|
|
RevCommit commit2 = repo.parseBody(repo.commit().message("foosubject").create());
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
RevCommit commit3 = repo.parseBody(repo.commit().add("Foo.java", "foo contents").create());
|
|
Change change3 = insert(repo, newChangeForCommit(repo, commit3));
|
|
|
|
ChangeInserter ins4 = newChange(repo);
|
|
Change change4 = insert(repo, ins4);
|
|
ReviewInput ri4 = new ReviewInput();
|
|
ri4.message = "toplevel";
|
|
ri4.labels = ImmutableMap.<String, Short>of("Code-Review", (short) 1);
|
|
gApi.changes().id(change4.getId().get()).current().review(ri4);
|
|
|
|
ChangeInserter ins5 = newChangeWithTopic(repo, "feature5");
|
|
Change change5 = insert(repo, ins5);
|
|
|
|
Change change6 = insert(repo, newChangeForBranch(repo, "branch6"));
|
|
|
|
assertQuery(change1.getId().get(), change1);
|
|
assertQuery(ChangeTriplet.format(change1), change1);
|
|
assertQuery("foosubject", change2);
|
|
assertQuery("Foo.java", change3);
|
|
assertQuery("Code-Review+1", change4);
|
|
assertQuery("toplevel", change4);
|
|
assertQuery("feature5", change5);
|
|
assertQuery("branch6", change6);
|
|
assertQuery("refs/heads/branch6", change6);
|
|
|
|
Change[] expected = new Change[] {change6, change5, change4, change3, change2, change1};
|
|
assertQuery("user@example.com", expected);
|
|
assertQuery("repo", expected);
|
|
}
|
|
|
|
@Test
|
|
public void byDefaultWithCommitPrefix() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit = repo.parseBody(repo.commit().message("message").create());
|
|
Change change = insert(repo, newChangeForCommit(repo, commit));
|
|
|
|
assertQuery(commit.getId().getName().substring(0, 6), change);
|
|
}
|
|
|
|
@Test
|
|
public void visible() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
gApi.changes().id(change2.getChangeId()).setPrivate(true, "private");
|
|
|
|
String q = "project:repo";
|
|
assertQuery(q, change2, change1);
|
|
|
|
// Second user cannot see first user's private change.
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
assertQuery(q + " visibleto:" + user2.get(), change1);
|
|
|
|
requestContext.setContext(
|
|
newRequestContext(
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId()));
|
|
assertQuery("is:visible", change1);
|
|
}
|
|
|
|
@Test
|
|
public void byCommentBy() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
int user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId().get();
|
|
|
|
ReviewInput input = new ReviewInput();
|
|
input.message = "toplevel";
|
|
ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
|
|
comment.line = 1;
|
|
comment.message = "inline";
|
|
input.comments =
|
|
ImmutableMap.<String, List<ReviewInput.CommentInput>>of(
|
|
Patch.COMMIT_MSG, ImmutableList.<ReviewInput.CommentInput>of(comment));
|
|
gApi.changes().id(change1.getId().get()).current().review(input);
|
|
|
|
input = new ReviewInput();
|
|
input.message = "toplevel";
|
|
gApi.changes().id(change2.getId().get()).current().review(input);
|
|
|
|
assertQuery("commentby:" + userId.get(), change2, change1);
|
|
assertQuery("commentby:" + user2);
|
|
}
|
|
|
|
@Test
|
|
public void byDraftBy() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
assertQuery("has:draft");
|
|
|
|
DraftInput in = new DraftInput();
|
|
in.line = 1;
|
|
in.message = "nit: trailing whitespace";
|
|
in.path = Patch.COMMIT_MSG;
|
|
gApi.changes().id(change1.getId().get()).current().createDraft(in);
|
|
|
|
in = new DraftInput();
|
|
in.line = 2;
|
|
in.message = "nit: point in the end of the statement";
|
|
in.path = Patch.COMMIT_MSG;
|
|
gApi.changes().id(change2.getId().get()).current().createDraft(in);
|
|
|
|
int user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId().get();
|
|
|
|
assertQuery("has:draft", change2, change1);
|
|
assertQuery("draftby:" + userId.get(), change2, change1);
|
|
assertQuery("draftby:" + user2);
|
|
}
|
|
|
|
@Test
|
|
public void byDraftByExcludesZombieDrafts() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isTrue();
|
|
|
|
Project.NameKey project = new Project.NameKey("repo");
|
|
TestRepository<Repo> repo = createProject(project.get());
|
|
Change change = insert(repo, newChange(repo));
|
|
Change.Id id = change.getId();
|
|
|
|
DraftInput in = new DraftInput();
|
|
in.line = 1;
|
|
in.message = "nit: trailing whitespace";
|
|
in.path = Patch.COMMIT_MSG;
|
|
gApi.changes().id(id.get()).current().createDraft(in);
|
|
|
|
assertQuery("draftby:" + userId, change);
|
|
assertQuery("commentby:" + userId);
|
|
|
|
TestRepository<Repo> allUsers = new TestRepository<>(repoManager.openRepository(allUsersName));
|
|
|
|
Ref draftsRef = allUsers.getRepository().exactRef(RefNames.refsDraftComments(id, userId));
|
|
assertThat(draftsRef).isNotNull();
|
|
|
|
ReviewInput rin = ReviewInput.dislike();
|
|
rin.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
|
|
gApi.changes().id(id.get()).current().review(rin);
|
|
|
|
assertQuery("draftby:" + userId);
|
|
assertQuery("commentby:" + userId, change);
|
|
assertThat(allUsers.getRepository().exactRef(draftsRef.getName())).isNull();
|
|
|
|
// Re-add drafts ref and ensure it gets filtered out during indexing.
|
|
allUsers.update(draftsRef.getName(), draftsRef.getObjectId());
|
|
assertThat(allUsers.getRepository().exactRef(draftsRef.getName())).isNotNull();
|
|
|
|
if (PrimaryStorage.of(change) == PrimaryStorage.REVIEW_DB
|
|
&& !notesMigration.disableChangeReviewDb()) {
|
|
// Record draft ref in noteDbState as well.
|
|
ReviewDb db = ReviewDbUtil.unwrapDb(this.db);
|
|
change = db.changes().get(id);
|
|
NoteDbChangeState.applyDelta(
|
|
change,
|
|
NoteDbChangeState.Delta.create(
|
|
id, Optional.empty(), ImmutableMap.of(userId, draftsRef.getObjectId())));
|
|
db.changes().update(Collections.singleton(change));
|
|
}
|
|
|
|
indexer.index(db, project, id);
|
|
assertQuery("draftby:" + userId);
|
|
}
|
|
|
|
@Test
|
|
public void byStarredBy() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
insert(repo, newChange(repo));
|
|
|
|
gApi.accounts().self().starChange(change1.getId().toString());
|
|
gApi.accounts().self().starChange(change2.getId().toString());
|
|
|
|
int user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId().get();
|
|
|
|
assertQuery("starredby:self", change2, change1);
|
|
assertQuery("starredby:" + user2);
|
|
}
|
|
|
|
@Test
|
|
public void byStar() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
|
|
Change change3 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
|
|
Change change4 = insert(repo, newChange(repo));
|
|
|
|
gApi.accounts()
|
|
.self()
|
|
.setStars(
|
|
change1.getId().toString(),
|
|
new StarsInput(new HashSet<>(Arrays.asList("red", "blue"))));
|
|
gApi.accounts()
|
|
.self()
|
|
.setStars(
|
|
change2.getId().toString(),
|
|
new StarsInput(
|
|
new HashSet<>(Arrays.asList(StarredChangesUtil.DEFAULT_LABEL, "green", "blue"))));
|
|
|
|
gApi.accounts()
|
|
.self()
|
|
.setStars(
|
|
change4.getId().toString(), new StarsInput(new HashSet<>(Arrays.asList("ignore"))));
|
|
|
|
// check labeled stars
|
|
assertQuery("star:red", change1);
|
|
assertQuery("star:blue", change2, change1);
|
|
assertQuery("has:stars", change4, change2, change1);
|
|
|
|
// check default star
|
|
assertQuery("has:star", change2);
|
|
assertQuery("is:starred", change2);
|
|
assertQuery("starredby:self", change2);
|
|
assertQuery("star:" + StarredChangesUtil.DEFAULT_LABEL, change2);
|
|
|
|
// check ignored
|
|
assertQuery("is:ignored", change4);
|
|
assertQuery("-is:ignored", change3, change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byIgnore() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
Change change1 = insert(repo, newChange(repo), user2);
|
|
Change change2 = insert(repo, newChange(repo), user2);
|
|
|
|
gApi.changes().id(change1.getId().toString()).ignore(true);
|
|
assertQuery("is:ignored", change1);
|
|
assertQuery("-is:ignored", change2);
|
|
|
|
gApi.changes().id(change1.getId().toString()).ignore(false);
|
|
assertQuery("is:ignored");
|
|
assertQuery("-is:ignored", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void byFrom() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
Change change2 = insert(repo, newChange(repo), user2);
|
|
|
|
ReviewInput input = new ReviewInput();
|
|
input.message = "toplevel";
|
|
ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
|
|
comment.line = 1;
|
|
comment.message = "inline";
|
|
input.comments =
|
|
ImmutableMap.<String, List<ReviewInput.CommentInput>>of(
|
|
Patch.COMMIT_MSG, ImmutableList.<ReviewInput.CommentInput>of(comment));
|
|
gApi.changes().id(change2.getId().get()).current().review(input);
|
|
|
|
assertQuery("from:" + userId.get(), change2, change1);
|
|
assertQuery("from:" + user2, change2);
|
|
}
|
|
|
|
@Test
|
|
public void conflicts() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 =
|
|
repo.parseBody(
|
|
repo.commit()
|
|
.add("file1", "contents1")
|
|
.add("dir/file2", "contents2")
|
|
.add("dir/file3", "contents3")
|
|
.create());
|
|
RevCommit commit2 = repo.parseBody(repo.commit().add("file1", "contents1").create());
|
|
RevCommit commit3 =
|
|
repo.parseBody(repo.commit().add("dir/file2", "contents2 different").create());
|
|
RevCommit commit4 = repo.parseBody(repo.commit().add("file4", "contents4").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
Change change3 = insert(repo, newChangeForCommit(repo, commit3));
|
|
Change change4 = insert(repo, newChangeForCommit(repo, commit4));
|
|
|
|
assertQuery("conflicts:" + change1.getId().get(), change3);
|
|
assertQuery("conflicts:" + change2.getId().get());
|
|
assertQuery("conflicts:" + change3.getId().get(), change1);
|
|
assertQuery("conflicts:" + change4.getId().get());
|
|
}
|
|
|
|
@Test
|
|
public void mergeable() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
|
|
RevCommit commit2 = repo.parseBody(repo.commit().add("file1", "contents2").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
assertQuery("conflicts:" + change1.getId().get(), change2);
|
|
assertQuery("conflicts:" + change2.getId().get(), change1);
|
|
assertQuery("is:mergeable", change2, change1);
|
|
|
|
gApi.changes().id(change1.getChangeId()).revision("current").review(ReviewInput.approve());
|
|
gApi.changes().id(change1.getChangeId()).revision("current").submit();
|
|
|
|
assertQuery("status:open conflicts:" + change2.getId().get());
|
|
assertQuery("status:open is:mergeable");
|
|
assertQuery("status:open -is:mergeable", change2);
|
|
}
|
|
|
|
@Test
|
|
public void reviewedBy() throws Exception {
|
|
resetTimeWithClockStep(2, MINUTES);
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
Change change3 = insert(repo, newChange(repo));
|
|
|
|
gApi.changes().id(change1.getId().get()).current().review(new ReviewInput().message("comment"));
|
|
|
|
Account.Id user2 =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
requestContext.setContext(newRequestContext(user2));
|
|
|
|
gApi.changes().id(change2.getId().get()).current().review(new ReviewInput().message("comment"));
|
|
|
|
PatchSet.Id ps3_1 = change3.currentPatchSetId();
|
|
change3 = newPatchSet(repo, change3);
|
|
assertThat(change3.currentPatchSetId()).isNotEqualTo(ps3_1);
|
|
// Response to previous patch set still counts as reviewing.
|
|
gApi.changes()
|
|
.id(change3.getId().get())
|
|
.revision(ps3_1.get())
|
|
.review(new ReviewInput().message("comment"));
|
|
|
|
List<ChangeInfo> actual;
|
|
actual = assertQuery(newQuery("is:reviewed").withOption(REVIEWED), change3, change2);
|
|
assertThat(actual.get(0).reviewed).isTrue();
|
|
assertThat(actual.get(1).reviewed).isTrue();
|
|
|
|
actual = assertQuery(newQuery("-is:reviewed").withOption(REVIEWED), change1);
|
|
assertThat(actual.get(0).reviewed).isNull();
|
|
|
|
actual = assertQuery("reviewedby:" + userId.get());
|
|
|
|
actual =
|
|
assertQuery(newQuery("reviewedby:" + user2.get()).withOption(REVIEWED), change3, change2);
|
|
assertThat(actual.get(0).reviewed).isTrue();
|
|
assertThat(actual.get(1).reviewed).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void reviewerAndCc() throws Exception {
|
|
Account.Id user1 = createAccount("user1");
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
Change change3 = insert(repo, newChange(repo));
|
|
insert(repo, newChange(repo));
|
|
|
|
AddReviewerInput rin = new AddReviewerInput();
|
|
rin.reviewer = user1.toString();
|
|
rin.state = ReviewerState.REVIEWER;
|
|
gApi.changes().id(change1.getId().get()).addReviewer(rin);
|
|
|
|
rin = new AddReviewerInput();
|
|
rin.reviewer = user1.toString();
|
|
rin.state = ReviewerState.CC;
|
|
gApi.changes().id(change2.getId().get()).addReviewer(rin);
|
|
|
|
assertQuery("is:reviewer");
|
|
assertQuery("reviewer:self");
|
|
gApi.changes().id(change3.getChangeId()).revision("current").review(ReviewInput.recommend());
|
|
assertQuery("is:reviewer", change3);
|
|
assertQuery("reviewer:self", change3);
|
|
|
|
requestContext.setContext(newRequestContext(user1));
|
|
if (notesMigration.readChanges()) {
|
|
assertQuery("reviewer:" + user1, change1);
|
|
assertQuery("cc:" + user1, change2);
|
|
assertQuery("is:cc", change2);
|
|
assertQuery("cc:self", change2);
|
|
} else {
|
|
assertQuery("reviewer:" + user1, change2, change1);
|
|
assertQuery("cc:" + user1);
|
|
assertQuery("is:cc");
|
|
assertQuery("cc:self");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void byReviewed() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Account.Id otherUser =
|
|
accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
assertQuery("is:reviewed");
|
|
assertQuery("status:reviewed");
|
|
assertQuery("-is:reviewed", change2, change1);
|
|
assertQuery("-status:reviewed", change2, change1);
|
|
|
|
requestContext.setContext(newRequestContext(otherUser));
|
|
gApi.changes().id(change1.getChangeId()).current().review(ReviewInput.recommend());
|
|
|
|
assertQuery("is:reviewed", change1);
|
|
assertQuery("status:reviewed", change1);
|
|
assertQuery("-is:reviewed", change2);
|
|
assertQuery("-status:reviewed", change2);
|
|
}
|
|
|
|
@Test
|
|
public void reviewerin() throws Exception {
|
|
Account.Id user1 = accountManager.authenticate(AuthRequest.forUser("user1")).getAccountId();
|
|
Account.Id user2 = accountManager.authenticate(AuthRequest.forUser("user2")).getAccountId();
|
|
Account.Id user3 = accountManager.authenticate(AuthRequest.forUser("user3")).getAccountId();
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
Change change3 = insert(repo, newChange(repo));
|
|
|
|
AddReviewerInput rin = new AddReviewerInput();
|
|
rin.reviewer = user1.toString();
|
|
rin.state = ReviewerState.REVIEWER;
|
|
gApi.changes().id(change1.getId().get()).addReviewer(rin);
|
|
|
|
rin = new AddReviewerInput();
|
|
rin.reviewer = user2.toString();
|
|
rin.state = ReviewerState.REVIEWER;
|
|
gApi.changes().id(change2.getId().get()).addReviewer(rin);
|
|
|
|
rin = new AddReviewerInput();
|
|
rin.reviewer = user3.toString();
|
|
rin.state = ReviewerState.CC;
|
|
gApi.changes().id(change3.getId().get()).addReviewer(rin);
|
|
|
|
String group = gApi.groups().create("foo").get().name;
|
|
gApi.groups().id(group).addMembers(user2.toString(), user3.toString());
|
|
|
|
List<String> members =
|
|
gApi.groups()
|
|
.id(group)
|
|
.members()
|
|
.stream()
|
|
.map(a -> a._accountId.toString())
|
|
.collect(toList());
|
|
assertThat(members).contains(user2.toString());
|
|
|
|
if (notesMigration.readChanges()) {
|
|
// CC and REVIEWER are separate in NoteDB
|
|
assertQuery("reviewerin:\"Registered Users\"", change2, change1);
|
|
assertQuery("reviewerin:" + group, change2);
|
|
} else {
|
|
// CC and REVIEWER are the same in ReviewDb
|
|
assertQuery("reviewerin:\"Registered Users\"", change3, change2, change1);
|
|
assertQuery("reviewerin:" + group, change3, change2);
|
|
}
|
|
|
|
gApi.changes().id(change2.getId().get()).current().review(ReviewInput.approve());
|
|
gApi.changes().id(change2.getId().get()).current().submit();
|
|
|
|
if (notesMigration.readChanges()) {
|
|
// CC and REVIEWER are separate in NoteDB
|
|
assertQuery("reviewerin:" + group, change2);
|
|
assertQuery("project:repo reviewerin:" + group, change2);
|
|
assertQuery("status:merged reviewerin:" + group, change2);
|
|
} else {
|
|
// CC and REVIEWER are the same in ReviewDb
|
|
assertQuery("reviewerin:" + group, change2, change3);
|
|
assertQuery("project:repo reviewerin:" + group, change2, change3);
|
|
assertQuery("status:merged reviewerin:" + group, change2);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void reviewerAndCcByEmail() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isTrue();
|
|
|
|
Project.NameKey project = new Project.NameKey("repo");
|
|
TestRepository<Repo> repo = createProject(project.get());
|
|
ConfigInput conf = new ConfigInput();
|
|
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
|
|
gApi.projects().name(project.get()).config(conf);
|
|
|
|
String userByEmail = "un.registered@reviewer.com";
|
|
String userByEmailWithName = "John Doe <" + userByEmail + ">";
|
|
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
insert(repo, newChange(repo));
|
|
|
|
AddReviewerInput rin = new AddReviewerInput();
|
|
rin.reviewer = userByEmailWithName;
|
|
rin.state = ReviewerState.REVIEWER;
|
|
gApi.changes().id(change1.getId().get()).addReviewer(rin);
|
|
|
|
rin = new AddReviewerInput();
|
|
rin.reviewer = userByEmailWithName;
|
|
rin.state = ReviewerState.CC;
|
|
gApi.changes().id(change2.getId().get()).addReviewer(rin);
|
|
|
|
if (getSchemaVersion() >= 41) {
|
|
assertQuery("reviewer:\"" + userByEmailWithName + "\"", change1);
|
|
assertQuery("cc:\"" + userByEmailWithName + "\"", change2);
|
|
|
|
// Omitting the name:
|
|
assertQuery("reviewer:\"" + userByEmail + "\"", change1);
|
|
assertQuery("cc:\"" + userByEmail + "\"", change2);
|
|
} else {
|
|
assertMissingField(ChangeField.REVIEWER_BY_EMAIL);
|
|
|
|
assertFailingQuery(
|
|
"reviewer:\"" + userByEmailWithName + "\"", "User " + userByEmailWithName + " not found");
|
|
assertFailingQuery(
|
|
"cc:\"" + userByEmailWithName + "\"", "User " + userByEmailWithName + " not found");
|
|
|
|
// Omitting the name:
|
|
assertFailingQuery("reviewer:\"" + userByEmail + "\"", "User " + userByEmail + " not found");
|
|
assertFailingQuery("cc:\"" + userByEmail + "\"", "User " + userByEmail + " not found");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void reviewerAndCcByEmailWithQueryForDifferentUser() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isTrue();
|
|
|
|
Project.NameKey project = new Project.NameKey("repo");
|
|
TestRepository<Repo> repo = createProject(project.get());
|
|
ConfigInput conf = new ConfigInput();
|
|
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
|
|
gApi.projects().name(project.get()).config(conf);
|
|
|
|
String userByEmail = "John Doe <un.registered@reviewer.com>";
|
|
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
insert(repo, newChange(repo));
|
|
|
|
AddReviewerInput rin = new AddReviewerInput();
|
|
rin.reviewer = userByEmail;
|
|
rin.state = ReviewerState.REVIEWER;
|
|
gApi.changes().id(change1.getId().get()).addReviewer(rin);
|
|
|
|
rin = new AddReviewerInput();
|
|
rin.reviewer = userByEmail;
|
|
rin.state = ReviewerState.CC;
|
|
gApi.changes().id(change2.getId().get()).addReviewer(rin);
|
|
|
|
if (getSchemaVersion() >= 41) {
|
|
assertQuery("reviewer:\"someone@example.com\"");
|
|
assertQuery("cc:\"someone@example.com\"");
|
|
} else {
|
|
assertMissingField(ChangeField.REVIEWER_BY_EMAIL);
|
|
|
|
String someoneEmail = "someone@example.com";
|
|
assertFailingQuery(
|
|
"reviewer:\"" + someoneEmail + "\"", "User " + someoneEmail + " not found");
|
|
assertFailingQuery("cc:\"" + someoneEmail + "\"", "User " + someoneEmail + " not found");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void submitRecords() throws Exception {
|
|
Account.Id user1 = createAccount("user1");
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
gApi.changes().id(change1.getId().get()).current().review(ReviewInput.approve());
|
|
requestContext.setContext(newRequestContext(user1));
|
|
gApi.changes().id(change2.getId().get()).current().review(ReviewInput.recommend());
|
|
requestContext.setContext(newRequestContext(user.getAccountId()));
|
|
|
|
assertQuery("is:submittable", change1);
|
|
assertQuery("-is:submittable", change2);
|
|
assertQuery("submittable:ok", change1);
|
|
assertQuery("submittable:not_ready", change2);
|
|
|
|
assertQuery("label:CodE-RevieW=ok", change1);
|
|
assertQuery("label:CodE-RevieW=ok,user=user", change1);
|
|
assertQuery("label:CodE-RevieW=ok,Administrators", change1);
|
|
assertQuery("label:CodE-RevieW=ok,group=Administrators", change1);
|
|
assertQuery("label:CodE-RevieW=ok,owner", change1);
|
|
assertQuery("label:CodE-RevieW=ok,user1");
|
|
assertQuery("label:CodE-RevieW=need", change2);
|
|
// NEED records don't have associated users.
|
|
assertQuery("label:CodE-RevieW=need,user1");
|
|
assertQuery("label:CodE-RevieW=need,user");
|
|
|
|
gApi.changes().id(change1.getId().get()).current().submit();
|
|
assertQuery("submittable:ok");
|
|
assertQuery("submittable:closed", change1);
|
|
}
|
|
|
|
@Test
|
|
public void hasEdit() throws Exception {
|
|
Account.Id user1 = createAccount("user1");
|
|
Account.Id user2 = createAccount("user2");
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
String changeId1 = change1.getKey().get();
|
|
Change change2 = insert(repo, newChange(repo));
|
|
String changeId2 = change2.getKey().get();
|
|
|
|
requestContext.setContext(newRequestContext(user1));
|
|
assertQuery("has:edit");
|
|
gApi.changes().id(changeId1).edit().create();
|
|
gApi.changes().id(changeId2).edit().create();
|
|
|
|
requestContext.setContext(newRequestContext(user2));
|
|
assertQuery("has:edit");
|
|
gApi.changes().id(changeId2).edit().create();
|
|
|
|
requestContext.setContext(newRequestContext(user1));
|
|
assertQuery("has:edit", change2, change1);
|
|
|
|
requestContext.setContext(newRequestContext(user2));
|
|
assertQuery("has:edit", change2);
|
|
}
|
|
|
|
@Test
|
|
public void byUnresolved() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
Change change3 = insert(repo, newChange(repo));
|
|
|
|
// Change1 has one resolved comment (unresolvedcount = 0)
|
|
// Change2 has one unresolved comment (unresolvedcount = 1)
|
|
// Change3 has one resolved comment and one unresolved comment (unresolvedcount = 1)
|
|
addComment(change1.getChangeId(), "comment 1", false);
|
|
addComment(change2.getChangeId(), "comment 2", true);
|
|
addComment(change3.getChangeId(), "comment 3", false);
|
|
addComment(change3.getChangeId(), "comment 4", true);
|
|
|
|
assertQuery("has:unresolved", change3, change2);
|
|
|
|
assertQuery("unresolved:0", change1);
|
|
List<ChangeInfo> changeInfos = assertQuery("unresolved:>=0", change3, change2, change1);
|
|
assertThat(changeInfos.get(0).unresolvedCommentCount).isEqualTo(1); // Change3
|
|
assertThat(changeInfos.get(1).unresolvedCommentCount).isEqualTo(1); // Change2
|
|
assertThat(changeInfos.get(2).unresolvedCommentCount).isEqualTo(0); // Change1
|
|
assertQuery("unresolved:>0", change3, change2);
|
|
|
|
assertQuery("unresolved:<1", change1);
|
|
assertQuery("unresolved:<=1", change3, change2, change1);
|
|
assertQuery("unresolved:1", change3, change2);
|
|
assertQuery("unresolved:>1");
|
|
assertQuery("unresolved:>=1", change3, change2);
|
|
}
|
|
|
|
@Test
|
|
public void byCommitsOnBranchNotMerged() throws Exception {
|
|
TestRepository<Repo> tr = createProject("repo");
|
|
testByCommitsOnBranchNotMerged(tr, ImmutableSet.of());
|
|
}
|
|
|
|
@Test
|
|
public void byCommitsOnBranchNotMergedSkipsMissingChanges() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ObjectId missing =
|
|
repo.branch(new PatchSet.Id(new Change.Id(987654), 1).toRefName())
|
|
.commit()
|
|
.message("No change for this commit")
|
|
.insertChangeId()
|
|
.create()
|
|
.copy();
|
|
testByCommitsOnBranchNotMerged(repo, ImmutableSet.of(missing));
|
|
}
|
|
|
|
private void testByCommitsOnBranchNotMerged(TestRepository<Repo> repo, Collection<ObjectId> extra)
|
|
throws Exception {
|
|
int n = 10;
|
|
List<String> shas = new ArrayList<>(n + extra.size());
|
|
extra.forEach(i -> shas.add(i.name()));
|
|
List<Integer> expectedIds = new ArrayList<>(n);
|
|
Branch.NameKey dest = null;
|
|
for (int i = 0; i < n; i++) {
|
|
ChangeInserter ins = newChange(repo);
|
|
insert(repo, ins);
|
|
if (dest == null) {
|
|
dest = ins.getChange().getDest();
|
|
}
|
|
shas.add(ins.getCommitId().name());
|
|
expectedIds.add(ins.getChange().getId().get());
|
|
}
|
|
|
|
for (int i = 1; i <= 11; i++) {
|
|
Iterable<ChangeData> cds =
|
|
queryProvider.get().byCommitsOnBranchNotMerged(repo.getRepository(), db, dest, shas, i);
|
|
Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
|
|
String name = "limit " + i;
|
|
assertThat(ids).named(name).hasSize(n);
|
|
assertThat(ids).named(name).containsExactlyElementsIn(expectedIds);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void prepopulatedFields() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isFalse();
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change = insert(repo, newChange(repo));
|
|
|
|
db = new DisabledReviewDb();
|
|
requestContext.setContext(newRequestContext(userId));
|
|
// Use QueryProcessor directly instead of API so we get ChangeDatas back.
|
|
List<ChangeData> cds =
|
|
queryProcessorProvider
|
|
.get()
|
|
.query(queryBuilder.parse(change.getId().toString()))
|
|
.entities();
|
|
assertThat(cds).hasSize(1);
|
|
|
|
ChangeData cd = cds.get(0);
|
|
cd.change();
|
|
cd.patchSets();
|
|
cd.currentApprovals();
|
|
cd.changedLines();
|
|
cd.reviewedBy();
|
|
cd.reviewers();
|
|
cd.unresolvedCommentCount();
|
|
|
|
// TODO(dborowitz): Swap out GitRepositoryManager somehow? Will probably be
|
|
// necessary for NoteDb anyway.
|
|
cd.isMergeable();
|
|
|
|
exception.expect(DisabledReviewDb.Disabled.class);
|
|
cd.messages();
|
|
}
|
|
|
|
@Test
|
|
public void prepopulateOnlyRequestedFields() throws Exception {
|
|
assume().that(notesMigration.readChanges()).isFalse();
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change = insert(repo, newChange(repo));
|
|
|
|
db = new DisabledReviewDb();
|
|
requestContext.setContext(newRequestContext(userId));
|
|
// Use QueryProcessor directly instead of API so we get ChangeDatas back.
|
|
List<ChangeData> cds =
|
|
queryProcessorProvider
|
|
.get()
|
|
.setRequestedFields(
|
|
ImmutableSet.of(ChangeField.PATCH_SET.getName(), ChangeField.CHANGE.getName()))
|
|
.query(queryBuilder.parse(change.getId().toString()))
|
|
.entities();
|
|
assertThat(cds).hasSize(1);
|
|
|
|
ChangeData cd = cds.get(0);
|
|
cd.change();
|
|
cd.patchSets();
|
|
|
|
exception.expect(DisabledReviewDb.Disabled.class);
|
|
cd.currentApprovals();
|
|
}
|
|
|
|
@Test
|
|
public void reindexIfStale() throws Exception {
|
|
Account.Id user = createAccount("user");
|
|
Project.NameKey project = new Project.NameKey("repo");
|
|
TestRepository<Repo> repo = createProject(project.get());
|
|
Change change = insert(repo, newChange(repo));
|
|
String changeId = change.getKey().get();
|
|
ChangeNotes notes = notesFactory.create(db, change.getProject(), change.getId());
|
|
PatchSet ps = psUtil.get(db, notes, change.currentPatchSetId());
|
|
|
|
requestContext.setContext(newRequestContext(user));
|
|
gApi.changes().id(changeId).edit().create();
|
|
assertQuery("has:edit", change);
|
|
assertThat(indexer.reindexIfStale(project, change.getId()).get()).isFalse();
|
|
|
|
// Delete edit ref behind index's back.
|
|
RefUpdate ru =
|
|
repo.getRepository().updateRef(RefNames.refsEdit(user, change.getId(), ps.getId()));
|
|
ru.setForceUpdate(true);
|
|
assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
|
|
|
|
// Index is stale.
|
|
assertQuery("has:edit", change);
|
|
assertThat(indexer.reindexIfStale(project, change.getId()).get()).isTrue();
|
|
assertQuery("has:edit");
|
|
}
|
|
|
|
@Test
|
|
public void refStateFields() throws Exception {
|
|
// This test method manages primary storage manually.
|
|
assume().that(notesMigration.changePrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
|
|
Account.Id user = createAccount("user");
|
|
Project.NameKey project = new Project.NameKey("repo");
|
|
TestRepository<Repo> repo = createProject(project.get());
|
|
String path = "file";
|
|
RevCommit commit = repo.parseBody(repo.commit().message("one").add(path, "contents").create());
|
|
Change change = insert(repo, newChangeForCommit(repo, commit));
|
|
Change.Id id = change.getId();
|
|
int c = id.get();
|
|
String changeId = change.getKey().get();
|
|
requestContext.setContext(newRequestContext(user));
|
|
|
|
// Ensure one of each type of supported ref is present for the change. If
|
|
// any more refs are added, update this test to reflect them.
|
|
|
|
// Edit
|
|
gApi.changes().id(changeId).edit().create();
|
|
|
|
// Star
|
|
gApi.accounts().self().starChange(change.getId().toString());
|
|
|
|
if (notesMigration.readChanges()) {
|
|
// Robot comment.
|
|
ReviewInput rin = new ReviewInput();
|
|
RobotCommentInput rcin = new RobotCommentInput();
|
|
rcin.robotId = "happyRobot";
|
|
rcin.robotRunId = "1";
|
|
rcin.line = 1;
|
|
rcin.message = "nit: trailing whitespace";
|
|
rcin.path = path;
|
|
rin.robotComments = ImmutableMap.of(path, ImmutableList.of(rcin));
|
|
gApi.changes().id(c).current().review(rin);
|
|
}
|
|
|
|
// Draft.
|
|
DraftInput din = new DraftInput();
|
|
din.path = path;
|
|
din.line = 1;
|
|
din.message = "draft";
|
|
gApi.changes().id(c).current().createDraft(din);
|
|
|
|
if (notesMigration.readChanges()) {
|
|
// Force NoteDb primary.
|
|
change = ReviewDbUtil.unwrapDb(db).changes().get(id);
|
|
change.setNoteDbState(NoteDbChangeState.NOTE_DB_PRIMARY_STATE);
|
|
ReviewDbUtil.unwrapDb(db).changes().update(Collections.singleton(change));
|
|
indexer.index(db, change);
|
|
}
|
|
|
|
QueryOptions opts =
|
|
IndexedChangeQuery.createOptions(indexConfig, 0, 1, StalenessChecker.FIELDS);
|
|
ChangeData cd = indexes.getSearchIndex().get(id, opts).get();
|
|
|
|
String cs = RefNames.shard(c);
|
|
int u = user.get();
|
|
String us = RefNames.shard(u);
|
|
|
|
List<String> expectedStates =
|
|
Lists.newArrayList(
|
|
"repo:refs/users/" + us + "/edit-" + c + "/1",
|
|
"All-Users:refs/starred-changes/" + cs + "/" + u);
|
|
if (notesMigration.readChanges()) {
|
|
expectedStates.add("repo:refs/changes/" + cs + "/meta");
|
|
expectedStates.add("repo:refs/changes/" + cs + "/robot-comments");
|
|
expectedStates.add("All-Users:refs/draft-comments/" + cs + "/" + u);
|
|
}
|
|
assertThat(
|
|
cd.getRefStates()
|
|
.stream()
|
|
.map(String::new)
|
|
// Omit SHA-1, we're just concerned with the project/ref names.
|
|
.map(s -> s.substring(0, s.lastIndexOf(':')))
|
|
.collect(toList()))
|
|
.containsExactlyElementsIn(expectedStates);
|
|
|
|
List<String> expectedPatterns = Lists.newArrayList("repo:refs/users/*/edit-" + c + "/*");
|
|
expectedPatterns.add("All-Users:refs/starred-changes/" + cs + "/*");
|
|
if (notesMigration.readChanges()) {
|
|
expectedPatterns.add("All-Users:refs/draft-comments/" + cs + "/*");
|
|
}
|
|
assertThat(cd.getRefStatePatterns().stream().map(String::new).collect(toList()))
|
|
.containsExactlyElementsIn(expectedPatterns);
|
|
}
|
|
|
|
@Test
|
|
public void watched() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
|
|
Change change1 = insert(repo, ins1);
|
|
|
|
TestRepository<Repo> repo2 = createProject("repo2");
|
|
|
|
ChangeInserter ins2 = newChangeWithStatus(repo2, Change.Status.NEW);
|
|
insert(repo2, ins2);
|
|
|
|
assertQuery("is:watched");
|
|
assertQuery("watchedby:self");
|
|
|
|
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
|
|
ProjectWatchInfo pwi = new ProjectWatchInfo();
|
|
pwi.project = "repo";
|
|
pwi.filter = null;
|
|
pwi.notifyAbandonedChanges = true;
|
|
pwi.notifyNewChanges = true;
|
|
pwi.notifyAllComments = true;
|
|
projectsToWatch.add(pwi);
|
|
gApi.accounts().self().setWatchedProjects(projectsToWatch);
|
|
resetUser();
|
|
|
|
assertQuery("is:watched", change1);
|
|
assertQuery("watchedby:self", change1);
|
|
}
|
|
|
|
@Test
|
|
public void trackingid() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
RevCommit commit1 =
|
|
repo.parseBody(repo.commit().message("Change one\n\nBug:QUERY123").create());
|
|
Change change1 = insert(repo, newChangeForCommit(repo, commit1));
|
|
RevCommit commit2 =
|
|
repo.parseBody(repo.commit().message("Change two\n\nFeature:QUERY456").create());
|
|
Change change2 = insert(repo, newChangeForCommit(repo, commit2));
|
|
|
|
assertQuery("tr:QUERY123", change1);
|
|
assertQuery("bug:QUERY123", change1);
|
|
assertQuery("tr:QUERY456", change2);
|
|
assertQuery("bug:QUERY456", change2);
|
|
assertQuery("tr:QUERY-123");
|
|
assertQuery("bug:QUERY-123");
|
|
assertQuery("tr:QUERY12");
|
|
assertQuery("bug:QUERY12");
|
|
assertQuery("tr:QUERY789");
|
|
assertQuery("bug:QUERY789");
|
|
}
|
|
|
|
@Test
|
|
public void selfAndMe() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo), userId);
|
|
insert(repo, newChange(repo));
|
|
gApi.accounts().self().starChange(change1.getId().toString());
|
|
gApi.accounts().self().starChange(change2.getId().toString());
|
|
|
|
assertQuery("starredby:self", change2, change1);
|
|
assertQuery("starredby:me", change2, change1);
|
|
}
|
|
|
|
@Test
|
|
public void defaultFieldWithManyUsers() throws Exception {
|
|
for (int i = 0; i < ChangeQueryBuilder.MAX_ACCOUNTS_PER_DEFAULT_FIELD * 2; i++) {
|
|
createAccount("user" + i, "User " + i, "user" + i + "@example.com", true);
|
|
}
|
|
assertQuery("us");
|
|
}
|
|
|
|
@Test
|
|
public void revertOf() throws Exception {
|
|
if (getSchemaVersion() < 45) {
|
|
assertMissingField(ChangeField.REVERT_OF);
|
|
assertFailingQuery(
|
|
"revertof:1", "'revertof' operator is not supported by change index version");
|
|
return;
|
|
}
|
|
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
// Create two commits and revert second commit (initial commit can't be reverted)
|
|
Change initial = insert(repo, newChange(repo));
|
|
gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
|
|
gApi.changes().id(initial.getChangeId()).current().submit();
|
|
|
|
ChangeInfo changeToRevert =
|
|
gApi.changes().create(new ChangeInput("repo", "master", "commit to revert")).get();
|
|
gApi.changes().id(changeToRevert.id).current().review(ReviewInput.approve());
|
|
gApi.changes().id(changeToRevert.id).current().submit();
|
|
|
|
ChangeInfo changeThatReverts = gApi.changes().id(changeToRevert.id).revert().get();
|
|
assertQueryByIds(
|
|
"revertof:" + changeToRevert._number, new Change.Id(changeThatReverts._number));
|
|
}
|
|
|
|
/** Change builder for helping in tests for dashboard sections. */
|
|
protected class DashboardChangeState {
|
|
private final Account.Id ownerId;
|
|
private final List<Account.Id> reviewedBy;
|
|
private final List<Account.Id> ignoredBy;
|
|
private boolean wip;
|
|
private boolean abandoned;
|
|
@Nullable private Account.Id mergedBy;
|
|
@Nullable private Account.Id assigneeId;
|
|
|
|
@Nullable Change.Id id;
|
|
|
|
DashboardChangeState(Account.Id ownerId) {
|
|
this.ownerId = ownerId;
|
|
reviewedBy = new ArrayList<>();
|
|
ignoredBy = new ArrayList<>();
|
|
}
|
|
|
|
DashboardChangeState assignTo(Account.Id assigneeId) {
|
|
this.assigneeId = assigneeId;
|
|
return this;
|
|
}
|
|
|
|
DashboardChangeState wip() {
|
|
wip = true;
|
|
return this;
|
|
}
|
|
|
|
DashboardChangeState abandon() {
|
|
abandoned = true;
|
|
return this;
|
|
}
|
|
|
|
DashboardChangeState mergeBy(Account.Id mergedBy) {
|
|
this.mergedBy = mergedBy;
|
|
return this;
|
|
}
|
|
|
|
DashboardChangeState ignoreBy(Account.Id ignorerId) {
|
|
ignoredBy.add(ignorerId);
|
|
return this;
|
|
}
|
|
|
|
DashboardChangeState addReviewer(Account.Id reviewerId) {
|
|
reviewedBy.add(reviewerId);
|
|
return this;
|
|
}
|
|
|
|
DashboardChangeState create(TestRepository<Repo> repo) throws Exception {
|
|
requestContext.setContext(newRequestContext(ownerId));
|
|
Change change = insert(repo, newChange(repo), ownerId);
|
|
id = change.getId();
|
|
ChangeApi cApi = gApi.changes().id(change.getChangeId());
|
|
if (assigneeId != null) {
|
|
AssigneeInput in = new AssigneeInput();
|
|
in.assignee = "" + assigneeId;
|
|
cApi.setAssignee(in);
|
|
}
|
|
if (wip) {
|
|
cApi.setWorkInProgress();
|
|
}
|
|
if (abandoned) {
|
|
cApi.abandon();
|
|
}
|
|
for (Account.Id reviewerId : reviewedBy) {
|
|
cApi.addReviewer("" + reviewerId);
|
|
}
|
|
for (Account.Id ignorerId : ignoredBy) {
|
|
requestContext.setContext(newRequestContext(ignorerId));
|
|
StarsInput in = new StarsInput(new HashSet<>(Arrays.asList("ignore")));
|
|
gApi.accounts().self().setStars("" + id, in);
|
|
}
|
|
if (mergedBy != null) {
|
|
requestContext.setContext(newRequestContext(mergedBy));
|
|
cApi = gApi.changes().id(change.getChangeId());
|
|
cApi.current().review(ReviewInput.approve());
|
|
cApi.current().submit();
|
|
}
|
|
requestContext.setContext(newRequestContext(user.getAccountId()));
|
|
return this;
|
|
}
|
|
}
|
|
|
|
protected List<ChangeInfo> assertDashboardQuery(
|
|
String viewedUser, String query, DashboardChangeState... expected) throws Exception {
|
|
Change.Id[] ids = new Change.Id[expected.length];
|
|
for (int i = 0; i < expected.length; i++) {
|
|
ids[i] = expected[i].id;
|
|
}
|
|
return assertQueryByIds(query.replaceAll("\\$\\{user}", viewedUser), ids);
|
|
}
|
|
|
|
@Test
|
|
public void dashboardWorkInProgressReviews() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
DashboardChangeState ownedOpenWip =
|
|
new DashboardChangeState(user.getAccountId()).wip().create(repo);
|
|
|
|
// Create changes that should not be returned by query.
|
|
new DashboardChangeState(user.getAccountId()).wip().abandon().create(repo);
|
|
new DashboardChangeState(user.getAccountId()).mergeBy(user.getAccountId()).create(repo);
|
|
new DashboardChangeState(createAccount("other")).wip().create(repo);
|
|
|
|
assertDashboardQuery("self", DASHBOARD_WORK_IN_PROGRESS_QUERY, ownedOpenWip);
|
|
}
|
|
|
|
@Test
|
|
public void dashboardOutgoingReviews() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Account.Id otherAccountId = createAccount("other");
|
|
DashboardChangeState ownedOpenReviewable =
|
|
new DashboardChangeState(user.getAccountId()).create(repo);
|
|
DashboardChangeState ownedOpenReviewableIgnoredByOther =
|
|
new DashboardChangeState(user.getAccountId()).ignoreBy(otherAccountId).create(repo);
|
|
|
|
// Create changes that should not be returned by any queries in this test.
|
|
new DashboardChangeState(user.getAccountId()).wip().create(repo);
|
|
new DashboardChangeState(otherAccountId).create(repo);
|
|
|
|
// Viewing one's own dashboard.
|
|
assertDashboardQuery(
|
|
"self", DASHBOARD_OUTGOING_QUERY, ownedOpenReviewableIgnoredByOther, ownedOpenReviewable);
|
|
|
|
// Viewing another user's dashboard.
|
|
requestContext.setContext(newRequestContext(otherAccountId));
|
|
assertDashboardQuery(user.getUserName().get(), DASHBOARD_OUTGOING_QUERY, ownedOpenReviewable);
|
|
}
|
|
|
|
@Test
|
|
public void dashboardIncomingReviews() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Account.Id otherAccountId = createAccount("other");
|
|
DashboardChangeState reviewingReviewable =
|
|
new DashboardChangeState(otherAccountId).addReviewer(user.getAccountId()).create(repo);
|
|
DashboardChangeState reviewingReviewableIgnoredByReviewer =
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.create(repo);
|
|
DashboardChangeState assignedReviewable =
|
|
new DashboardChangeState(otherAccountId).assignTo(user.getAccountId()).create(repo);
|
|
DashboardChangeState assignedReviewableIgnoredByAssignee =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.create(repo);
|
|
|
|
// Create changes that should not be returned by any queries in this test.
|
|
new DashboardChangeState(otherAccountId).wip().addReviewer(user.getAccountId()).create(repo);
|
|
new DashboardChangeState(otherAccountId).wip().assignTo(user.getAccountId()).create(repo);
|
|
new DashboardChangeState(otherAccountId).addReviewer(otherAccountId).create(repo);
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.mergeBy(user.getAccountId())
|
|
.create(repo);
|
|
|
|
// Viewing one's own dashboard.
|
|
assertDashboardQuery("self", DASHBOARD_INCOMING_QUERY, assignedReviewable, reviewingReviewable);
|
|
|
|
// Viewing another user's dashboard.
|
|
requestContext.setContext(newRequestContext(otherAccountId));
|
|
assertDashboardQuery(
|
|
user.getUserName().get(),
|
|
DASHBOARD_INCOMING_QUERY,
|
|
assignedReviewableIgnoredByAssignee,
|
|
assignedReviewable,
|
|
reviewingReviewableIgnoredByReviewer,
|
|
reviewingReviewable);
|
|
}
|
|
|
|
@Test
|
|
public void dashboardRecentlyClosedReviews() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Account.Id otherAccountId = createAccount("other");
|
|
DashboardChangeState mergedOwned =
|
|
new DashboardChangeState(user.getAccountId()).mergeBy(user.getAccountId()).create(repo);
|
|
DashboardChangeState mergedOwnedIgnoredByOther =
|
|
new DashboardChangeState(user.getAccountId())
|
|
.ignoreBy(otherAccountId)
|
|
.mergeBy(user.getAccountId())
|
|
.create(repo);
|
|
DashboardChangeState mergedReviewing =
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.mergeBy(user.getAccountId())
|
|
.create(repo);
|
|
DashboardChangeState mergedReviewingIgnoredByUser =
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.mergeBy(user.getAccountId())
|
|
.create(repo);
|
|
DashboardChangeState mergedAssigned =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.mergeBy(user.getAccountId())
|
|
.create(repo);
|
|
DashboardChangeState mergedAssignedIgnoredByUser =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.mergeBy(user.getAccountId())
|
|
.create(repo);
|
|
DashboardChangeState abandonedOwned =
|
|
new DashboardChangeState(user.getAccountId()).abandon().create(repo);
|
|
DashboardChangeState abandonedOwnedIgnoredByOther =
|
|
new DashboardChangeState(user.getAccountId())
|
|
.ignoreBy(otherAccountId)
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedOwnedWip =
|
|
new DashboardChangeState(user.getAccountId()).wip().abandon().create(repo);
|
|
DashboardChangeState abandonedOwnedWipIgnoredByOther =
|
|
new DashboardChangeState(user.getAccountId())
|
|
.ignoreBy(otherAccountId)
|
|
.wip()
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedReviewing =
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedReviewingIgnoredByUser =
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedAssigned =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedAssignedIgnoredByUser =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedAssignedWip =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.wip()
|
|
.abandon()
|
|
.create(repo);
|
|
DashboardChangeState abandonedAssignedWipIgnoredByUser =
|
|
new DashboardChangeState(otherAccountId)
|
|
.assignTo(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.wip()
|
|
.abandon()
|
|
.create(repo);
|
|
|
|
// Create changes that should not be returned by any queries in this test.
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.wip()
|
|
.abandon()
|
|
.create(repo);
|
|
new DashboardChangeState(otherAccountId)
|
|
.addReviewer(user.getAccountId())
|
|
.ignoreBy(user.getAccountId())
|
|
.wip()
|
|
.abandon()
|
|
.create(repo);
|
|
|
|
// Viewing one's own dashboard.
|
|
assertDashboardQuery(
|
|
"self",
|
|
DASHBOARD_RECENTLY_CLOSED_QUERY,
|
|
abandonedAssigned,
|
|
abandonedReviewing,
|
|
abandonedOwnedWipIgnoredByOther,
|
|
abandonedOwnedWip,
|
|
abandonedOwnedIgnoredByOther,
|
|
abandonedOwned,
|
|
mergedAssigned,
|
|
mergedReviewing,
|
|
mergedOwnedIgnoredByOther,
|
|
mergedOwned);
|
|
|
|
// Viewing another user's dashboard.
|
|
requestContext.setContext(newRequestContext(otherAccountId));
|
|
assertDashboardQuery(
|
|
user.getUserName().get(),
|
|
DASHBOARD_RECENTLY_CLOSED_QUERY,
|
|
abandonedAssignedWipIgnoredByUser,
|
|
abandonedAssignedWip,
|
|
abandonedAssignedIgnoredByUser,
|
|
abandonedAssigned,
|
|
abandonedReviewingIgnoredByUser,
|
|
abandonedReviewing,
|
|
abandonedOwned,
|
|
mergedAssignedIgnoredByUser,
|
|
mergedAssigned,
|
|
mergedReviewingIgnoredByUser,
|
|
mergedReviewing,
|
|
mergedOwned);
|
|
}
|
|
|
|
@Test
|
|
public void assignee() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChange(repo));
|
|
|
|
AssigneeInput input = new AssigneeInput();
|
|
input.assignee = user.getUserName().get();
|
|
gApi.changes().id(change1.getChangeId()).setAssignee(input);
|
|
|
|
assertQuery("is:assigned", change1);
|
|
assertQuery("-is:assigned", change2);
|
|
assertQuery("is:unassigned", change2);
|
|
assertQuery("-is:unassigned", change1);
|
|
assertQuery("assignee:" + user.getUserName().get(), change1);
|
|
assertQuery("-assignee:" + user.getUserName().get(), change2);
|
|
}
|
|
|
|
@Test
|
|
public void userDestination() throws Exception {
|
|
TestRepository<Repo> repo1 = createProject("repo1");
|
|
Change change1 = insert(repo1, newChange(repo1));
|
|
TestRepository<Repo> repo2 = createProject("repo2");
|
|
Change change2 = insert(repo2, newChange(repo2));
|
|
|
|
assertThatQueryException("destination:foo")
|
|
.hasMessageThat()
|
|
.isEqualTo("Unknown named destination: foo");
|
|
|
|
String destination1 = "refs/heads/master\trepo1";
|
|
String destination2 = "refs/heads/master\trepo2";
|
|
String destination3 = "refs/heads/master\trepo1\nrefs/heads/master\trepo2";
|
|
String destination4 = "refs/heads/master\trepo3";
|
|
String destination5 = "refs/heads/other\trepo1";
|
|
|
|
TestRepository<Repo> allUsers = new TestRepository<>(repoManager.openRepository(allUsersName));
|
|
String refsUsers = RefNames.refsUsers(userId);
|
|
allUsers.branch(refsUsers).commit().add("destinations/destination1", destination1).create();
|
|
allUsers.branch(refsUsers).commit().add("destinations/destination2", destination2).create();
|
|
allUsers.branch(refsUsers).commit().add("destinations/destination3", destination3).create();
|
|
allUsers.branch(refsUsers).commit().add("destinations/destination4", destination4).create();
|
|
allUsers.branch(refsUsers).commit().add("destinations/destination5", destination5).create();
|
|
|
|
Ref userRef = allUsers.getRepository().exactRef(refsUsers);
|
|
assertThat(userRef).isNotNull();
|
|
|
|
assertQuery("destination:destination1", change1);
|
|
assertQuery("destination:destination2", change2);
|
|
assertQuery("destination:destination3", change2, change1);
|
|
assertQuery("destination:destination4");
|
|
assertQuery("destination:destination5");
|
|
}
|
|
|
|
@Test
|
|
public void userQuery() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change1 = insert(repo, newChange(repo));
|
|
Change change2 = insert(repo, newChangeForBranch(repo, "stable"));
|
|
|
|
String queries =
|
|
"query1\tproject:repo\n"
|
|
+ "query2\tproject:repo status:open\n"
|
|
+ "query3\tproject:repo branch:stable\n"
|
|
+ "query4\tproject:repo branch:other";
|
|
|
|
TestRepository<Repo> allUsers = new TestRepository<>(repoManager.openRepository(allUsersName));
|
|
String refsUsers = RefNames.refsUsers(userId);
|
|
allUsers.branch(refsUsers).commit().add("queries", queries).create();
|
|
|
|
Ref userRef = allUsers.getRepository().exactRef(refsUsers);
|
|
assertThat(userRef).isNotNull();
|
|
|
|
assertThatQueryException("query:foo").hasMessageThat().isEqualTo("Unknown named query: foo");
|
|
|
|
assertQuery("query:query1", change2, change1);
|
|
assertQuery("query:query2", change2, change1);
|
|
gApi.changes().id(change1.getChangeId()).revision("current").review(ReviewInput.approve());
|
|
gApi.changes().id(change1.getChangeId()).revision("current").submit();
|
|
assertQuery("query:query2", change2);
|
|
assertQuery("query:query3", change2);
|
|
assertQuery("query:query4");
|
|
}
|
|
|
|
@Test
|
|
public void byOwnerInvalidQuery() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
insert(repo, newChange(repo), userId);
|
|
String nameEmail = user.asIdentifiedUser().getNameEmail();
|
|
assertQuery("owner: \"" + nameEmail + "\"\\");
|
|
}
|
|
|
|
@Test
|
|
public void byDeletedChange() throws Exception {
|
|
TestRepository<Repo> repo = createProject("repo");
|
|
Change change = insert(repo, newChange(repo));
|
|
|
|
String query = "change:" + change.getId();
|
|
assertQuery(query, change);
|
|
|
|
gApi.changes().id(change.getChangeId()).delete();
|
|
assertQuery(query);
|
|
}
|
|
|
|
protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
|
|
return newChange(repo, null, null, null, null, false);
|
|
}
|
|
|
|
protected ChangeInserter newChangeForCommit(TestRepository<Repo> repo, RevCommit commit)
|
|
throws Exception {
|
|
return newChange(repo, commit, null, null, null, false);
|
|
}
|
|
|
|
protected ChangeInserter newChangeForBranch(TestRepository<Repo> repo, String branch)
|
|
throws Exception {
|
|
return newChange(repo, null, branch, null, null, false);
|
|
}
|
|
|
|
protected ChangeInserter newChangeWithStatus(TestRepository<Repo> repo, Change.Status status)
|
|
throws Exception {
|
|
return newChange(repo, null, null, status, null, false);
|
|
}
|
|
|
|
protected ChangeInserter newChangeWithTopic(TestRepository<Repo> repo, String topic)
|
|
throws Exception {
|
|
return newChange(repo, null, null, null, topic, false);
|
|
}
|
|
|
|
protected ChangeInserter newChangeWorkInProgress(TestRepository<Repo> repo) throws Exception {
|
|
return newChange(repo, null, null, null, null, true);
|
|
}
|
|
|
|
protected ChangeInserter newChange(
|
|
TestRepository<Repo> repo,
|
|
@Nullable RevCommit commit,
|
|
@Nullable String branch,
|
|
@Nullable Change.Status status,
|
|
@Nullable String topic,
|
|
boolean workInProgress)
|
|
throws Exception {
|
|
if (commit == null) {
|
|
commit = repo.parseBody(repo.commit().message("message").create());
|
|
}
|
|
|
|
branch = MoreObjects.firstNonNull(branch, "refs/heads/master");
|
|
if (!branch.startsWith("refs/heads/")) {
|
|
branch = "refs/heads/" + branch;
|
|
}
|
|
|
|
Change.Id id = new Change.Id(seq.nextChangeId());
|
|
ChangeInserter ins =
|
|
changeFactory
|
|
.create(id, commit, branch)
|
|
.setValidate(false)
|
|
.setStatus(status)
|
|
.setTopic(topic)
|
|
.setWorkInProgress(workInProgress);
|
|
return ins;
|
|
}
|
|
|
|
protected Change insert(TestRepository<Repo> repo, ChangeInserter ins) throws Exception {
|
|
return insert(repo, ins, null, TimeUtil.nowTs());
|
|
}
|
|
|
|
protected Change insert(TestRepository<Repo> repo, ChangeInserter ins, @Nullable Account.Id owner)
|
|
throws Exception {
|
|
return insert(repo, ins, owner, TimeUtil.nowTs());
|
|
}
|
|
|
|
protected Change insert(
|
|
TestRepository<Repo> repo,
|
|
ChangeInserter ins,
|
|
@Nullable Account.Id owner,
|
|
Timestamp createdOn)
|
|
throws Exception {
|
|
Project.NameKey project =
|
|
new Project.NameKey(repo.getRepository().getDescription().getRepositoryName());
|
|
Account.Id ownerId = owner != null ? owner : userId;
|
|
IdentifiedUser user = userFactory.create(ownerId);
|
|
try (BatchUpdate bu = updateFactory.create(db, project, user, createdOn)) {
|
|
bu.insertChange(ins);
|
|
bu.execute();
|
|
return ins.getChange();
|
|
}
|
|
}
|
|
|
|
protected Change newPatchSet(TestRepository<Repo> repo, Change c) throws Exception {
|
|
// Add a new file so the patch set is not a trivial rebase, to avoid default
|
|
// Code-Review label copying.
|
|
int n = c.currentPatchSetId().get() + 1;
|
|
RevCommit commit =
|
|
repo.parseBody(repo.commit().message("message").add("file" + n, "contents " + n).create());
|
|
|
|
PatchSetInserter inserter =
|
|
patchSetFactory
|
|
.create(changeNotesFactory.createChecked(db, c), new PatchSet.Id(c.getId(), n), commit)
|
|
.setNotify(NotifyHandling.NONE)
|
|
.setFireRevisionCreated(false)
|
|
.setValidate(false);
|
|
try (BatchUpdate bu = updateFactory.create(db, c.getProject(), user, TimeUtil.nowTs());
|
|
ObjectInserter oi = repo.getRepository().newObjectInserter();
|
|
ObjectReader reader = oi.newReader();
|
|
RevWalk rw = new RevWalk(reader)) {
|
|
bu.setRepository(repo.getRepository(), rw, oi);
|
|
bu.addOp(c.getId(), inserter);
|
|
bu.execute();
|
|
}
|
|
|
|
return inserter.getChange();
|
|
}
|
|
|
|
protected ThrowableSubject assertThatQueryException(Object query) throws Exception {
|
|
return assertThatQueryException(newQuery(query));
|
|
}
|
|
|
|
protected ThrowableSubject assertThatQueryException(QueryRequest query) throws Exception {
|
|
try {
|
|
query.get();
|
|
throw new AssertionError("expected BadRequestException for query: " + query);
|
|
} catch (BadRequestException e) {
|
|
return assertThat(e);
|
|
}
|
|
}
|
|
|
|
protected TestRepository<Repo> createProject(String name) throws Exception {
|
|
gApi.projects().create(name).get();
|
|
return new TestRepository<>(repoManager.openRepository(new Project.NameKey(name)));
|
|
}
|
|
|
|
protected TestRepository<Repo> createProject(String name, String parent) throws Exception {
|
|
ProjectInput input = new ProjectInput();
|
|
input.name = name;
|
|
input.parent = parent;
|
|
gApi.projects().create(input).get();
|
|
return new TestRepository<>(repoManager.openRepository(new Project.NameKey(name)));
|
|
}
|
|
|
|
protected QueryRequest newQuery(Object query) {
|
|
return gApi.changes().query(query.toString());
|
|
}
|
|
|
|
protected List<ChangeInfo> assertQuery(Object query, Change... changes) throws Exception {
|
|
return assertQuery(newQuery(query), changes);
|
|
}
|
|
|
|
protected List<ChangeInfo> assertQueryByIds(Object query, Change.Id... changes) throws Exception {
|
|
return assertQueryByIds(newQuery(query), changes);
|
|
}
|
|
|
|
protected List<ChangeInfo> assertQuery(QueryRequest query, Change... changes) throws Exception {
|
|
return assertQueryByIds(
|
|
query, Arrays.stream(changes).map(Change::getId).toArray(Change.Id[]::new));
|
|
}
|
|
|
|
protected List<ChangeInfo> assertQueryByIds(QueryRequest query, Change.Id... changes)
|
|
throws Exception {
|
|
List<ChangeInfo> result = query.get();
|
|
Iterable<Change.Id> ids = ids(result);
|
|
assertThat(ids)
|
|
.named(format(query, ids, changes))
|
|
.containsExactlyElementsIn(Arrays.asList(changes))
|
|
.inOrder();
|
|
return result;
|
|
}
|
|
|
|
private String format(
|
|
QueryRequest query, Iterable<Change.Id> actualIds, Change.Id... expectedChanges)
|
|
throws RestApiException {
|
|
StringBuilder b = new StringBuilder();
|
|
b.append("query '").append(query.getQuery()).append("' with expected changes ");
|
|
b.append(format(Arrays.asList(expectedChanges)));
|
|
b.append(" and result ");
|
|
b.append(format(actualIds));
|
|
return b.toString();
|
|
}
|
|
|
|
private String format(Iterable<Change.Id> changeIds) throws RestApiException {
|
|
return format(changeIds.iterator());
|
|
}
|
|
|
|
private String format(Iterator<Change.Id> changeIds) throws RestApiException {
|
|
StringBuilder b = new StringBuilder();
|
|
b.append("[");
|
|
while (changeIds.hasNext()) {
|
|
Change.Id id = changeIds.next();
|
|
ChangeInfo c = gApi.changes().id(id.get()).get();
|
|
b.append("{")
|
|
.append(id)
|
|
.append(" (")
|
|
.append(c.changeId)
|
|
.append("), ")
|
|
.append("dest=")
|
|
.append(new Branch.NameKey(new Project.NameKey(c.project), c.branch))
|
|
.append(", ")
|
|
.append("status=")
|
|
.append(c.status)
|
|
.append(", ")
|
|
.append("lastUpdated=")
|
|
.append(c.updated.getTime())
|
|
.append("}");
|
|
if (changeIds.hasNext()) {
|
|
b.append(", ");
|
|
}
|
|
}
|
|
b.append("]");
|
|
return b.toString();
|
|
}
|
|
|
|
protected static Iterable<Change.Id> ids(Change... changes) {
|
|
return Arrays.stream(changes).map(Change::getId).collect(toList());
|
|
}
|
|
|
|
protected static Iterable<Change.Id> ids(Iterable<ChangeInfo> changes) {
|
|
return Streams.stream(changes).map(c -> new Change.Id(c._number)).collect(toList());
|
|
}
|
|
|
|
protected static long lastUpdatedMs(Change c) {
|
|
return c.getLastUpdatedOn().getTime();
|
|
}
|
|
|
|
private void addComment(int changeId, String message, Boolean unresolved) throws Exception {
|
|
ReviewInput input = new ReviewInput();
|
|
ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
|
|
comment.line = 1;
|
|
comment.message = message;
|
|
comment.unresolved = unresolved;
|
|
input.comments =
|
|
ImmutableMap.<String, List<ReviewInput.CommentInput>>of(
|
|
Patch.COMMIT_MSG, ImmutableList.<ReviewInput.CommentInput>of(comment));
|
|
gApi.changes().id(changeId).current().review(input);
|
|
}
|
|
|
|
private Account.Id createAccount(String username, String fullName, String email, boolean active)
|
|
throws Exception {
|
|
try (ManualRequestContext ctx = oneOffRequestContext.open()) {
|
|
Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
|
|
if (email != null) {
|
|
accountManager.link(id, AuthRequest.forEmail(email));
|
|
}
|
|
accountsUpdate
|
|
.get()
|
|
.update(
|
|
"Update Test Account",
|
|
id,
|
|
u -> {
|
|
u.setFullName(fullName).setPreferredEmail(email).setActive(active);
|
|
});
|
|
return id;
|
|
}
|
|
}
|
|
|
|
protected void assertMissingField(FieldDef<ChangeData, ?> field) {
|
|
assertThat(getSchema().hasField(field))
|
|
.named("schema %s has field %s", getSchemaVersion(), field.getName())
|
|
.isFalse();
|
|
}
|
|
|
|
protected void assertFailingQuery(String query, String expectedMessage) throws Exception {
|
|
try {
|
|
assertQuery(query);
|
|
fail("expected BadRequestException for query '" + query + "'");
|
|
} catch (BadRequestException e) {
|
|
assertThat(e.getMessage()).isEqualTo(expectedMessage);
|
|
}
|
|
}
|
|
|
|
protected int getSchemaVersion() {
|
|
return getSchema().getVersion();
|
|
}
|
|
|
|
protected Schema<ChangeData> getSchema() {
|
|
return indexes.getSearchIndex().getSchema();
|
|
}
|
|
}
|