Rework the text parser.
[Sone.git] / src / test / java / net / pterodactylus / sone / text / SoneTextParserTest.java
1 /*
2  * Sone - SoneTextParserTest.java - Copyright © 2011–2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.text;
19
20 import static com.google.common.collect.ImmutableList.builder;
21 import static java.util.Arrays.asList;
22 import static java.util.UUID.randomUUID;
23 import static org.hamcrest.MatcherAssert.assertThat;
24 import static org.hamcrest.Matchers.is;
25
26 import java.io.IOException;
27 import java.io.StringReader;
28 import java.util.Collection;
29
30 import net.pterodactylus.sone.data.Mocks;
31 import net.pterodactylus.sone.data.Post;
32 import net.pterodactylus.sone.data.Sone;
33 import net.pterodactylus.sone.data.impl.DefaultSone;
34
35 import com.google.common.collect.ImmutableList;
36 import org.hamcrest.Description;
37 import org.hamcrest.Matcher;
38 import org.hamcrest.TypeSafeMatcher;
39 import org.hamcrest.collection.IsIterableContainingInOrder;
40 import org.junit.Test;
41
42 /**
43  * JUnit test case for {@link SoneTextParser}.
44  *
45  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
46  */
47 public class SoneTextParserTest {
48
49         private final Mocks mocks = new Mocks();
50         private final SoneTextParser soneTextParser = new SoneTextParser(mocks.database);
51
52         private Matcher<Iterable<Part>> matches(final Matcher<? extends Part>... partsToMatch) {
53                 return new TypeSafeMatcher<Iterable<Part>>() {
54
55                         private Matcher<Iterable<? extends Part>> iterableMatcher;
56
57                         @Override
58                         protected boolean matchesSafely(Iterable<Part> parts) {
59                                 iterableMatcher = new IsIterableContainingInOrder(asList(partsToMatch));
60                                 return iterableMatcher.matches(collapseParts(expandParts(parts)));
61                         }
62
63                         private Iterable<Part> expandParts(Iterable<? extends Part> parts) {
64                                 PartContainer partContainer = new PartContainer();
65                                 for (Part part : parts) {
66                                         partContainer.add(part);
67                                 }
68                                 return partContainer;
69                         }
70
71                         private Collection<Part> collapseParts(Iterable<? extends Part> parts) {
72                                 ImmutableList.Builder<Part> collapsedPartsBuilder = builder();
73                                 PlainTextPart lastPlainTextPart = null;
74                                 for (Part part : parts) {
75                                         if (part instanceof PlainTextPart) {
76                                                 if (lastPlainTextPart != null) {
77                                                         lastPlainTextPart = new PlainTextPart(lastPlainTextPart.getText() + ((PlainTextPart) part).getText());
78                                                 } else {
79                                                         lastPlainTextPart = (PlainTextPart) part;
80                                                 }
81                                         } else {
82                                                 if (lastPlainTextPart != null) {
83                                                         collapsedPartsBuilder.add(lastPlainTextPart);
84                                                         lastPlainTextPart = null;
85                                                 }
86                                                 collapsedPartsBuilder.add(part);
87                                         }
88                                 }
89                                 if (lastPlainTextPart != null) {
90                                         collapsedPartsBuilder.add(lastPlainTextPart);
91                                 }
92                                 return collapsedPartsBuilder.build();
93                         }
94
95                         @Override
96                         protected void describeMismatchSafely(Iterable<Part> parts, Description mismatchDescription) {
97                                 iterableMatcher.describeMismatch(collapseParts(parts), mismatchDescription);
98                         }
99
100                         @Override
101                         public void describeTo(Description description) {
102                                 iterableMatcher.describeTo(description);
103                         }
104                 };
105         }
106
107         private Iterable<Part> parse(String text) throws IOException {
108                 return soneTextParser.parse(null, new StringReader(text));
109         }
110
111         private Iterable<Part> parse(SoneTextParserContext context, String text) throws IOException {
112                 return soneTextParser.parse(context, new StringReader(text));
113         }
114
115         @Test
116         public void parsePlainText() throws IOException {
117                 assertThat(parse("Test."), matches(is(new PlainTextPart("Test."))));
118         }
119
120         @Test
121         public void parsePlainTextWithEmptyLinesAtTheBeginningAndEnd() throws IOException {
122                 assertThat(parse("\nTest.\n\n"), matches(is(new PlainTextPart("Test."))));
123         }
124
125         @Test
126         public void parsePlainTextAndCollapseMultipleEmptyLines() throws IOException {
127                 assertThat(parse("\nTest.\n\n\nTest."), matches(is(new PlainTextPart("Test.\n\nTest."))));
128         }
129
130         @Test
131         public void parseSimpleKskLinks() throws IOException {
132                 assertThat(parse("KSK@gpl.txt"), matches(is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false))));
133         }
134
135         @Test
136         public void parseEmbeddedLinks() throws IOException {
137                 assertThat(parse("Link is KSK@gpl.txt\u200b."), matches(
138                                 is(new PlainTextPart("Link is ")),
139                                 is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)),
140                                 is(new PlainTextPart("\u200b."))
141                 ));
142         }
143
144         @Test
145         public void parseEmbeddedLinksAndLineBreaks() throws IOException {
146                 assertThat(parse("Link is KSK@gpl.txt\nKSK@test.dat\n"), matches(
147                                 is(new PlainTextPart("Link is ")),
148                                 is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)),
149                                 is(new PlainTextPart("\n")),
150                                 is(new FreenetLinkPart("KSK@test.dat", "test.dat", false))
151                 ));
152         }
153
154         @Test
155         public void parseEmptyLinesAndSoneLinks() throws IOException {
156                 Sone sone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create();
157                 assertThat(parse("Some text.\n\nLink to sone://DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU and stuff."), matches(
158                                 is(new PlainTextPart("Some text.\n\nLink to ")),
159                                 is(new SonePart(sone)),
160                                 is(new PlainTextPart(" and stuff."))
161                 ));
162         }
163
164         @Test
165         public void parseEmptyHttpLinks() throws IOException {
166                 assertThat(parse("Some text. Empty link: http:// – nice!"), matches(
167                                 is(new PlainTextPart("Some text. Empty link: http:// – nice!"))
168                 ));
169         }
170
171         @Test
172         public void parseTrustedSoneSSKLinks() throws IOException {
173                 Sone trustedSone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create();
174                 assertThat(parse(new SoneTextParserContext(trustedSone), "Get SSK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/file.txt\u200b!"), matches(
175                                 is(new PlainTextPart("Get ")),
176                                 is(new FreenetLinkPart("SSK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/file.txt", "file.txt", true)),
177                                 is(new PlainTextPart("\u200b!"))
178                 ));
179         }
180
181         @Test
182         public void parseTrustedSoneUSKLinks() throws IOException {
183                 Sone trustedSone = mocks.mockSone("DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU").create();
184                 assertThat(parse(new SoneTextParserContext(trustedSone), "Get USK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU,DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/site/0/file.txt\u200b!"), matches(
185                                 is(new PlainTextPart("Get ")),
186                                 is(new FreenetLinkPart("USK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU,DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU/site/0/file.txt", "file.txt", true)),
187                                 is(new PlainTextPart("\u200b!"))
188                 ));
189         }
190
191         @Test
192         public void parseHttpLink() throws IOException {
193                 assertThat(parse("http://w3.org/foo.html"), matches(
194                                 is(new LinkPart("http://w3.org/foo.html", "w3.org/foo.html", "w3.org/foo.html"))
195                 ));
196         }
197
198         @Test
199         public void twoNonEmptyLinesAreParsedCorrectly() throws IOException {
200                 assertThat(parse("First line.\nSecond line."), matches(
201                                 is(new PlainTextPart("First line.\nSecond line."))
202                 ));
203         }
204
205         @Test
206         public void malformedChkLinkIsParsedAsText() throws IOException {
207                 assertThat(parse("CHK@key/gpl.txt"), matches(
208                                 is(new PlainTextPart("CHK@key/gpl.txt"))
209                 ));
210         }
211
212         @Test
213         public void malformedUskLinkIsParsedAsText() throws IOException {
214                 assertThat(parse("USK@key/site/"), matches(
215                                 is(new PlainTextPart("USK@key/site/"))
216                 ));
217         }
218
219         @Test
220         public void httpsLinksAreParsedCorrectly() throws IOException {
221                 assertThat(parse("https://site/file.txt"), matches(
222                                 is(new LinkPart("https://site/file.txt", "site/file.txt"))
223                 ));
224         }
225
226         @Test
227         public void postLinksAreParsedCorrectly() throws IOException {
228                 Sone sone = mocks.mockSone("Sone").create();
229                 Post post = mocks.mockPost(sone, randomUUID().toString()).create();
230                 assertThat(parse("post://" + post.getId()), matches(
231                                 is(new PostPart(post))
232                 ));
233         }
234
235         @Test
236         public void linkToNonExistingPostIsParsedAsPlainText() throws IOException {
237                 String postId = randomUUID().toString();
238                 assertThat(parse("post://" + postId), matches(
239                                 is(new PlainTextPart("post://" + postId))
240                 ));
241         }
242
243         @Test
244         public void tooShortPostLinkIsParsedAsPlainText() throws IOException {
245                 assertThat(parse("post://post"), matches(
246                                 is(new PlainTextPart("post://post"))
247                 ));
248         }
249
250         @Test
251         public void freenetPrefixBeforeKeysIsCutOff() throws IOException {
252                 assertThat(parse("freenet:KSK@gpl.txt"), matches(
253                                 is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false))
254                 ));
255         }
256
257         @Test
258         public void freenetPrefixBeforeKeysInMiddleOfTextIsCutOff() throws IOException {
259                 assertThat(parse("Link is freenet:KSK@gpl.txt"), matches(
260                                 is(new PlainTextPart("Link is ")),
261                                 is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false))
262                 ));
263         }
264
265         @Test
266         public void linkToNonExistingSoneCreatesPlainTextLink() throws IOException {
267                 assertThat(parse("sone://1234567890123456789012345678901234567890123"), matches(
268                                 is(new PlainTextPart("sone://1234567890123456789012345678901234567890123"))
269                 ));
270         }
271
272         @Test
273         public void linkToTooShortSoneIdIsParsedAsPlainText() throws IOException {
274                 assertThat(parse("sone://Sone"), matches(
275                                 is(new PlainTextPart("sone://Sone"))
276                 ));
277         }
278
279         @Test
280         public void cutOffQueryFromTextOfFreenetLink() throws IOException {
281                 assertThat(parse("KSK@gpl.txt?max-size=17"), matches(
282                                 is(new FreenetLinkPart("KSK@gpl.txt?max-size=17", "gpl.txt", false))
283                 ));
284         }
285
286         @Test
287         public void linkWithoutMetaInformationShowsShortenedRoutingKey() throws IOException {
288                 assertThat(parse("CHK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU"), matches(
289                                 is(new FreenetLinkPart("CHK@DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU", "CHK@DAxKQ", false))
290                 ));
291         }
292
293         @Test
294         public void httpLinkGetsPartOfPathRemoved() throws IOException {
295                 assertThat(parse("http://server.com/path/foo/test.html"), matches(
296                                 is(new LinkPart("http://server.com/path/foo/test.html", "server.com/…/test.html"))
297                 ));
298         }
299
300         @Test
301         public void httpLinkThatEndsInASlashGetsSlashRemoved() throws IOException {
302                 assertThat(parse("http://server.com/path/foo/"), matches(
303                                 is(new LinkPart("http://server.com/path/foo/", "server.com/…"))
304                 ));
305         }
306
307         @Test
308         public void httpLinkGetsWwwRemoved() throws IOException {
309                 assertThat(parse("http://www.server.com/foo.html"), matches(
310                                 is(new LinkPart("http://www.server.com/foo.html", "server.com/foo.html"))
311                 ));
312         }
313
314         @Test
315         public void httpLinkGetsQueryRemoved() throws IOException {
316                 assertThat(parse("http://server.com/foo.html?id=4"), matches(
317                                 is(new LinkPart("http://server.com/foo.html?id=4", "server.com/foo.html"))
318                 ));
319         }
320
321         @Test
322         public void multipleLinksInOneLine() throws IOException {
323                 assertThat(parse("KSK@gpl.txt and http://server.com/"), matches(
324                                 is(new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false)),
325                                 is(new PlainTextPart(" and ")),
326                                 is(new FreenetLinkPart("http://server.com/", "server.com", false))
327                 ));
328         }
329
330 }