Add abstract base class for testing notifications
This will be used for integration tests that make assertions about who
receives notification emails in response to particular actions.
Change-Id: Iea5b67a9988707a089c02a8b099138d84fbe07e0
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index f8f515c..07d39be 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -235,6 +235,7 @@
protected TestAccount admin;
protected TestAccount user;
protected TestRepository<InMemoryRepository> testRepo;
+ protected String resourcePrefix;
@Inject private ChangeIndexCollection changeIndexes;
@Inject private EventRecorder.Factory eventRecorderFactory;
@@ -243,7 +244,6 @@
@Inject private SchemaFactory<ReviewDb> reviewDbProvider;
private List<Repository> toClose;
- private String resourcePrefix;
private boolean useSsh;
@Before
@@ -1230,16 +1230,36 @@
assertThat(m.headers().get("CC").isEmpty()).isTrue();
}
- protected void watch(String project, String filter) throws RestApiException {
- List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
+ protected interface ProjectWatchInfoConfiguration {
+ void configure(ProjectWatchInfo pwi);
+ }
+
+ protected void watch(String project, ProjectWatchInfoConfiguration config)
+ throws OrmException, RestApiException {
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = project;
- pwi.filter = filter;
- pwi.notifyAbandonedChanges = true;
- pwi.notifyNewChanges = true;
- pwi.notifyAllComments = true;
- projectsToWatch.add(pwi);
- gApi.accounts().self().setWatchedProjects(projectsToWatch);
+ config.configure(pwi);
+ gApi.accounts().self().setWatchedProjects(ImmutableList.of(pwi));
+ }
+
+ protected void watch(PushOneCommit.Result r, ProjectWatchInfoConfiguration config)
+ throws OrmException, RestApiException {
+ watch(r.getChange().project().get(), config);
+ }
+
+ protected void watch(String project, String filter) throws OrmException, RestApiException {
+ watch(
+ project,
+ pwi -> {
+ pwi.filter = filter;
+ pwi.notifyAbandonedChanges = true;
+ pwi.notifyNewChanges = true;
+ pwi.notifyAllComments = true;
+ });
+ }
+
+ protected void watch(String project) throws OrmException, RestApiException {
+ watch(project, (String) null);
}
protected void assertContent(PushOneCommit.Result pushResult, String path, String expectedContent)
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
new file mode 100644
index 0000000..cf71f79
--- /dev/null
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
@@ -0,0 +1,435 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.gerrit.extensions.api.changes.RecipientType.*;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.Subject;
+import com.google.common.truth.SubjectFactory;
+import com.google.common.truth.Truth;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.AddReviewerResult;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.extensions.api.projects.ConfigInput;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.ReviewerState;
+import com.google.gerrit.server.account.WatchConfig.NotifyType;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.send.EmailHeader;
+import com.google.gerrit.server.mail.send.EmailHeader.AddressList;
+import com.google.gerrit.testutil.FakeEmailSender;
+import com.google.gerrit.testutil.FakeEmailSender.Message;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Before;
+
+public abstract class AbstractNotificationTest extends AbstractDaemonTest {
+ @Before
+ public void enableReviewerByEmail() throws Exception {
+ setApiUser(admin);
+ ConfigInput conf = new ConfigInput();
+ conf.enableReviewerByEmail = InheritableBoolean.TRUE;
+ gApi.projects().name(project.get()).config(conf);
+ }
+
+ private static final SubjectFactory<FakeEmailSenderSubject, FakeEmailSender>
+ FAKE_EMAIL_SENDER_SUBJECT_FACTORY =
+ new SubjectFactory<FakeEmailSenderSubject, FakeEmailSender>() {
+ @Override
+ public FakeEmailSenderSubject getSubject(
+ FailureStrategy failureStrategy, FakeEmailSender target) {
+ return new FakeEmailSenderSubject(failureStrategy, target);
+ }
+ };
+
+ protected static FakeEmailSenderSubject assertThat(FakeEmailSender sender) {
+ return assertAbout(FAKE_EMAIL_SENDER_SUBJECT_FACTORY).that(sender);
+ }
+
+ protected void setEmailStrategy(TestAccount account, EmailStrategy strategy) throws Exception {
+ setApiUser(account);
+ GeneralPreferencesInfo prefs = gApi.accounts().self().getPreferences();
+ prefs.emailStrategy = strategy;
+ gApi.accounts().self().setPreferences(prefs);
+ }
+
+ protected static class FakeEmailSenderSubject
+ extends Subject<FakeEmailSenderSubject, FakeEmailSender> {
+ private Message message;
+ private StagedUsers users;
+ private Map<RecipientType, List<String>> recipients = new HashMap<>();
+
+ FakeEmailSenderSubject(FailureStrategy failureStrategy, FakeEmailSender target) {
+ super(failureStrategy, target);
+ }
+
+ public FakeEmailSenderSubject notSent() {
+ if (actual().peekMessage() != null) {
+ fail("a message wasn't sent");
+ }
+ return this;
+ }
+
+ public FakeEmailSenderSubject sent(String messageType, StagedUsers users) {
+ message = actual().nextMessage();
+ if (message == null) {
+ fail("a message was sent");
+ }
+ recipients = new HashMap<>();
+ recipients.put(TO, parseAddresses(message, "To"));
+ recipients.put(CC, parseAddresses(message, "CC"));
+ recipients.put(
+ BCC,
+ message
+ .rcpt()
+ .stream()
+ .map(Address::getEmail)
+ .filter(e -> !recipients.get(TO).contains(e) && !recipients.get(CC).contains(e))
+ .collect(Collectors.toList()));
+ this.users = users;
+ if (!message.headers().containsKey("X-Gerrit-MessageType")) {
+ fail("a message was sent with X-Gerrit-MessageType header");
+ }
+ EmailHeader header = message.headers().get("X-Gerrit-MessageType");
+ if (!header.equals(new EmailHeader.String(messageType))) {
+ fail("message of type " + messageType + " was sent; X-Gerrit-MessageType is " + header);
+ }
+
+ // Return a named subject that displays a human-readable table of
+ // recipients.
+ StringBuilder buf = new StringBuilder();
+ buf.append('[');
+ for (RecipientType type : ImmutableList.of(TO, CC, BCC)) {
+ buf.append('\n');
+ buf.append(type);
+ buf.append(':');
+ String delim = " ";
+ for (String r : recipients.get(type)) {
+ buf.append(delim);
+ buf.append(users.emailToName(r));
+ delim = ", ";
+ }
+ }
+ buf.append("\n]");
+ return named(buf.toString());
+ }
+
+ List<String> parseAddresses(Message msg, String headerName) {
+ EmailHeader header = msg.headers().get(headerName);
+ if (header == null) {
+ return ImmutableList.of();
+ }
+ Truth.assertThat(header).isInstanceOf(AddressList.class);
+ AddressList addrList = (AddressList) header;
+ return addrList.getAddressList().stream().map(Address::getEmail).collect(Collectors.toList());
+ }
+
+ public FakeEmailSenderSubject to(String... emails) {
+ return rcpt(users.supportReviewersByEmail ? TO : null, emails);
+ }
+
+ public FakeEmailSenderSubject cc(String... emails) {
+ return rcpt(users.supportReviewersByEmail ? CC : null, emails);
+ }
+
+ public FakeEmailSenderSubject bcc(String... emails) {
+ return rcpt(users.supportReviewersByEmail ? BCC : null, emails);
+ }
+
+ private FakeEmailSenderSubject rcpt(@Nullable RecipientType type, String[] emails) {
+ for (String email : emails) {
+ rcpt(type, email);
+ }
+ return this;
+ }
+
+ private void rcpt(@Nullable RecipientType type, String email) {
+ rcpt(TO, email, TO.equals(type));
+ rcpt(CC, email, CC.equals(type));
+ rcpt(BCC, email, BCC.equals(type));
+ }
+
+ private void rcpt(@Nullable RecipientType type, String email, boolean expected) {
+ if (recipients.get(type).contains(email) != expected) {
+ fail(
+ expected ? "notifies" : "doesn't notify",
+ "]\n" + type + ": " + users.emailToName(email) + "\n]");
+ }
+ }
+
+ public FakeEmailSenderSubject notTo(String... emails) {
+ return rcpt(null, emails);
+ }
+
+ public FakeEmailSenderSubject to(TestAccount... accounts) {
+ return rcpt(TO, accounts);
+ }
+
+ public FakeEmailSenderSubject cc(TestAccount... accounts) {
+ return rcpt(CC, accounts);
+ }
+
+ public FakeEmailSenderSubject bcc(TestAccount... accounts) {
+ return rcpt(BCC, accounts);
+ }
+
+ public FakeEmailSenderSubject notTo(TestAccount... accounts) {
+ return rcpt(null, accounts);
+ }
+
+ private FakeEmailSenderSubject rcpt(@Nullable RecipientType type, TestAccount[] accounts) {
+ for (TestAccount account : accounts) {
+ rcpt(type, account);
+ }
+ return this;
+ }
+
+ private void rcpt(@Nullable RecipientType type, TestAccount account) {
+ rcpt(type, account.email);
+ }
+
+ public FakeEmailSenderSubject to(NotifyType... watches) {
+ return rcpt(TO, watches);
+ }
+
+ public FakeEmailSenderSubject cc(NotifyType... watches) {
+ return rcpt(CC, watches);
+ }
+
+ public FakeEmailSenderSubject bcc(NotifyType... watches) {
+ return rcpt(BCC, watches);
+ }
+
+ public FakeEmailSenderSubject notTo(NotifyType... watches) {
+ return rcpt(null, watches);
+ }
+
+ private FakeEmailSenderSubject rcpt(@Nullable RecipientType type, NotifyType[] watches) {
+ for (NotifyType watch : watches) {
+ rcpt(type, watch);
+ }
+ return this;
+ }
+
+ private void rcpt(@Nullable RecipientType type, NotifyType watch) {
+ if (!users.watchers.containsKey(watch)) {
+ fail("configured to watch", watch);
+ }
+ rcpt(type, users.watchers.get(watch));
+ }
+ }
+
+ protected class StagedUsers {
+ public final TestAccount owner;
+ public final TestAccount author;
+ public final TestAccount uploader;
+ public final TestAccount reviewer;
+ public final TestAccount ccer;
+ public final TestAccount starrer;
+ public final TestAccount watchingProjectOwner;
+ public final String reviewerByEmail = "reviewerByEmail@example.com";
+ public final String ccerByEmail = "ccByEmail@example.com";
+ private final Map<NotifyType, TestAccount> watchers = new HashMap<>();
+ private final Map<String, TestAccount> accountsByEmail = new HashMap<>();
+ boolean supportReviewersByEmail;
+
+ StagedUsers(List<NotifyType> watches) throws Exception {
+ owner = testAccount("owner");
+ reviewer = testAccount("reviewer");
+ author = testAccount("author");
+ uploader = testAccount("uploader");
+ ccer = testAccount("ccer");
+ starrer = testAccount("starrer");
+
+ watchingProjectOwner = testAccount("watchingProjectOwner", "Administrators");
+ setApiUser(watchingProjectOwner);
+ watch(project.get(), pwi -> pwi.notifyNewChanges = true);
+
+ for (NotifyType watch : watches) {
+ TestAccount watcher = testAccount(watch.toString());
+ setApiUser(watcher);
+ watch(
+ project.get(),
+ pwi -> {
+ pwi.notifyAllComments = watch.equals(NotifyType.ALL_COMMENTS);
+ pwi.notifyAbandonedChanges = watch.equals(NotifyType.ABANDONED_CHANGES);
+ pwi.notifyNewChanges = watch.equals(NotifyType.NEW_CHANGES);
+ pwi.notifyNewPatchSets = watch.equals(NotifyType.NEW_PATCHSETS);
+ pwi.notifySubmittedChanges = watch.equals(NotifyType.SUBMITTED_CHANGES);
+ });
+ watchers.put(watch, watcher);
+ }
+ }
+
+ private String email(String username) {
+ // Email validator rejects usernames longer than 64 bytes.
+ if (username.length() > 64) {
+ username = username.substring(username.length() - 64);
+ if (username.startsWith(".")) {
+ username = username.substring(1);
+ }
+ }
+ return username + "@example.com";
+ }
+
+ TestAccount testAccount(String name) throws Exception {
+ String username = name(name);
+ TestAccount account = accountCreator.create(username, email(username), name);
+ accountsByEmail.put(account.email, account);
+ return account;
+ }
+
+ TestAccount testAccount(String name, String groupName) throws Exception {
+ String username = name(name);
+ TestAccount account = accountCreator.create(username, email(username), name, groupName);
+ accountsByEmail.put(account.email, account);
+ return account;
+ }
+
+ String emailToName(String email) {
+ if (accountsByEmail.containsKey(email)) {
+ return accountsByEmail.get(email).fullName;
+ }
+ return email;
+ }
+
+ protected void addReviewers(PushOneCommit.Result r) throws Exception {
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = reviewer.email;
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+
+ in.reviewer = ccer.email;
+ in.state = ReviewerState.CC;
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+
+ in.reviewer = reviewerByEmail;
+ in.state = ReviewerState.REVIEWER;
+ AddReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer(in);
+ if (result.reviewers == null || result.reviewers.isEmpty()) {
+ supportReviewersByEmail = false;
+ } else {
+ supportReviewersByEmail = true;
+ in.reviewer = ccerByEmail;
+ in.state = ReviewerState.CC;
+ gApi.changes().id(r.getChangeId()).addReviewer(in);
+ }
+ }
+ }
+
+ protected interface PushOptionGenerator {
+ List<String> pushOptions(StagedUsers users);
+ }
+
+ protected class StagedPreChange extends StagedUsers {
+ public final TestRepository<?> repo;
+ protected final PushOneCommit.Result result;
+ public final String changeId;
+
+ StagedPreChange(String ref, List<NotifyType> watches) throws Exception {
+ this(ref, null, watches);
+ }
+
+ StagedPreChange(
+ String ref, @Nullable PushOptionGenerator pushOptionGenerator, List<NotifyType> watches)
+ throws Exception {
+ super(watches);
+ List<String> pushOptions = null;
+ if (pushOptionGenerator != null) {
+ pushOptions = pushOptionGenerator.pushOptions(this);
+ }
+ if (pushOptions != null) {
+ ref = ref + '%' + Joiner.on(',').join(pushOptions);
+ }
+ repo = cloneProject(project, owner);
+ PushOneCommit push = pushFactory.create(db, owner.getIdent(), repo);
+ result = push.to(ref);
+ result.assertOkStatus();
+ changeId = result.getChangeId();
+ }
+ }
+
+ protected StagedPreChange stagePreChange(String ref, NotifyType... watches) throws Exception {
+ return new StagedPreChange(ref, ImmutableList.copyOf(watches));
+ }
+
+ protected StagedPreChange stagePreChange(
+ String ref, @Nullable PushOptionGenerator pushOptionGenerator, NotifyType... watches)
+ throws Exception {
+ return new StagedPreChange(ref, pushOptionGenerator, ImmutableList.copyOf(watches));
+ }
+
+ protected class StagedChange extends StagedPreChange {
+
+ StagedChange(String ref, List<NotifyType> watches) throws Exception {
+ super(ref, watches);
+
+ setApiUser(starrer);
+ gApi.accounts().self().starChange(result.getChangeId());
+
+ setApiUser(owner);
+ addReviewers(result);
+ sender.clear();
+ }
+ }
+
+ protected StagedChange stageReviewableChange(NotifyType... watches) throws Exception {
+ return new StagedChange("refs/for/master", ImmutableList.copyOf(watches));
+ }
+
+ protected StagedChange stageWipChange(NotifyType... watches) throws Exception {
+ return new StagedChange("refs/for/master%wip", ImmutableList.copyOf(watches));
+ }
+
+ protected StagedChange stageReviewableWipChange(NotifyType... watches) throws Exception {
+ StagedChange sc = stageReviewableChange(watches);
+ setApiUser(sc.owner);
+ gApi.changes().id(sc.changeId).setWorkInProgress();
+ return sc;
+ }
+
+ protected StagedChange stageAbandonedReviewableChange(NotifyType... watches) throws Exception {
+ StagedChange sc = stageReviewableChange(watches);
+ setApiUser(sc.owner);
+ gApi.changes().id(sc.changeId).abandon();
+ sender.clear();
+ return sc;
+ }
+
+ protected StagedChange stageAbandonedReviewableWipChange(NotifyType... watches) throws Exception {
+ StagedChange sc = stageReviewableWipChange(watches);
+ setApiUser(sc.owner);
+ gApi.changes().id(sc.changeId).abandon();
+ sender.clear();
+ return sc;
+ }
+
+ protected StagedChange stageAbandonedWipChange(NotifyType... watches) throws Exception {
+ StagedChange sc = stageWipChange(watches);
+ setApiUser(sc.owner);
+ gApi.changes().id(sc.changeId).abandon();
+ sender.clear();
+ return sc;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index afd20bb..43d02a6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -113,7 +113,7 @@
@Test
public void notificationsOnChangeCreation() throws Exception {
setApiUser(user);
- watch(project.get(), null);
+ watch(project.get());
// check that watcher is notified
setApiUser(admin);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index 140a756..e7fe81f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -285,7 +285,7 @@
// watch project
String watchedProject = createProject("watchedProject").get();
setApiUser(user);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a change to watched project -> should trigger email notification
setApiUser(admin);
@@ -327,7 +327,7 @@
watch(watchedProject, "file:a.txt");
// watch other project as user
- watch(otherWatchedProject, null);
+ watch(otherWatchedProject);
// push a change to watched file -> should trigger email notification for
// user
@@ -352,7 +352,7 @@
// watch project as user2
TestAccount user2 = accountCreator.create("user2", "user2@test.com", "User2");
setApiUser(user2);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a change to non-watched file -> should not trigger email
// notification for user, only for user2
@@ -416,7 +416,7 @@
setApiUser(user);
// watch the All-Projects project to watch all projects
- watch(allProjects.get(), null);
+ watch(allProjects.get());
// push a change to any project -> should trigger email notification
setApiUser(admin);
@@ -469,7 +469,7 @@
// watch project as user2
TestAccount user2 = accountCreator.create("user2", "user2@test.com", "User2");
setApiUser(user2);
- watch(anyProject, null);
+ watch(anyProject);
// push a change to non-watched file in any project -> should not trigger
// email notification for user, only for user2
@@ -533,7 +533,7 @@
// watch project
String watchedProject = createProject("watchedProject").get();
setApiUser(user);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a draft change to watched project -> should not trigger email notification
setApiUser(admin);
@@ -564,13 +564,13 @@
// watch project as user that can't view drafts
setApiUser(user);
- watch(watchedProject, null);
+ watch(watchedProject);
// watch project as user that can view all drafts
TestAccount userThatCanViewDrafts =
accountCreator.create("user2", "user2@test.com", "User2", groupThatCanViewDrafts.name);
setApiUser(userThatCanViewDrafts);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a draft change to watched project -> should trigger email notification for
// userThatCanViewDrafts, but not for user
@@ -597,7 +597,7 @@
// watch project
String watchedProject = createProject("watchedProject").get();
setApiUser(user);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a change to watched project
setApiUser(admin);
@@ -658,7 +658,7 @@
// watch project
String watchedProject = createProject("watchedProject").get();
setApiUser(user);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a private change to watched project -> should not trigger email notification
setApiUser(admin);
@@ -690,14 +690,14 @@
// watch project as user that can't view private changes
setApiUser(user);
- watch(watchedProject, null);
+ watch(watchedProject);
// watch project as user that can view all private change
TestAccount userThatCanViewPrivateChanges =
accountCreator.create(
"user2", "user2@test.com", "User2", groupThatCanViewPrivateChanges.name);
setApiUser(userThatCanViewPrivateChanges);
- watch(watchedProject, null);
+ watch(watchedProject);
// push a private change to watched project -> should trigger email notification for
// userThatCanViewPrivateChanges, but not for user
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
index ed3e41f..9ab1d6e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
@@ -28,11 +28,7 @@
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,11 +78,13 @@
private final WorkQueue workQueue;
private final List<Message> messages;
+ private int messagesRead;
@Inject
FakeEmailSender(WorkQueue workQueue) {
this.workQueue = workQueue;
messages = Collections.synchronizedList(new ArrayList<Message>());
+ messagesRead = 0;
}
@Override
@@ -121,9 +119,23 @@
waitForEmails();
synchronized (messages) {
messages.clear();
+ messagesRead = 0;
}
}
+ public synchronized @Nullable Message peekMessage() {
+ if (messagesRead >= messages.size()) {
+ return null;
+ }
+ return messages.get(messagesRead);
+ }
+
+ public synchronized @Nullable Message nextMessage() {
+ Message msg = peekMessage();
+ messagesRead++;
+ return msg;
+ }
+
public ImmutableList<Message> getMessages() {
waitForEmails();
synchronized (messages) {