Implement HTML Parser and Tests
This change implements an HTML parser that works for Gmail and most generic HTML clients and adds tests for the newly created parser. In future iterations, we will add tests for more email clients and adapt the parser if necessary. Change-Id: I0ae5f3d496d164e791a2dd0181fcb6299e98e427
This commit is contained in:
@@ -64,6 +64,7 @@ java_library(
|
||||
'//lib/jgit/org.eclipse.jgit:jgit',
|
||||
'//lib/jgit/org.eclipse.jgit.archive:jgit-archive',
|
||||
'//lib/joda:joda-time',
|
||||
'//lib/jsoup:jsoup',
|
||||
'//lib/log:api',
|
||||
'//lib/log:jsonevent-layout',
|
||||
'//lib/log:log4j',
|
||||
|
||||
@@ -68,6 +68,7 @@ java_library(
|
||||
"//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
|
||||
"//lib/jgit/org.eclipse.jgit:jgit",
|
||||
"//lib/joda:joda-time",
|
||||
"//lib/jsoup:jsoup",
|
||||
"//lib/log:api",
|
||||
"//lib/log:jsonevent-layout",
|
||||
"//lib/log:log4j",
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.PeekingIterator;
|
||||
import com.google.gerrit.reviewdb.client.Comment;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/** HTMLParser provides parsing functionality for html email. */
|
||||
public class HtmlParser {
|
||||
/**
|
||||
* Parses comments from html email.
|
||||
*
|
||||
* @param email MailMessage as received from the email service.
|
||||
* @param comments A specific set of comments as sent out in the original
|
||||
* notification email. Comments are expected to be in the same
|
||||
* order as they were sent out to in the email
|
||||
* @param changeUrl Canonical change URL that points to the change on this
|
||||
* Gerrit instance.
|
||||
* Example: https://go-review.googlesource.com/#/c/91570
|
||||
* @return List of MailComments parsed from the html part of the email.
|
||||
*/
|
||||
public static List<MailComment> parse(MailMessage email,
|
||||
Collection<Comment> comments, String changeUrl) {
|
||||
// TODO(hiesel) Add support for Gmail Mobile
|
||||
// TODO(hiesel) Add tests for other popular email clients
|
||||
|
||||
// This parser goes though all html elements in the email and checks for
|
||||
// matching patterns. It keeps track of the last file and comments it
|
||||
// encountered to know in which context a parsed comment belongs.
|
||||
// It uses the href attributes of <a> tags to identify comments sent out by
|
||||
// Gerrit as these are generally more reliable then the text captions.
|
||||
List<MailComment> parsedComments = new ArrayList<>();
|
||||
Document d = Jsoup.parse(email.htmlContent());
|
||||
PeekingIterator<Comment> iter =
|
||||
Iterators.peekingIterator(comments.iterator());
|
||||
|
||||
String lastEncounteredFileName = null;
|
||||
Comment lastEncounteredComment = null;
|
||||
for (Element e : d.body().getAllElements()) {
|
||||
String elementName = e.tagName();
|
||||
boolean isInBlockQuote = e.parents().stream()
|
||||
.filter(p -> p.tagName().equals("blockquote"))
|
||||
.findAny()
|
||||
.isPresent();
|
||||
|
||||
if (elementName.equals("a")) {
|
||||
String href = e.attr("href");
|
||||
// Check if there is still a next comment that could be contained in
|
||||
// this <a> tag
|
||||
if (!iter.hasNext()) {
|
||||
continue;
|
||||
}
|
||||
Comment perspectiveComment = iter.peek();
|
||||
if (href.equals(ParserUtil.filePath(changeUrl, perspectiveComment))) {
|
||||
if (lastEncounteredFileName == null || !lastEncounteredFileName
|
||||
.equals(perspectiveComment.key.filename)) {
|
||||
// Not a file-level comment, but users could have typed a comment
|
||||
// right after this file annotation to create a new file-level
|
||||
// comment. If this file has a file-level comment, we have already
|
||||
// set lastEncounteredComment to that file-level comment when we
|
||||
// encountered the file link and should not reset it now.
|
||||
lastEncounteredFileName = perspectiveComment.key.filename;
|
||||
lastEncounteredComment = null;
|
||||
} else if (perspectiveComment.lineNbr == 0) {
|
||||
// This was originally a file-level comment
|
||||
lastEncounteredComment = perspectiveComment;
|
||||
iter.next();
|
||||
}
|
||||
} else if (ParserUtil.isCommentUrl(href, changeUrl,
|
||||
perspectiveComment)) {
|
||||
// This is a regular inline comment
|
||||
lastEncounteredComment = perspectiveComment;
|
||||
iter.next();
|
||||
}
|
||||
} else if (!isInBlockQuote && elementName.equals("div") &&
|
||||
!e.className().startsWith("gmail")) {
|
||||
// This is a comment typed by the user
|
||||
String content = e.ownText().trim();
|
||||
if (!Strings.isNullOrEmpty(content)) {
|
||||
if (lastEncounteredComment == null &&
|
||||
lastEncounteredFileName == null) {
|
||||
// Remove quotation line, email signature and
|
||||
// "Sent from my xyz device"
|
||||
content = ParserUtil.trimQuotationLine(content);
|
||||
// TODO(hiesel) Add more sanitizer
|
||||
if (!Strings.isNullOrEmpty(content)) {
|
||||
parsedComments.add(new MailComment(content, null, null,
|
||||
MailComment.CommentType.CHANGE_MESSAGE));
|
||||
}
|
||||
} else if (lastEncounteredComment == null) {
|
||||
parsedComments.add(new MailComment(content, lastEncounteredFileName,
|
||||
null, MailComment.CommentType.FILE_COMMENT));
|
||||
} else {
|
||||
parsedComments.add(new MailComment(content, null,
|
||||
lastEncounteredComment,
|
||||
MailComment.CommentType.INLINE_COMMENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsedComments;
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,14 @@ public class MailComment {
|
||||
Comment inReplyTo;
|
||||
String fileName;
|
||||
String message;
|
||||
|
||||
public MailComment() { }
|
||||
|
||||
public MailComment(String message, String fileName, Comment inReplyTo,
|
||||
CommentType type) {
|
||||
this.message = message;
|
||||
this.fileName = fileName;
|
||||
this.inReplyTo = inReplyTo;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Comment;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ParserUtil {
|
||||
private static final Pattern SIMPLE_EMAIL_PATTERN = Pattern.compile(
|
||||
"[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+"
|
||||
+ "(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})");
|
||||
|
||||
/**
|
||||
* Trims the quotation line that email clients add
|
||||
* Example: On Sun, Nov 20, 2016 at 10:33 PM, <gerrit@hiesel.it> wrote:
|
||||
* @param comment Comment parsed from an email.
|
||||
* @return Trimmed comment.
|
||||
*/
|
||||
public static String trimQuotationLine(String comment) {
|
||||
// Identifying the quotation line is hard, as it can be in any language.
|
||||
// We identify this line by it's characteristics: It usually contains a
|
||||
// valid email address, some digits for the date in groups of 1-4 in a row
|
||||
// as well as some characters.
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (String line : comment.split("\n")) {
|
||||
// Count occurrences of digit groups
|
||||
int numConsecutiveDigits = 0;
|
||||
int maxConsecutiveDigits = 0;
|
||||
int numDigitGroups = 0;
|
||||
for (char c : line.toCharArray()) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
numConsecutiveDigits++;
|
||||
} else if (numConsecutiveDigits > 0) {
|
||||
maxConsecutiveDigits = Integer.max(maxConsecutiveDigits,
|
||||
numConsecutiveDigits);
|
||||
numConsecutiveDigits = 0;
|
||||
numDigitGroups++;
|
||||
}
|
||||
}
|
||||
if (numDigitGroups < 4 || maxConsecutiveDigits > 4 ||
|
||||
!SIMPLE_EMAIL_PATTERN.matcher(line).find()) {
|
||||
b.append(line);
|
||||
}
|
||||
}
|
||||
return b.toString().trim();
|
||||
}
|
||||
|
||||
/** Check if string is an inline comment url on a patch set or the base */
|
||||
public static boolean isCommentUrl(String str, String changeUrl,
|
||||
Comment comment) {
|
||||
return str.equals(filePath(changeUrl, comment) + "@" + comment.lineNbr) ||
|
||||
str.equals(filePath(changeUrl, comment) + "@a" + comment.lineNbr);
|
||||
}
|
||||
|
||||
/** Generate the fully qualified filepath */
|
||||
public static String filePath(String changeUrl, Comment comment) {
|
||||
return changeUrl + "/" + comment.key.patchSetId + "/" +
|
||||
comment.key.filename;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public class TextParser {
|
||||
continue;
|
||||
}
|
||||
Comment perspectiveComment = iter.peek();
|
||||
if (line.equals(filePath(changeUrl, perspectiveComment))) {
|
||||
if (line.equals(ParserUtil.filePath(changeUrl, perspectiveComment))) {
|
||||
if (lastEncounteredFileName == null ||
|
||||
!lastEncounteredFileName
|
||||
.equals(perspectiveComment.key.filename)) {
|
||||
@@ -94,7 +94,8 @@ public class TextParser {
|
||||
lastEncounteredComment = perspectiveComment;
|
||||
iter.next();
|
||||
}
|
||||
} else if (isCommentUrl(line, changeUrl, perspectiveComment)) {
|
||||
} else if (ParserUtil.isCommentUrl(line, changeUrl,
|
||||
perspectiveComment)) {
|
||||
lastEncounteredComment = perspectiveComment;
|
||||
iter.next();
|
||||
}
|
||||
@@ -137,17 +138,4 @@ public class TextParser {
|
||||
private static int countOccurrences(String s, String pattern) {
|
||||
return (s.length() - s.replace(pattern, "").length()) / pattern.length();
|
||||
}
|
||||
|
||||
/** Check if string is an inline comment url on a patch set or the base */
|
||||
private static boolean isCommentUrl(String str, String changeUrl,
|
||||
Comment comment) {
|
||||
return str.equals(filePath(changeUrl, comment) + "@" + comment.lineNbr) ||
|
||||
str.equals(filePath(changeUrl, comment) + "@a" + comment.lineNbr);
|
||||
}
|
||||
|
||||
/** Generate the fully qualified filepath */
|
||||
private static String filePath(String changeUrl, Comment comment) {
|
||||
return changeUrl + "/" + comment.key.patchSetId + "/" +
|
||||
comment.key.filename;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ package com.google.gerrit.server.mail.receive;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Comment;
|
||||
import com.google.gerrit.server.mail.Address;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
@@ -26,6 +28,9 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
@Ignore
|
||||
public class AbstractParserTest {
|
||||
protected static final String changeURL =
|
||||
"https://gerrit-review.googlesource.com/#/changes/123";
|
||||
|
||||
protected static void assertChangeMessage(String message,
|
||||
MailComment comment) {
|
||||
assertThat(comment.fileName).isNull();
|
||||
@@ -67,4 +72,14 @@ public class AbstractParserTest {
|
||||
b.subject("");
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Returns a List of default comments for testing. */
|
||||
protected static List<Comment> defaultComments() {
|
||||
List<Comment> comments = new ArrayList<>();
|
||||
comments.add(newComment("c1", "gerrit-server/test.txt", "comment", 0));
|
||||
comments.add(newComment("c2", "gerrit-server/test.txt", "comment", 2));
|
||||
comments.add(newComment("c3", "gerrit-server/test.txt", "comment", 3));
|
||||
comments.add(newComment("c4", "gerrit-server/readme.txt", "comment", 3));
|
||||
return comments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
/** Test parser for a generic Html email client response */
|
||||
public class GenericHtmlParserTest extends HtmlParserTest {
|
||||
@Override
|
||||
protected String newHtmlBody(String changeMessage, String c1,
|
||||
String c2, String c3, String f1, String f2, String fc1) {
|
||||
String email = "" +
|
||||
"<div dir=\"ltr\">" + (changeMessage != null ? changeMessage : "") +
|
||||
"<div class=\"extra\"><br><div class=\"quote\">" +
|
||||
"On Fri, Nov 18, 2016 at 11:15 AM, foobar (Gerrit) noreply@gerrit.com" +
|
||||
"<span dir=\"ltr\"><<a href=\"mailto:noreply@gerrit.com\" " +
|
||||
"target=\"_blank\">noreply@gerrit.com</a>></span> wrote:<br>" +
|
||||
"<blockquote class=\"quote\" " +
|
||||
"<p>foobar <strong>posted comments</strong> on this change.</p>" +
|
||||
"<p><a href=\"" + changeURL + "/1\" " +
|
||||
"target=\"_blank\">View Change</a></p><div>Patch Set 2: CR-1\n" +
|
||||
"\n" +
|
||||
"(3 comments)</div><ul><li>" +
|
||||
"<p>" + // File #1: test.txt
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt\">" +
|
||||
"File gerrit-server/<wbr>test.txt:</a></p>" +
|
||||
commentBlock(f1) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt\">" +
|
||||
"Patch Set #2:</a> </p>" +
|
||||
"<blockquote><pre>Some inline comment from Gerrit</pre>" +
|
||||
"</blockquote><p>Some comment on file 1</p>" +
|
||||
"</li>" +
|
||||
commentBlock(fc1) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt@2\">" +
|
||||
"Patch Set #2, Line 31:</a> </p>" +
|
||||
"<blockquote><pre>Some inline comment from Gerrit</pre>" +
|
||||
"</blockquote><p>Some text from original comment</p>" +
|
||||
"</li>" +
|
||||
commentBlock(c1) +
|
||||
"" + // Inline comment #2
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt@3\">" +
|
||||
"Patch Set #2, Line 47:</a> </p>" +
|
||||
"<blockquote><pre>Some comment posted on Gerrit</pre>" +
|
||||
"</blockquote><p>Some more comments from Gerrit</p>" +
|
||||
"</li>" +
|
||||
commentBlock(c2) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt@115\">" +
|
||||
"Patch Set #2, Line 115:</a> <code>some code</code></p>" +
|
||||
"<p>some comment</p></li></ul></li>" +
|
||||
"" +
|
||||
"<li><p>" + // File #2: test.txt
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/readme.txt\">" +
|
||||
"File gerrit-server/<wbr>readme.txt:</a></p>" +
|
||||
commentBlock(f2) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/readme.txt@3\">" +
|
||||
"Patch Set #2, Line 31:</a> </p>" +
|
||||
"<blockquote><pre>Some inline comment from Gerrit</pre>" +
|
||||
"</blockquote><p>Some text from original comment</p>" +
|
||||
"</li>" +
|
||||
commentBlock(c3) +
|
||||
"" + // Inline comment #2
|
||||
"</ul></li></ul>" +
|
||||
"" + // Footer
|
||||
"<p>To view, visit <a href=\"" + changeURL + "/1\">this change</a>. " +
|
||||
"To unsubscribe, visit <a href=\"https://someurl\">settings</a>." +
|
||||
"</p><p>Gerrit-MessageType: comment<br>" +
|
||||
"Footer omitted</p>" +
|
||||
"<div><div></div></div>" +
|
||||
"<p>Gerrit-HasComments: Yes</p></blockquote></div><br></div></div>";
|
||||
return email;
|
||||
}
|
||||
|
||||
private static String commentBlock(String comment) {
|
||||
if (comment == null) {
|
||||
return "";
|
||||
}
|
||||
return "</ul></li></ul></blockquote><div>" + comment +
|
||||
"</div><blockquote class=\"quote\"><ul><li><ul>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
public class GmailHtmlParserTest extends HtmlParserTest {
|
||||
@Override
|
||||
protected String newHtmlBody(String changeMessage, String c1,
|
||||
String c2, String c3, String f1, String f2, String fc1) {
|
||||
String email = "" +
|
||||
"<div dir=\"ltr\">" + (changeMessage != null ? changeMessage : "") +
|
||||
"<div class=\"gmail_extra\"><br><div class=\"gmail_quote\">" +
|
||||
"On Fri, Nov 18, 2016 at 11:15 AM, foobar (Gerrit) noreply@gerrit.com" +
|
||||
"<span dir=\"ltr\"><<a href=\"mailto:noreply@gerrit.com\" " +
|
||||
"target=\"_blank\">noreply@gerrit.com</a>></span> wrote:<br>" +
|
||||
"<blockquote class=\"gmail_quote\" " +
|
||||
"<p>foobar <strong>posted comments</strong> on this change.</p>" +
|
||||
"<p><a href=\"" + changeURL + "/1\" " +
|
||||
"target=\"_blank\">View Change</a></p><div>Patch Set 2: CR-1\n" +
|
||||
"\n" +
|
||||
"(3 comments)</div><ul><li>" +
|
||||
"<p>" + // File #1: test.txt
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt\">" +
|
||||
"File gerrit-server/<wbr>test.txt:</a></p>" +
|
||||
commentBlock(f1) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt\">" +
|
||||
"Patch Set #2:</a> </p>" +
|
||||
"<blockquote><pre>Some inline comment from Gerrit</pre>" +
|
||||
"</blockquote><p>Some comment on file 1</p>" +
|
||||
"</li>" +
|
||||
commentBlock(fc1) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt@2\">" +
|
||||
"Patch Set #2, Line 31:</a> </p>" +
|
||||
"<blockquote><pre>Some inline comment from Gerrit</pre>" +
|
||||
"</blockquote><p>Some text from original comment</p>" +
|
||||
"</li>" +
|
||||
commentBlock(c1) +
|
||||
"" + // Inline comment #2
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt@3\">" +
|
||||
"Patch Set #2, Line 47:</a> </p>" +
|
||||
"<blockquote><pre>Some comment posted on Gerrit</pre>" +
|
||||
"</blockquote><p>Some more comments from Gerrit</p>" +
|
||||
"</li>" +
|
||||
commentBlock(c2) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/test.txt@115\">" +
|
||||
"Patch Set #2, Line 115:</a> <code>some code</code></p>" +
|
||||
"<p>some comment</p></li></ul></li>" +
|
||||
"" +
|
||||
"<li><p>" + // File #2: test.txt
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/readme.txt\">" +
|
||||
"File gerrit-server/<wbr>readme.txt:</a></p>" +
|
||||
commentBlock(f2) +
|
||||
"<li><p>" +
|
||||
"<a href=\"" + changeURL + "/1/gerrit-server/readme.txt@3\">" +
|
||||
"Patch Set #2, Line 31:</a> </p>" +
|
||||
"<blockquote><pre>Some inline comment from Gerrit</pre>" +
|
||||
"</blockquote><p>Some text from original comment</p>" +
|
||||
"</li>" +
|
||||
commentBlock(c3) +
|
||||
"" + // Inline comment #2
|
||||
"</ul></li></ul>" +
|
||||
"" + // Footer
|
||||
"<p>To view, visit <a href=\"" + changeURL + "/1\">this change</a>. " +
|
||||
"To unsubscribe, visit <a href=\"https://someurl\">settings</a>." +
|
||||
"</p><p>Gerrit-MessageType: comment<br>" +
|
||||
"Footer omitted</p>" +
|
||||
"<div><div></div></div>" +
|
||||
"<p>Gerrit-HasComments: Yes</p></blockquote></div><br></div></div>";
|
||||
return email;
|
||||
}
|
||||
|
||||
private static String commentBlock(String comment) {
|
||||
if (comment == null) {
|
||||
return "";
|
||||
}
|
||||
return "</ul></li></ul></blockquote><div>" + comment +
|
||||
"</div><blockquote class=\"gmail_quote\"><ul><li><ul>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Comment;
|
||||
import java.util.List;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@Ignore
|
||||
public abstract class HtmlParserTest extends AbstractParserTest {
|
||||
@Test
|
||||
public void simpleChangeMessage() {
|
||||
MailMessage.Builder b = newMailMessageBuilder();
|
||||
b.htmlContent(newHtmlBody("Looks good to me", null, null,
|
||||
null, null, null, null));
|
||||
|
||||
List<Comment> comments = defaultComments();
|
||||
List<MailComment> parsedComments =
|
||||
HtmlParser.parse(b.build(), comments, "");
|
||||
|
||||
assertThat(parsedComments).hasSize(1);
|
||||
assertChangeMessage("Looks good to me", parsedComments.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleInlineComments() {
|
||||
MailMessage.Builder b = newMailMessageBuilder();
|
||||
b.htmlContent(newHtmlBody("Looks good to me",
|
||||
"I have a comment on this.", null, "Also have a comment here.",
|
||||
null, null, null));
|
||||
|
||||
List<Comment> comments = defaultComments();
|
||||
List<MailComment> parsedComments =
|
||||
HtmlParser.parse(b.build(), comments, changeURL);
|
||||
|
||||
assertThat(parsedComments).hasSize(3);
|
||||
assertChangeMessage("Looks good to me", parsedComments.get(0));
|
||||
assertInlineComment("I have a comment on this.", parsedComments.get(1),
|
||||
comments.get(1));
|
||||
assertInlineComment("Also have a comment here.", parsedComments.get(2),
|
||||
comments.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleFileComment() {
|
||||
MailMessage.Builder b = newMailMessageBuilder();
|
||||
b.htmlContent(newHtmlBody("Looks good to me",
|
||||
null, null, "Also have a comment here.",
|
||||
"This is a nice file", null, null));
|
||||
|
||||
List<Comment> comments = defaultComments();
|
||||
List<MailComment> parsedComments =
|
||||
HtmlParser.parse(b.build(), comments, changeURL);
|
||||
|
||||
assertThat(parsedComments).hasSize(3);
|
||||
assertChangeMessage("Looks good to me", parsedComments.get(0));
|
||||
assertFileComment("This is a nice file", parsedComments.get(1),
|
||||
comments.get(1).key.filename);
|
||||
assertInlineComment("Also have a comment here.", parsedComments.get(2),
|
||||
comments.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noComments() {
|
||||
MailMessage.Builder b = newMailMessageBuilder();
|
||||
b.htmlContent(newHtmlBody(null, null, null, null, null, null, null));
|
||||
|
||||
List<Comment> comments = defaultComments();
|
||||
List<MailComment> parsedComments =
|
||||
HtmlParser.parse(b.build(), comments, changeURL);
|
||||
|
||||
assertThat(parsedComments).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noChangeMessage() {
|
||||
MailMessage.Builder b = newMailMessageBuilder();
|
||||
b.htmlContent(newHtmlBody(null, null, null,
|
||||
"Also have a comment here.", "This is a nice file", null, null));
|
||||
|
||||
List<Comment> comments = defaultComments();
|
||||
List<MailComment> parsedComments =
|
||||
HtmlParser.parse(b.build(), comments, changeURL);
|
||||
|
||||
assertThat(parsedComments).hasSize(2);
|
||||
assertFileComment("This is a nice file", parsedComments.get(0),
|
||||
comments.get(1).key.filename);
|
||||
assertInlineComment("Also have a comment here.", parsedComments.get(1),
|
||||
comments.get(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an html message body with the specified comments.
|
||||
*
|
||||
* @param changeMessage
|
||||
* @param c1 Comment in reply to first comment.
|
||||
* @param c2 Comment in reply to second comment.
|
||||
* @param c3 Comment in reply to third comment.
|
||||
* @param f1 Comment on file one.
|
||||
* @param f2 Comment on file two.
|
||||
* @param fc1 Comment in reply to a comment on file 1.
|
||||
* @return A string with all inline comments and the original quoted email.
|
||||
*/
|
||||
protected abstract String newHtmlBody(String changeMessage, String c1,
|
||||
String c2, String c3, String f1, String f2, String fc1);
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import com.google.gerrit.reviewdb.client.Comment;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TextParserTest extends AbstractParserTest {
|
||||
@@ -34,8 +33,6 @@ public class TextParserTest extends AbstractParserTest {
|
||||
"> Gerrit-Branch: master\n" +
|
||||
"> Gerrit-Owner: Foo Bar <foo@bar.com>\n" +
|
||||
"> Gerrit-HasComments: Yes";
|
||||
private static final String changeURL =
|
||||
"https://gerrit-review.googlesource.com/#/changes/123";
|
||||
|
||||
@Test
|
||||
public void simpleChangeMessage() {
|
||||
@@ -218,13 +215,4 @@ public class TextParserTest extends AbstractParserTest {
|
||||
"> Should this be EEE like in other places?\n" +
|
||||
(c3 == null ? "" : c3 + "\n");
|
||||
}
|
||||
|
||||
private List<Comment> defaultComments() {
|
||||
List<Comment> comments = new ArrayList<>();
|
||||
comments.add(newComment("c1", "gerrit-server/test.txt", "comment", 0));
|
||||
comments.add(newComment("c2", "gerrit-server/test.txt", "comment", 2));
|
||||
comments.add(newComment("c3", "gerrit-server/test.txt", "comment", 3));
|
||||
comments.add(newComment("c4", "gerrit-server/readme.txt", "comment", 3));
|
||||
return comments;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user