Line data Source code
1 : // Copyright (C) 2015 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.testing; 16 : 17 : import static java.util.stream.Collectors.toList; 18 : 19 : import com.google.auto.value.AutoValue; 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.common.collect.ImmutableMap; 22 : import com.google.common.flogger.FluentLogger; 23 : import com.google.gerrit.common.Nullable; 24 : import com.google.gerrit.entities.Address; 25 : import com.google.gerrit.entities.EmailHeader; 26 : import com.google.gerrit.exceptions.EmailException; 27 : import com.google.gerrit.mail.MailHeader; 28 : import com.google.gerrit.server.git.WorkQueue; 29 : import com.google.gerrit.server.mail.send.EmailSender; 30 : import com.google.inject.AbstractModule; 31 : import com.google.inject.Inject; 32 : import com.google.inject.Singleton; 33 : import java.util.ArrayList; 34 : import java.util.Collection; 35 : import java.util.Collections; 36 : import java.util.List; 37 : import java.util.Map; 38 : import java.util.concurrent.ExecutionException; 39 : 40 : /** 41 : * Email sender implementation that records messages in memory. 42 : * 43 : * <p>This class is mostly threadsafe. The only exception is that not all {@link EmailHeader} 44 : * subclasses are immutable. In particular, if a caller holds a reference to an {@code AddressList} 45 : * and mutates it after sending, the message returned by {@link #getMessages()} may or may not 46 : * reflect mutations. 47 : */ 48 : @Singleton 49 : public class FakeEmailSender implements EmailSender { 50 152 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 51 : 52 152 : public static class FakeEmailSenderModule extends AbstractModule { 53 : @Override 54 : public void configure() { 55 152 : bind(EmailSender.class).to(FakeEmailSender.class); 56 152 : } 57 : } 58 : 59 : @AutoValue 60 55 : public abstract static class Message { 61 : private static Message create( 62 : Address from, 63 : Collection<Address> rcpt, 64 : Map<String, EmailHeader> headers, 65 : String body, 66 : String htmlBody) { 67 55 : return new AutoValue_FakeEmailSender_Message( 68 55 : from, ImmutableList.copyOf(rcpt), ImmutableMap.copyOf(headers), body, htmlBody); 69 : } 70 : 71 : public abstract Address from(); 72 : 73 : public abstract ImmutableList<Address> rcpt(); 74 : 75 : public abstract ImmutableMap<String, EmailHeader> headers(); 76 : 77 : public abstract String body(); 78 : 79 : @Nullable 80 : public abstract String htmlBody(); 81 : } 82 : 83 : private final WorkQueue workQueue; 84 : private final List<Message> messages; 85 : private int messagesRead; 86 : 87 : @Inject 88 152 : FakeEmailSender(WorkQueue workQueue) { 89 152 : this.workQueue = workQueue; 90 152 : messages = Collections.synchronizedList(new ArrayList<>()); 91 152 : messagesRead = 0; 92 152 : } 93 : 94 : @Override 95 : public boolean isEnabled() { 96 106 : return true; 97 : } 98 : 99 : @Override 100 : public boolean canEmail(String address) { 101 104 : return true; 102 : } 103 : 104 : @Override 105 : public void send( 106 : Address from, Collection<Address> rcpt, Map<String, EmailHeader> headers, String body) 107 : throws EmailException { 108 2 : send(from, rcpt, headers, body, null); 109 2 : } 110 : 111 : @Override 112 : public void send( 113 : Address from, 114 : Collection<Address> rcpt, 115 : Map<String, EmailHeader> headers, 116 : String body, 117 : String htmlBody) 118 : throws EmailException { 119 55 : messages.add(Message.create(from, rcpt, headers, body, htmlBody)); 120 55 : } 121 : 122 : public void clear() { 123 132 : waitForEmails(); 124 132 : synchronized (messages) { 125 132 : messages.clear(); 126 132 : messagesRead = 0; 127 132 : } 128 132 : } 129 : 130 : public synchronized @Nullable Message peekMessage() { 131 2 : if (messagesRead >= messages.size()) { 132 1 : return null; 133 : } 134 2 : return messages.get(messagesRead); 135 : } 136 : 137 : public synchronized @Nullable Message nextMessage() { 138 2 : Message msg = peekMessage(); 139 2 : messagesRead++; 140 2 : return msg; 141 : } 142 : 143 : public ImmutableList<Message> getMessages() { 144 25 : waitForEmails(); 145 25 : synchronized (messages) { 146 25 : return ImmutableList.copyOf(messages); 147 : } 148 : } 149 : 150 : public List<Message> getMessages(String changeId, String type) { 151 2 : final String idFooter = "\n" + MailHeader.CHANGE_ID.withDelimiter() + changeId + "\n"; 152 2 : final String typeFooter = "\n" + MailHeader.MESSAGE_TYPE.withDelimiter() + type + "\n"; 153 2 : return getMessages().stream() 154 2 : .filter(in -> in.body().contains(idFooter) && in.body().contains(typeFooter)) 155 2 : .collect(toList()); 156 : } 157 : 158 : private void waitForEmails() { 159 : // TODO(dborowitz): This is brittle; consider forcing emails to use 160 : // a single thread in tests (tricky because most callers just use the 161 : // default executor). 162 132 : for (WorkQueue.Task<?> task : workQueue.getTasks()) { 163 132 : if (task.toString().contains("send-email")) { 164 : try { 165 0 : task.get(); 166 0 : } catch (ExecutionException | InterruptedException e) { 167 0 : logger.atWarning().withCause(e).log("error finishing email task"); 168 0 : } 169 : } 170 132 : } 171 132 : } 172 : }