package net.pterodactylus.sone.text;
+import static com.google.common.collect.ImmutableList.builder;
+import static java.util.Arrays.asList;
+import static java.util.UUID.randomUUID;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
import java.io.IOException;
import java.io.StringReader;
-import java.util.Arrays;
+import java.util.Collection;
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
+import net.pterodactylus.sone.data.Mocks;
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.data.impl.DefaultSone;
-import junit.framework.TestCase;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.hamcrest.collection.IsIterableContainingInOrder;
+import org.junit.Test;
/**
* JUnit test case for {@link SoneTextParser}.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
-public class SoneTextParserTest extends TestCase {
-
- //
- // ACTIONS
- //
-
- /**
- * Tests basic plain-text operation of the parser.
- *
- * @throws IOException
- * if an I/O error occurs
- */
- @SuppressWarnings("static-method")
- public void testPlainText() throws IOException {
- SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null));
- Iterable<Part> parts;
-
- /* check basic operation. */
- parts = soneTextParser.parse(null, new StringReader("Test."));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class));
-
- /* check empty lines at start and end. */
- parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n"));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class));
-
- /* check duplicate empty lines in the text. */
- parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n\nTest."));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Test.\n\nTest.", convertText(parts, PlainTextPart.class));
- }
-
- /**
- * Tests parsing of KSK links.
- *
- * @throws IOException
- * if an I/O error occurs
- */
- @SuppressWarnings("static-method")
- public void testKSKLinks() throws IOException {
- SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null));
- Iterable<Part> parts;
-
- /* check basic links. */
- parts = soneTextParser.parse(null, new StringReader("KSK@gpl.txt"));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "[KSK@gpl.txt|gpl.txt|gpl.txt]", convertText(parts, FreenetLinkPart.class));
-
- /* check embedded links. */
- parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\u200b."));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\u200b.", convertText(parts, PlainTextPart.class, FreenetLinkPart.class));
-
- /* check embedded links and line breaks. */
- parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\nKSK@test.dat\n"));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\n[KSK@test.dat|test.dat|test.dat]", convertText(parts, PlainTextPart.class, FreenetLinkPart.class));
- }
-
- /**
- * Test case for a bug that was discovered in 0.6.7.
- *
- * @throws IOException
- * if an I/O error occurs
- */
- @SuppressWarnings({ "synthetic-access", "static-method" })
- public void testEmptyLinesAndSoneLinks() throws IOException {
- SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null));
- Iterable<Part> parts;
-
- /* check basic links. */
- parts = soneTextParser.parse(null, new StringReader("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff."));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff.", convertText(parts, PlainTextPart.class, SonePart.class));
- }
-
- /**
- * Test for a bug discovered in Sone 0.8.4 where a plain “http://” would be
- * parsed into a link.
- *
- * @throws IOException
- * if an I/O error occurs
- */
- @SuppressWarnings({ "synthetic-access", "static-method" })
- public void testEmpyHttpLinks() throws IOException {
- SoneTextParser soneTextParser = new SoneTextParser(new MemoryDatabase(null));
- Iterable<Part> parts;
-
- /* check empty http links. */
- parts = soneTextParser.parse(null, new StringReader("Some text. Empty link: http:// – nice!"));
- assertNotNull("Parts", parts);
- assertEquals("Part Text", "Some text. Empty link: http:// – nice!", convertText(parts, PlainTextPart.class));
- }
-
- //
- // PRIVATE METHODS
- //
-
- /**
- * Converts all given {@link Part}s into a string, validating that the
- * part’s classes match only the expected classes.
- *
- * @param parts
- * The parts to convert to text
- * @param validClasses
- * The valid classes; if no classes are given, all classes are
- * valid
- * @return The converted text
- */
- private static String convertText(Iterable<Part> parts, Class<?>... validClasses) {
- StringBuilder text = new StringBuilder();
- for (Part part : parts) {
- assertNotNull("Part", part);
- boolean classValid = validClasses.length == 0;
- for (Class<?> validClass : validClasses) {
- if (validClass.isAssignableFrom(part.getClass())) {
- classValid = true;
- break;
+public class SoneTextParserTest {
+
+ private final Mocks mocks = new Mocks();
+ private final SoneTextParser soneTextParser = new SoneTextParser(mocks.database);
+
+ private Matcher<Iterable<Part>> matches(final Matcher<? extends Part>... partsToMatch) {
+ return new TypeSafeMatcher<Iterable<Part>>() {
+
+ private Matcher<Iterable<? extends Part>> iterableMatcher;
+
+ @Override
+ protected boolean matchesSafely(Iterable<Part> parts) {
+ iterableMatcher = new IsIterableContainingInOrder(asList(partsToMatch));
+ return iterableMatcher.matches(collapseParts(expandParts(parts)));
+ }
+
+ private Iterable<Part> expandParts(Iterable<? extends Part> parts) {
+ PartContainer partContainer = new PartContainer();
+ for (Part part : parts) {
+ partContainer.add(part);
+ }
+ return partContainer;
+ }
+
+ private Collection<Part> collapseParts(Iterable<? extends Part> parts) {
+ ImmutableList.Builder<Part> collapsedPartsBuilder = builder();
+ PlainTextPart lastPlainTextPart = null;
+ for (Part part : parts) {
+ if (part instanceof PlainTextPart) {
+ if (lastPlainTextPart != null) {
+ lastPlainTextPart = new PlainTextPart(lastPlainTextPart.getText() + ((PlainTextPart) part).getText());
+ } else {
+ lastPlainTextPart = (PlainTextPart) part;
+ }
+ } else {
+ if (lastPlainTextPart != null) {
+ collapsedPartsBuilder.add(lastPlainTextPart);
+ lastPlainTextPart = null;
+ }
+ collapsedPartsBuilder.add(part);
+ }
+ }
+ if (lastPlainTextPart != null) {
+ collapsedPartsBuilder.add(lastPlainTextPart);
}
+ return collapsedPartsBuilder.build();
}
- if (!classValid) {
- fail("Part’s Class (" + part.getClass() + ") is not one of " + Arrays.toString(validClasses));
+
+ @Override
+ protected void describeMismatchSafely(Iterable<Part> parts, Description mismatchDescription) {
+ iterableMatcher.describeMismatch(collapseParts(parts), mismatchDescription);
}
- if (part instanceof PlainTextPart) {
- text.append(((PlainTextPart) part).getText());
- } else if (part instanceof FreenetLinkPart) {
- FreenetLinkPart freenetLinkPart = (FreenetLinkPart) part;
- text.append('[').append(freenetLinkPart.getLink()).append('|').append(freenetLinkPart.isTrusted() ? "trusted|" : "").append(freenetLinkPart.getTitle()).append('|').append(freenetLinkPart.getText()).append(']');
- } else if (part instanceof LinkPart) {
- LinkPart linkPart = (LinkPart) part;
- text.append('[').append(linkPart.getLink()).append('|').append(linkPart.getTitle()).append('|').append(linkPart.getText()).append(']');
- } else if (part instanceof SonePart) {
- SonePart sonePart = (SonePart) part;
- text.append("[Sone|").append(sonePart.getSone().getId()).append(']');
+
+ @Override
+ public void describeTo(Description description) {
+ iterableMatcher.describeTo(description);
}
- }
- return text.toString();
+ };
+ }
+
+ private Iterable<Part> parse(String text) throws IOException {
+ return soneTextParser.parse(null, new StringReader(text));
+ }
+
+ private Iterable<Part> parse(SoneTextParserContext context, String text) throws IOException {
+ return soneTextParser.parse(context, new StringReader(text));
+ }
+
+ @Test
+ public void parsePlainText() throws IOException {
+ assertThat(parse("Test."), matches(is(new PlainTextPart("Test."))));
+ }
+
+ @Test
+ public void parsePlainTextWithEmptyLinesAtTheBeginningAndEnd() throws IOException {
+ assertThat(parse("\nTest.\n\n"), matches(is(new PlainTextPart("Test."))));
+ }
+
+ @Test
+ public void parsePlainTextAndCollapseMultipleEmptyLines() throws IOException {
+ assertThat(parse("\nTest.\n\n\nTest."), matches(is(new PlainTextPart("Test.\n\nTest."))));
+ }
+
+ @Test
+ public void parseSimpleKskLinks() throws IOException {
+ assertThat(parse("KSK@gpl.txt"), matches(is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false))));
+ }
+
+ @Test
+ public void parseEmbeddedLinks() throws IOException {
+ assertThat(parse("Link is KSK@gpl.txt\u200b."), matches(
+ is(new PlainTextPart("Link is ")),
+ is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)),
+ is(new PlainTextPart("\u200b."))
+ ));
+ }
+
+ @Test
+ public void parseEmbeddedLinksAndLineBreaks() throws IOException {
+ assertThat(parse("Link is KSK@gpl.txt\nKSK@test.dat\n"), matches(
+ is(new PlainTextPart("Link is ")),
+ is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)),
+ is(new PlainTextPart("\n")),
+ is(new FreenetLinkPart("KSK@test.dat", "test.dat", false))
+ ));
+ }
+
+ @Test
+ public void parseEmptyLinesAndSoneLinks() throws IOException {
+ Sone sone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create();
+ assertThat(parse("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff."), matches(
+ is(new PlainTextPart("Some text.\n\nLink to ")),
+ is(new SonePart(sone)),
+ is(new PlainTextPart(" and stuff."))
+ ));
+ }
+
+ @Test
+ public void parseEmptyHttpLinks() throws IOException {
+ assertThat(parse("Some text. Empty link: http:// – nice!"), matches(
+ is(new PlainTextPart("Some text. Empty link: http:// – nice!"))
+ ));
+ }
+
+ @Test
+ public void parseTrustedSoneLinks() throws IOException {
+ Sone trustedSone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create();
+ assertThat(parse(new SoneTextParserContext(trustedSone), "Get SSK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/file.txt\u200b!"), matches(
+ is(new PlainTextPart("Get ")),
+ is(new FreenetLinkPart("SSK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/file.txt", "file.txt", true)),
+ is(new PlainTextPart("\u200b!"))
+ ));
+ }
+
+ @Test
+ public void parseHttpLink() throws IOException {
+ assertThat(parse("http://w3.org/foo.html"), matches(
+ is(new LinkPart("http://w3.org/foo.html", "w3.org/foo.html", "w3.org/foo.html"))
+ ));
+ }
+
+ @Test
+ public void twoNonEmptyLinesAreParsedCorrectly() throws IOException {
+ assertThat(parse("First line.\nSecond line."), matches(
+ is(new PlainTextPart("First line.\nSecond line."))
+ ));
+ }
+
+ @Test
+ public void malformedChkLinkIsParsedAsText() throws IOException {
+ assertThat(parse("CHK@key/gpl.txt"), matches(
+ is(new PlainTextPart("CHK@key/gpl.txt"))
+ ));
+ }
+
+ @Test
+ public void malformedUskLinkIsParsedAsText() throws IOException {
+ assertThat(parse("USK@key/site/"), matches(
+ is(new PlainTextPart("USK@key/site/"))
+ ));
+ }
+
+ @Test
+ public void httpsLinksAreParsedCorrectly() throws IOException {
+ assertThat(parse("https://site/file.txt"), matches(
+ is(new LinkPart("https://site/file.txt", "site/file.txt"))
+ ));
+ }
+
+ @Test
+ public void postLinksAreParsedCorrectly() throws IOException {
+ Sone sone = mocks.mockSone("Sone").create();
+ Post post = mocks.mockPost(sone, randomUUID().toString()).create();
+ assertThat(parse("post://" + post.getId()), matches(
+ is(new PostPart(post))
+ ));
+ }
+
+ @Test
+ public void linkToNonExistingPostIsParsedAsPlainText() throws IOException {
+ String postId = randomUUID().toString();
+ assertThat(parse("post://" + postId), matches(
+ is(new PlainTextPart("post://" + postId))
+ ));
+ }
+
+ @Test
+ public void tooShortPostLinkIsParsedAsPlainText() throws IOException {
+ assertThat(parse("post://post"), matches(
+ is(new PlainTextPart("post://post"))
+ ));
+ }
+
+ @Test
+ public void freenetPrefixBeforeKeysIsCutOff() throws IOException {
+ assertThat(parse("freenet:KSK@gpl.txt"), matches(
+ is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false))
+ ));
+ }
+
+ @Test
+ public void linkToNonExistingSoneCreatesLinkToEmptyShell() throws IOException {
+ assertThat(parse("sone://1234567890123456789012345678901234567890123"), matches(
+ is(new SonePart(new DefaultSone(mocks.database, "1234567890123456789012345678901234567890123", false, null)))
+ ));
+ }
+
+ @Test
+ public void linkToTooShortSoneIdIsParsedAsPlainText() throws IOException {
+ assertThat(parse("sone://Sone"), matches(
+ is(new PlainTextPart("sone://Sone"))
+ ));
+ }
+
+ @Test
+ public void cutOffQueryFromTextOfFreenetLink() throws IOException {
+ assertThat(parse("KSK@gpl.txt?max-size=17"), matches(
+ is(new FreenetLinkPart("KSK@gpl.txt?max-size=17", "gpl.txt", false))
+ ));
+ }
+
+ @Test
+ public void linkWithoutMetaInformationShowsShortenedRoutingKey() throws IOException {
+ assertThat(parse("CHK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU"), matches(
+ is(new FreenetLinkPart("CHK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU", "CHK@DAxKQ", false))
+ ));
+ }
+
+ @Test
+ public void httpLinkGetsPartOfPathRemoved() throws IOException {
+ assertThat(parse("http://server.com/path/foo/test.html"), matches(
+ is(new LinkPart("http://server.com/path/foo/test.html", "server.com/…/test.html"))
+ ));
+ }
+
+ @Test
+ public void httpLinkThatEndsInASlashGetsSlashRemoved() throws IOException {
+ assertThat(parse("http://server.com/path/foo/"), matches(
+ is(new LinkPart("http://server.com/path/foo/", "server.com/…"))
+ ));
+ }
+
+ @Test
+ public void httpLinkGetsWwwRemoved() throws IOException {
+ assertThat(parse("http://www.server.com/foo.html"), matches(
+ is(new LinkPart("http://www.server.com/foo.html", "server.com/foo.html"))
+ ));
+ }
+
+ @Test
+ public void httpLinkGetsQueryRemoved() throws IOException {
+ assertThat(parse("http://server.com/foo.html?id=4"), matches(
+ is(new LinkPart("http://server.com/foo.html?id=4", "server.com/foo.html"))
+ ));
}
}