Add unit test for parser filter
[Sone.git] / src / test / java / net / pterodactylus / sone / template / ParserFilterTest.java
1 package net.pterodactylus.sone.template;
2
3 import static java.util.Arrays.asList;
4 import static org.hamcrest.MatcherAssert.assertThat;
5 import static org.hamcrest.Matchers.containsInAnyOrder;
6 import static org.hamcrest.Matchers.is;
7 import static org.mockito.ArgumentCaptor.forClass;
8 import static org.mockito.ArgumentMatchers.any;
9 import static org.mockito.ArgumentMatchers.eq;
10 import static org.mockito.Mockito.mock;
11 import static org.mockito.Mockito.verify;
12 import static org.mockito.Mockito.when;
13
14 import java.net.URLEncoder;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.Map;
18
19 import net.pterodactylus.sone.core.Core;
20 import net.pterodactylus.sone.data.Post;
21 import net.pterodactylus.sone.data.Profile;
22 import net.pterodactylus.sone.data.Sone;
23 import net.pterodactylus.sone.text.FreemailPart;
24 import net.pterodactylus.sone.text.FreenetLinkPart;
25 import net.pterodactylus.sone.text.LinkPart;
26 import net.pterodactylus.sone.text.Part;
27 import net.pterodactylus.sone.text.PartContainer;
28 import net.pterodactylus.sone.text.PlainTextPart;
29 import net.pterodactylus.sone.text.PostPart;
30 import net.pterodactylus.sone.text.SonePart;
31 import net.pterodactylus.sone.text.SoneTextParser;
32 import net.pterodactylus.sone.text.SoneTextParserContext;
33 import net.pterodactylus.util.template.HtmlFilter;
34 import net.pterodactylus.util.template.TemplateContext;
35 import net.pterodactylus.util.template.TemplateContextFactory;
36
37 import com.google.common.base.Optional;
38 import org.jsoup.Jsoup;
39 import org.jsoup.nodes.Attribute;
40 import org.jsoup.nodes.Element;
41 import org.jsoup.nodes.TextNode;
42 import org.junit.Test;
43 import org.mockito.ArgumentCaptor;
44
45 /**
46  * Unit test for {@link ParserFilter}.
47  *
48  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
49  */
50 public class ParserFilterTest {
51
52         private static final String FREEMAIL_ID = "t4dlzfdww3xvsnsc6j6gtliox6zaoak7ymkobbmcmdw527ubuqra";
53         private static final String SONE_FREEMAIL = "sone@" + FREEMAIL_ID + ".freemail";
54         private static final String SONE_IDENTITY = "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI";
55         private static final String POST_ID = "37a06250-6775-4b94-86ff-257ba690953c";
56
57         private final Core core = mock(Core.class);
58         private final TemplateContextFactory templateContextFactory = new TemplateContextFactory();
59         private final TemplateContext templateContext;
60         private final SoneTextParser soneTextParser = mock(SoneTextParser.class);
61         private final ParserFilter filter = new ParserFilter(core, templateContextFactory, soneTextParser);
62         private final Sone sone = setupSone(SONE_IDENTITY, "Sone", "First");
63         private final Map<String, Object> parameters = new HashMap<>();
64
65         public ParserFilterTest() {
66                 templateContextFactory.addFilter("html", new HtmlFilter());
67                 templateContext = templateContextFactory.createTemplateContext();
68         }
69
70         @Test
71         public void givenSoneIsUsedInParseContext() {
72                 setupSoneAndVerifyItIsUsedInContext(sone, sone);
73         }
74
75         private void setupSoneAndVerifyItIsUsedInContext(Object soneOrSoneId, Sone sone) {
76                 setupParser("text", new PlainTextPart("text"));
77                 parameters.put("sone", sone);
78                 filter.format(templateContext, "text", parameters);
79                 ArgumentCaptor<SoneTextParserContext> context = forClass(SoneTextParserContext.class);
80                 verify(soneTextParser).parse(eq("text"), context.capture());
81                 assertThat(context.getValue().getPostingSone(), is(sone));
82         }
83
84         @Test
85         public void soneWithGivenSoneIdIsUsedInParseContext() {
86                 setupSoneAndVerifyItIsUsedInContext(SONE_IDENTITY, sone);
87         }
88
89         @Test
90         public void plainTextIsRenderedCorrectly() {
91                 setupParser("plain text", new PlainTextPart("plain text"));
92                 String result = (String) filter.format(templateContext, "plain text", Collections.<String, Object>emptyMap());
93                 assertThat(result, is("plain text"));
94         }
95
96         private void setupParser(String text, Part... parsedParts) {
97                 when(soneTextParser.parse(eq(text), any(SoneTextParserContext.class))).thenReturn(asList(parsedParts));
98         }
99
100         @Test
101         public void plainTextPartIsShortenedIfLengthExceedsMaxLength() {
102                 setupParser("text", new PlainTextPart("This is a long text."));
103                 setLengthAndCutOffLength(15, 10);
104                 String output = (String) filter.format(templateContext, "text", parameters);
105                 assertThat(output, is("This is a &hellip;"));
106         }
107
108         @Test
109         public void plainTextPartIsNotShortenedIfLengthDoesNotExceedMaxLength() {
110                 setupParser("text", new PlainTextPart("This is a long text."));
111                 setLengthAndCutOffLength(20, 10);
112                 String output = (String) filter.format(templateContext, "text", parameters);
113                 assertThat(output, is("This is a &hellip;"));
114         }
115
116         @Test
117         public void shortPartsAreNotShortened() {
118                 setupParser("text", new PlainTextPart("This."));
119                 setLengthAndCutOffLength(15, 10);
120                 String output = (String) filter.format(templateContext, "text", parameters);
121                 assertThat(output, is("This."));
122         }
123
124         @Test
125         public void multiplePlainTextPartsAreShortened() {
126                 setupParser("text", new PlainTextPart("This "), new PlainTextPart("is a long text."));
127                 setLengthAndCutOffLength(15, 10);
128                 String output = (String) filter.format(templateContext, "text", parameters);
129                 assertThat(output, is("This is a &hellip;"));
130         }
131
132         @Test
133         public void partsAfterLengthHasBeenReachedAreIgnored() {
134                 setupParser("text", new PlainTextPart("This is a long text."), new PlainTextPart(" And even more."));
135                 setLengthAndCutOffLength(15, 10);
136                 String output = (String) filter.format(templateContext, "text", parameters);
137                 assertThat(output, is("This is a &hellip;"));
138         }
139
140         @Test
141         public void linkPartsAreNotShortened() {
142                 setupParser("text", new FreenetLinkPart("KSK@gpl.txt", "This is a long text.", false));
143                 setLengthAndCutOffLength(15, 10);
144                 String output = (String) filter.format(templateContext, "text", parameters);
145                 Element linkNode = Jsoup.parseBodyFragment(output).body().child(0);
146                 verifyLink(linkNode, "/KSK@gpl.txt", "freenet", "KSK@gpl.txt", "This is a long text.");
147         }
148
149         @Test
150         public void additionalLinkPartsAreIgnored() {
151                 setupParser("text", new PlainTextPart("This is a long text."), new FreenetLinkPart("KSK@gpl.txt", "This is a long text.", false));
152                 setLengthAndCutOffLength(15, 10);
153                 String output = (String) filter.format(templateContext, "text", parameters);
154                 assertThat(output, is("This is a &hellip;"));
155         }
156
157         private void setLengthAndCutOffLength(int length, int cutOffLength) {
158                 parameters.put("length", length);
159                 parameters.put("cut-off-length", cutOffLength);
160         }
161
162         @Test
163         public void sonePartsAreAddedButTheirLengthIsIgnored() {
164                 setupParser("text", new SonePart(sone), new PlainTextPart("This is a long text."));
165                 setLengthAndCutOffLength(15, 10);
166                 String output = (String) filter.format(templateContext, "text", parameters);
167                 Element body = Jsoup.parseBodyFragment(output).body();
168                 Element linkNode = (Element) body.childNode(0);
169                 System.out.println(linkNode);
170                 verifyLink(linkNode, "viewSone.html?sone=" + SONE_IDENTITY, "in-sone", "First", "First");
171                 assertThat(((TextNode) body.childNode(1)).text(), is("This is a …"));
172         }
173
174         @Test
175         public void additionalSonePartsAreIgnored() {
176                 setupParser("text", new PlainTextPart("This is a long text."), new SonePart(sone));
177                 setLengthAndCutOffLength(15, 10);
178                 String output = (String) filter.format(templateContext, "text", parameters);
179                 assertThat(output, is("This is a &hellip;"));
180         }
181
182         @Test
183         public void freenetLinkIsRenderedCorrectly() {
184                 setupParser("KSK@gpl.txt", new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", false));
185                 Element linkNode = filterText("KSK@gpl.txt");
186                 verifyLink(linkNode, "/KSK@gpl.txt", "freenet", "KSK@gpl.txt", "gpl.txt");
187         }
188
189         private void verifyLink(Element linkNode, String url, String cssClass, String tooltip, String text) {
190                 assertThat(linkNode.nodeName(), is("a"));
191                 assertThat(linkNode.attributes().asList(), containsInAnyOrder(
192                                 new Attribute("href", url),
193                                 new Attribute("class", cssClass),
194                                 new Attribute("title", tooltip)
195                 ));
196                 assertThat(linkNode.text(), is(text));
197         }
198
199         @Test
200         public void trustedFreenetLinkIsRenderedWithCorrectCssClass() {
201                 setupParser("KSK@gpl.txt", new FreenetLinkPart("KSK@gpl.txt", "gpl.txt", true));
202                 Element linkNode = filterText("KSK@gpl.txt");
203                 verifyLink(linkNode, "/KSK@gpl.txt", "freenet-trusted", "KSK@gpl.txt", "gpl.txt");
204         }
205
206         private Element filterText(String text) {
207                 String output = (String) filter.format(templateContext, text, Collections.<String, Object>emptyMap());
208                 return Jsoup.parseBodyFragment(output).body().child(0);
209         }
210
211         @Test
212         public void internetLinkIsRenderedCorrectly() throws Exception {
213                 setupParser("http://test.com/test.html", new LinkPart("http://test.com/test.html", "test.com/test.html"));
214                 Element linkNode = filterText("http://test.com/test.html");
215                 verifyLink(linkNode, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode("http://test.com/test.html", "UTF-8"), "internet",
216                                 "http://test.com/test.html", "test.com/test.html");
217         }
218
219         @Test
220         public void sonePartsAreRenderedCorrectly() {
221                 setupParser("sone://" + SONE_IDENTITY, new SonePart(sone));
222                 Element linkNode = filterText("sone://" + SONE_IDENTITY);
223                 verifyLink(linkNode, "viewSone.html?sone=" + SONE_IDENTITY, "in-sone", "First", "First");
224         }
225
226         private Sone setupSone(String identity, String name, String firstName) {
227                 Sone sone = mock(Sone.class);
228                 when(sone.getId()).thenReturn(identity);
229                 when(sone.getProfile()).thenReturn(new Profile(sone));
230                 when(sone.getName()).thenReturn(name);
231                 sone.getProfile().setFirstName(firstName);
232                 when(core.getSone(identity)).thenReturn(Optional.of(sone));
233                 return sone;
234         }
235
236         @Test
237         public void sonePartsWithUnknownSoneIsRenderedAsLinkToWebOfTrust() {
238                 Sone sone = setupSone(SONE_IDENTITY, null, "First");
239                 setupParser("sone://" + SONE_IDENTITY, new SonePart(sone));
240                 Element linkNode = filterText("sone://" + SONE_IDENTITY);
241                 verifyLink(linkNode, "/WebOfTrust/ShowIdentity?id=" + SONE_IDENTITY, "in-sone", SONE_IDENTITY, SONE_IDENTITY);
242         }
243
244         @Test
245         public void postPartIsCutOffCorrectlyWhenThereAreSpaces() {
246                 Post post = setupPost(sone, "1234 678901 345 789012 45678 01.");
247                 setupParser("post://" + POST_ID, new PostPart(post));
248                 Element linkNode = filterText("post://" + POST_ID);
249                 verifyLink(linkNode, "viewPost.html?post=" + POST_ID, "in-sone", "First", "1234 678901 345…");
250         }
251
252         private Post setupPost(Sone sone, String value) {
253                 Post post = mock(Post.class);
254                 when(post.getId()).thenReturn(POST_ID);
255                 when(post.getSone()).thenReturn(sone);
256                 when(post.getText()).thenReturn(value);
257                 return post;
258         }
259
260         @Test
261         public void postPartIsCutOffCorrectlyWhenThereAreNoSpaces() {
262                 Post post = setupPost(sone, "1234567890123456789012345678901.");
263                 setupParser("post://" + POST_ID, new PostPart(post));
264                 Element linkNode = filterText("post://" + POST_ID);
265                 verifyLink(linkNode, "viewPost.html?post=" + POST_ID, "in-sone", "First", "12345678901234567890…");
266         }
267
268         @Test
269         public void postPartShorterThan21CharsIsNotCutOff() {
270                 Post post = setupPost(sone, "12345678901234567890");
271                 setupParser("post://" + POST_ID, new PostPart(post));
272                 Element linkNode = filterText("post://" + POST_ID);
273                 verifyLink(linkNode, "viewPost.html?post=" + POST_ID, "in-sone", "First", "12345678901234567890");
274         }
275
276         @Test
277         public void multiplePartsAreRenderedCorrectly() {
278                 PartContainer parts = new PartContainer();
279                 parts.add(new PlainTextPart("te"));
280                 parts.add(new PlainTextPart("xt"));
281                 setupParser("text", parts);
282                 String result = (String) filter.format(templateContext, "text", Collections.<String, Object>emptyMap());
283                 assertThat(result, is("text"));
284         }
285
286         @Test
287         public void freemailAddressIsDisplayedCorrectly() {
288                 setupParser(SONE_FREEMAIL, new FreemailPart("sone", FREEMAIL_ID, SONE_IDENTITY));
289                 Element linkNode = filterText(SONE_FREEMAIL);
290                 verifyLink(linkNode, "/Freemail/NewMessage?to=" + SONE_IDENTITY, "in-sone", "First\n" + SONE_FREEMAIL, "sone@First.freemail");
291         }
292
293 }