diff --git a/pom.xml b/pom.xml index dd8c46d6e9b..0a8ec4b88e7 100644 --- a/pom.xml +++ b/pom.xml @@ -511,7 +511,7 @@ org.jsoup jsoup - 1.15.3 + 1.23.1 io.searchbox diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItemsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItemsIT.java index d7dc47bccea..d4ce246ba5a 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItemsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/DataverseFeaturedItemsIT.java @@ -9,6 +9,7 @@ import java.text.MessageFormat; +import static edu.harvard.iq.dataverse.util.MarkupCheckerUtil.htmlEqualTo; import static jakarta.ws.rs.core.Response.Status.*; import java.io.File; import java.io.IOException; @@ -408,7 +409,7 @@ private Long createFeaturedItemAndGetId(String dataverseAlias, String apiToken, private void verifyUpdatedFeaturedItem(Response response, String expectedContent, String expectedImageFileName, int expectedDisplayOrder, String type, String dvObject, String dvObjectDisplayName) { response.prettyPrint(); response.then().assertThat() - .body("data.content", equalTo(expectedContent)) + .body("data.content", htmlEqualTo(expectedContent)) .body("data.imageFileName", equalTo(expectedImageFileName)) .body("data.displayOrder", equalTo(expectedDisplayOrder)) .body("data.type", equalTo(type)) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java index 410a72a1054..1c1a9756360 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/SearchIT.java @@ -994,6 +994,7 @@ public void testSubtreePermissions() { // Wait a little while for the index to pick up the datasets, otherwise timing issue with searching for it. UtilIT.sleepForDatasetIndex(datasetId2.toString(), apiToken); + UtilIT.sleepForDatasetIndex(datasetId.toString(), apiToken); String identifier = JsonPath.from(datasetAsJson.getBody().asString()).getString("data.identifier"); String identifier2 = JsonPath.from(datasetAsJson2.getBody().asString()).getString("data.identifier"); diff --git a/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerTest.java b/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerTest.java index 02219059db3..d1527734ab3 100644 --- a/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerTest.java @@ -1,10 +1,12 @@ package edu.harvard.iq.dataverse.util; +import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; public class MarkupCheckerTest { @@ -28,7 +30,12 @@ public class MarkupCheckerTest { "NULL, NULL" }, nullValues = {"NULL"}) public void testSanitizeBasicHTML(String unsafe, String safe) { - assertEquals(safe, MarkupChecker.sanitizeBasicHTML(unsafe)); + String actual = MarkupChecker.sanitizeBasicHTML(unsafe); + MarkupCheckerUtil.assertHtmlEqual(safe, actual); + // Sanity check that the key tag we strip is truly gone + if(actual != null) { + assertFalse(actual.toLowerCase().contains("script")); + } } /** @@ -51,21 +58,12 @@ public void testSanitizeBasicHTML(String unsafe, String safe) { "NULL, NULL" }, nullValues = {"NULL"}) public void testSanitizeAdvancedHTML(String unsafe, String safe) { - String sanitizedOutput = MarkupChecker.sanitizeAdvancedHTML(unsafe); - - // Normalize both the expected and actual content by removing whitespaces - - String normalizedSafe = null; - if (safe != null) { - normalizedSafe = safe.replaceAll("\\s+", "").trim(); + String actual = MarkupChecker.sanitizeAdvancedHTML(unsafe); + MarkupCheckerUtil.assertHtmlEqual(safe, actual); + // Sanity check that the key tag we strip is truly gone + if(actual != null) { + assertFalse(actual.toLowerCase().contains("script")); } - - String normalizedOutput = null; - if (sanitizedOutput != null) { - normalizedOutput = sanitizedOutput.replaceAll("\\s+", "").trim(); - } - - assertEquals(normalizedSafe, normalizedOutput); } /** @@ -90,5 +88,4 @@ public void testStripAllTags(String unsafe, String safe) { public void testEscapeHtml() { assertEquals("foo<br>bar", MarkupChecker.escapeHtml("foo
bar")); } - } diff --git a/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerUtil.java b/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerUtil.java new file mode 100644 index 00000000000..c28c983618f --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/util/MarkupCheckerUtil.java @@ -0,0 +1,97 @@ +package edu.harvard.iq.dataverse.util; + +import org.jsoup.Jsoup; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MarkupCheckerUtil { + + /** Test HTML equivalence and ignore differences in the order of attributes + * This does (in normalizeHtml()) use Jsoup to help test Jsoup though. + * + * @param expected - the HTML we expect to be returned + * @param actual - the HTML we actually got + */ + public static void assertHtmlEqual(String expected, String actual) { + if (expected == null || actual == null) { + assertEquals(expected, actual); + return; + } + + String normalizedExpected = normalizeHtml(expected); + String normalizedActual = normalizeHtml(actual); + assertEquals(normalizedExpected, normalizedActual); + } + + /** + * Creates a Hamcrest matcher that compares HTML after normalization, ignoring + * differences such as attribute ordering and whitespace. + * + * @param expected the expected HTML + * @return a matcher for HTML-equivalent strings + */ + public static org.hamcrest.Matcher htmlEqualTo(String expected) { + return new org.hamcrest.BaseMatcher() { + + @Override + public boolean matches(Object actual) { + if (expected == null || actual == null) { + return expected == actual; + } + + if (!(actual instanceof String actualHtml)) { + return false; + } + + return normalizeHtml(expected).equals(normalizeHtml(actualHtml)); + } + + @Override + public void describeTo(org.hamcrest.Description description) { + description.appendText("HTML equal to ") + .appendValue(expected); + + if (expected != null) { + description.appendText(" after normalization as ") + .appendValue(normalizeHtml(expected)); + } + } + + @Override + public void describeMismatch(Object actual, org.hamcrest.Description description) { + if (actual == null) { + description.appendText("was null"); + return; + } + + if (!(actual instanceof String actualHtml)) { + description.appendText("was ") + .appendValue(actual); + return; + } + + description.appendText("was ") + .appendValue(actualHtml) + .appendText(" after normalization as ") + .appendValue(normalizeHtml(actualHtml)); + } + }; + } + + private static String normalizeHtml(String html) { + org.jsoup.nodes.Document doc = Jsoup.parseBodyFragment(html); + for (org.jsoup.nodes.Element el : doc.getAllElements()) { + org.jsoup.nodes.Attributes attrs = el.attributes(); + java.util.List list = new java.util.ArrayList<>(attrs.asList()); + list.sort(java.util.Map.Entry.comparingByKey()); + for (org.jsoup.nodes.Attribute a : list) { + attrs.remove(a.getKey()); + } + for (org.jsoup.nodes.Attribute a : list) { + attrs.put(a); + } + } + return doc.body().html().replaceAll("\\s+", ""); + } +}