From d9e7cb020409491b45199ab8762eb22746e3543d Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 16 Apr 2019 15:18:55 +0200 Subject: [PATCH] SONAR-11757 add HtmlFragmentAssert and MimeMessageAssert --- build.gradle | 1 + sonar-testing-harness/build.gradle | 4 +- .../org/sonar/test/html/HtmlBlockAssert.java | 130 +++++++++++ .../sonar/test/html/HtmlFragmentAssert.java | 78 +++++++ .../org/sonar/test/html/HtmlListAssert.java | 130 +++++++++++ .../sonar/test/html/HtmlParagraphAssert.java | 220 ++++++++++++++++++ .../sonar/test/html/MimeMessageAssert.java | 89 +++++++ .../org/sonar/test/html/package-info.java | 23 ++ 8 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlBlockAssert.java create mode 100644 sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlFragmentAssert.java create mode 100644 sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlListAssert.java create mode 100644 sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlParagraphAssert.java create mode 100644 sonar-testing-harness/src/main/java/org/sonar/test/html/MimeMessageAssert.java create mode 100644 sonar-testing-harness/src/main/java/org/sonar/test/html/package-info.java diff --git a/build.gradle b/build.gradle index cbf8d64c974..d5e14b5c68c 100644 --- a/build.gradle +++ b/build.gradle @@ -144,6 +144,7 @@ subprojects { dependency 'com.tngtech.java:junit-dataprovider:1.9.2' dependency 'info.picocli:picocli:3.6.1' dependency 'io.jsonwebtoken:jjwt:0.9.0' + dependency 'javax.mail:mail:1.4.4' dependency 'javax.servlet:javax.servlet-api:3.0.1' dependency 'javax.xml.bind:jaxb-api:2.3.0' dependency 'junit:junit:4.12' diff --git a/sonar-testing-harness/build.gradle b/sonar-testing-harness/build.gradle index a8bc3f4290b..c5001fbcfbd 100644 --- a/sonar-testing-harness/build.gradle +++ b/sonar-testing-harness/build.gradle @@ -10,12 +10,14 @@ dependencies { compile 'com.google.code.gson:gson' compile 'com.googlecode.json-simple:json-simple' compile 'commons-io:commons-io' + compile 'javax.mail:mail' compile 'junit:junit' + compile 'org.assertj:assertj-core' compile 'org.hamcrest:hamcrest-all' + compile 'org.jsoup:jsoup:1.11.3' compileOnly 'com.google.code.findbugs:jsr305' - testCompile 'org.assertj:assertj-core' } artifactoryPublish.skip = false diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlBlockAssert.java b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlBlockAssert.java new file mode 100644 index 00000000000..c0471077e18 --- /dev/null +++ b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlBlockAssert.java @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.test.html; + +import java.util.stream.Collectors; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import static org.assertj.core.util.Preconditions.checkArgument; + +public abstract class HtmlBlockAssert> extends AbstractAssert { + static final String PRINT_FRAGMENT_TEMPLATE = "\n---fragment---\n%s\n---fragment---"; + private static final String NO_LINK_IN_BLOC = "no link in bloc"; + + public HtmlBlockAssert(Element v, Class selfType) { + super(v, selfType); + } + + /** + * Verifies the current block contains a single link with the specified piece of text. + */ + public T withLinkOn(String linkText) { + return withLinkOn(linkText, 1); + } + + /** + * Verifies the current block contains {@code times} links with the specified piece of text. + */ + public T withLinkOn(String linkText, int times) { + checkArgument(times >= 1, "times must be >= 1"); + + isNotNull(); + + Elements as = actual.select("a"); + Assertions.assertThat(as) + .describedAs(NO_LINK_IN_BLOC + PRINT_FRAGMENT_TEMPLATE, actual) + .isNotEmpty(); + + long count = as.stream().filter(t -> linkText.equals(t.text())).count(); + if (count != times) { + failWithMessage("link on text \"%s\" found %s times in bloc (expected %s). \n Got: %s", linkText, count, times, asyncToString(as)); + } + + return myself; + } + + /** + * Verifies the current block contains a link with the specified text and href. + */ + public T withLink(String linkText, String href) { + isNotNull(); + + Elements as = actual.select("a"); + Assertions.assertThat(as) + .describedAs(NO_LINK_IN_BLOC) + .isNotEmpty(); + + if (as.stream().noneMatch(t -> linkText.equals(t.text()) && href.equals(t.attr("href")))) { + failWithMessage( + "link with text \"%s\" and href \"%s\" not found in block. \n Got: %s", + linkText, href, asyncToString(as)); + } + + return myself; + } + + public T withoutLink() { + isNotNull(); + + Assertions.assertThat(actual.select("a")).isEmpty(); + + return myself; + } + + private static Object asyncToString(Elements as) { + return new Object() { + @Override + public String toString() { + return as.stream() + .map(a -> "< href=\"" + a.attr("href") + "\">" + a.text() + "") + .collect(Collectors.joining("\n")); + } + }; + } + + public T withEmphasisOn(String emphasisText) { + isNotNull(); + + Elements emphases = actual.select("em"); + Assertions.assertThat(emphases) + .describedAs("no in block") + .isNotEmpty(); + Assertions.assertThat(emphases.stream().map(Element::text)) + .contains(emphasisText); + + return myself; + } + + public T withSmallOn(String emphasisText) { + isNotNull(); + + Elements smalls = actual.select("small"); + Assertions.assertThat(smalls) + .describedAs("no in block") + .isNotEmpty(); + Assertions.assertThat(smalls.stream().map(Element::text)) + .contains(emphasisText); + + return myself; + } +} diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlFragmentAssert.java b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlFragmentAssert.java new file mode 100644 index 00000000000..3054112afb5 --- /dev/null +++ b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlFragmentAssert.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.test.html; + +import java.util.Iterator; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import static java.util.stream.Collectors.toList; +import static org.sonar.test.html.HtmlParagraphAssert.verifyIsParagraph; + +public class HtmlFragmentAssert extends AbstractAssert { + + public HtmlFragmentAssert(String s) { + super(s, HtmlFragmentAssert.class); + } + + public static HtmlFragmentAssert assertThat(String s) { + return new HtmlFragmentAssert(s); + } + + public HtmlParagraphAssert hasParagraph() { + isNotNull(); + + Document document = Jsoup.parseBodyFragment(actual); + Iterator blockIt = document.body().children().stream() + .filter(Element::isBlock) + .collect(toList()) + .iterator(); + Assertions.assertThat(blockIt.hasNext()) + .describedAs("no bloc in fragment") + .isTrue(); + + Element firstBlock = blockIt.next(); + verifyIsParagraph(firstBlock); + + return new HtmlParagraphAssert(firstBlock, blockIt); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withText(text)}. + */ + public HtmlParagraphAssert hasParagraph(String text) { + return hasParagraph() + .withText(text); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withLines(line1, line2, ...)}. + */ + public HtmlParagraphAssert hasParagraph(String firstLine, String... otherLines) { + return hasParagraph() + .withLines(firstLine, otherLines); + } + +} diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlListAssert.java b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlListAssert.java new file mode 100644 index 00000000000..87de54c2465 --- /dev/null +++ b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlListAssert.java @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.test.html; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.jsoup.nodes.Element; + +public class HtmlListAssert extends HtmlBlockAssert { + private final Iterator nextBlocks; + + public HtmlListAssert(Element list, Iterator nextBlocks) { + super(list, HtmlListAssert.class); + this.nextBlocks = nextBlocks; + } + + static void verifyIsList(Element element) { + Assertions.assertThat(element.tagName()) + .describedAs( + "next block is neither a <%s> nor a <%s> (got <%s>):" + PRINT_FRAGMENT_TEMPLATE, + "ul", "ol", element.tagName(), element.toString()) + .isIn("ul", "ol"); + } + + /** + * Verifies the text of every items in the current list is equal to the specified strings, in order. + */ + public HtmlListAssert withItemTexts(String firstItemText, String... otherItemsText) { + isNotNull(); + + List itemsText = actual.children() + .stream() + .filter(t -> t.tagName().equals("li")) + .map(Element::text) + .collect(Collectors.toList()); + + String[] itemTexts = Stream.concat( + Stream.of(firstItemText), + Arrays.stream(otherItemsText)) + .toArray(String[]::new); + Assertions.assertThat(itemsText) + .describedAs(PRINT_FRAGMENT_TEMPLATE, actual) + .containsOnly(itemTexts); + + return this; + } + + public HtmlListAssert hasList() { + isNotNull(); + + Assertions.assertThat(nextBlocks.hasNext()) + .describedAs("no more block") + .isTrue(); + + Element element = nextBlocks.next(); + verifyIsList(element); + + return new HtmlListAssert(element, nextBlocks); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withText(text)}. + */ + public HtmlParagraphAssert hasParagraph(String text) { + return hasParagraph() + .withText(text); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withText("")}. + */ + public HtmlParagraphAssert hasEmptyParagraph() { + return hasParagraph() + .withText(""); + } + + public HtmlParagraphAssert hasParagraph() { + isNotNull(); + + Assertions.assertThat(nextBlocks.hasNext()) + .describedAs("no more block") + .isTrue(); + + Element element = nextBlocks.next(); + HtmlParagraphAssert.verifyIsParagraph(element); + + return new HtmlParagraphAssert(element, nextBlocks); + } + + /** + * Verifies there is no more list in the block. + */ + public void noMoreBlock() { + isNotNull(); + + Assertions.assertThat(nextBlocks.hasNext()) + .describedAs("there are still some block. Next one:" + PRINT_FRAGMENT_TEMPLATE, + new Object() { + @Override + public String toString() { + return nextBlocks.next().toString(); + } + }) + .isFalse(); + } + +} diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlParagraphAssert.java b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlParagraphAssert.java new file mode 100644 index 00000000000..bf7210afa33 --- /dev/null +++ b/sonar-testing-harness/src/main/java/org/sonar/test/html/HtmlParagraphAssert.java @@ -0,0 +1,220 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.test.html; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; + +import static java.util.Collections.emptyList; + +public class HtmlParagraphAssert extends HtmlBlockAssert { + private final Iterator nextBlocks; + + public HtmlParagraphAssert(Element paragraph, Iterator nextBlocks) { + super(paragraph, HtmlParagraphAssert.class); + this.nextBlocks = nextBlocks; + } + + static void verifyIsParagraph(Element element) { + Assertions.assertThat(element.tagName()) + .describedAs( + "next block is not a <%s> (got <%s>):" + PRINT_FRAGMENT_TEMPLATE, + "p", element.tagName(), element.toString()) + .isEqualTo("p"); + } + + /** + * Verify the next block exists, is a paragraph and returns an Assert on this block. + */ + public HtmlParagraphAssert hasParagraph() { + isNotNull(); + + Assertions.assertThat(nextBlocks.hasNext()) + .describedAs("no more bloc") + .isTrue(); + + Element element = nextBlocks.next(); + verifyIsParagraph(element); + + return new HtmlParagraphAssert(element, nextBlocks); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withText(text)}. + */ + public HtmlParagraphAssert hasParagraph(String text) { + return hasParagraph() + .withText(text); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withText("")}. + */ + public HtmlParagraphAssert hasEmptyParagraph() { + return hasParagraph() + .withText(""); + } + + /** + * Convenience method. + * Sames as {@code hasParagraph().withLines(line1, line2, ...)}. + */ + public HtmlParagraphAssert hasParagraph(String firstLine, String... otherLines) { + return hasParagraph() + .withLines(firstLine, otherLines); + } + + /** + * Verifies there is no more block. + */ + public void noMoreBlock() { + isNotNull(); + + Assertions.assertThat(nextBlocks.hasNext()) + .describedAs("there are still some paragraph. Next one:" + PRINT_FRAGMENT_TEMPLATE, + new Object() { + @Override + public String toString() { + return nextBlocks.next().toString(); + } + }) + .isFalse(); + } + + /** + * Verifies the current block as the specified text, ignoring lines. + */ + public HtmlParagraphAssert withText(String text) { + isNotNull(); + + Assertions.assertThat(actual.text()) + .describedAs(PRINT_FRAGMENT_TEMPLATE, actual) + .isEqualTo(text); + + return this; + } + + /** + * Verifies the current block has all and only the specified lines, in order. + */ + public HtmlParagraphAssert withLines(String firstLine, String... otherLines) { + isNotNull(); + + List actualLines = toLines(actual); + String[] expectedLines = Stream.concat( + Stream.of(firstLine), + Arrays.stream(otherLines)) + .toArray(String[]::new); + + Assertions.assertThat(actualLines) + .describedAs(PRINT_FRAGMENT_TEMPLATE, actual) + .containsExactly(expectedLines); + + return this; + } + + /** + * Verifies the current block has all and only the specified lines, in any order. + */ + public HtmlParagraphAssert withLines(Set lines) { + isNotNull(); + + List actualLines = toLines(actual); + String[] expectedLines = lines.toArray(new String[0]); + + Assertions.assertThat(actualLines) + .describedAs(PRINT_FRAGMENT_TEMPLATE, actual) + .containsOnly(expectedLines); + + return this; + } + + private static List toLines(Element parent) { + Iterator iterator = parent.childNodes().iterator(); + if (!iterator.hasNext()) { + return emptyList(); + } + + List actualLines = new ArrayList<>(parent.childNodeSize()); + StringBuilder currentLine = null; + while (iterator.hasNext()) { + Node node = iterator.next(); + if (node instanceof TextNode) { + if (currentLine == null) { + currentLine = new StringBuilder(node.toString()); + } else { + currentLine.append(node.toString()); + } + } else if (node instanceof Element) { + Element element = (Element) node; + if (element.tagName().equals("br")) { + actualLines.add(currentLine == null ? "" : currentLine.toString()); + currentLine = null; + } else { + if (currentLine == null) { + currentLine = new StringBuilder(element.text()); + } else { + currentLine.append(element.text()); + } + } + } else { + throw new IllegalStateException("unsupported node " + node.getClass()); + } + + if (!iterator.hasNext()) { + actualLines.add(currentLine == null ? "" : currentLine.toString()); + currentLine = null; + } + } + return actualLines; + } + + /** + * Convenience method. + * Same as {@code hasList().withItemTexts("foo", "bar")}. + */ + public HtmlListAssert hasList(String firstItemText, String... otherItemsText) { + return hasList() + .withItemTexts(firstItemText, otherItemsText); + } + + public HtmlListAssert hasList() { + isNotNull(); + + Assertions.assertThat(nextBlocks.hasNext()) + .describedAs("no more block") + .isTrue(); + + Element element = nextBlocks.next(); + HtmlListAssert.verifyIsList(element); + + return new HtmlListAssert(element, nextBlocks); + } +} diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/html/MimeMessageAssert.java b/sonar-testing-harness/src/main/java/org/sonar/test/html/MimeMessageAssert.java new file mode 100644 index 00000000000..faa654b562a --- /dev/null +++ b/sonar-testing-harness/src/main/java/org/sonar/test/html/MimeMessageAssert.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.test.html; + +import java.io.IOException; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; + +public final class MimeMessageAssert extends AbstractAssert { + + public MimeMessageAssert(MimeMessage mimeMessage) { + super(mimeMessage, MimeMessageAssert.class); + } + + public static MimeMessageAssert assertThat(MimeMessage m) { + return new MimeMessageAssert(m); + } + + public HtmlFragmentAssert isHtml() { + isNotNull(); + + try { + Object content = actual.getContent(); + Assertions.assertThat(content).isInstanceOf(MimeMultipart.class); + MimeMultipart m = (MimeMultipart) content; + Assertions.assertThat(m.getCount()).isEqualTo(1); + return new HtmlFragmentAssert((String) m.getBodyPart(0).getContent()); + } catch (MessagingException | IOException e) { + throw new IllegalStateException(e); + } + } + + public MimeMessageAssert hasRecipient(String userEmail) { + isNotNull(); + + try { + Assertions.assertThat(actual.getHeader("To", null)).isEqualTo(String.format("<%s>", userEmail)); + } catch (MessagingException e) { + throw new IllegalStateException(e); + } + + return this; + } + + public MimeMessageAssert hasSubject(String text) { + isNotNull(); + + try { + Assertions.assertThat(actual.getSubject()).isEqualTo(text); + } catch (MessagingException e) { + throw new IllegalStateException(e); + } + + return this; + } + + public MimeMessageAssert subjectContains(String text) { + isNotNull(); + + try { + Assertions.assertThat(actual.getSubject()).contains(text); + } catch (MessagingException e) { + throw new IllegalStateException(e); + } + + return this; + } + +} diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/html/package-info.java b/sonar-testing-harness/src/main/java/org/sonar/test/html/package-info.java new file mode 100644 index 00000000000..6bed9071e0e --- /dev/null +++ b/sonar-testing-harness/src/main/java/org/sonar/test/html/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.test.html; + +import javax.annotation.ParametersAreNonnullByDefault; -- 2.39.5