Add test for Sone downloader.
[Sone.git] / src / test / java / net / pterodactylus / sone / core / SoneDownloaderTest.java
1 package net.pterodactylus.sone.core;
2
3 import static com.google.common.base.Optional.of;
4 import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading;
5 import static net.pterodactylus.sone.data.Sone.SoneStatus.idle;
6 import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown;
7 import static org.hamcrest.MatcherAssert.assertThat;
8 import static org.hamcrest.Matchers.hasSize;
9 import static org.hamcrest.Matchers.is;
10 import static org.hamcrest.Matchers.not;
11 import static org.hamcrest.Matchers.notNullValue;
12 import static org.hamcrest.Matchers.nullValue;
13 import static org.mockito.ArgumentCaptor.forClass;
14 import static org.mockito.Matchers.any;
15 import static org.mockito.Matchers.anyBoolean;
16 import static org.mockito.Matchers.anyLong;
17 import static org.mockito.Matchers.anyString;
18 import static org.mockito.Matchers.eq;
19 import static org.mockito.Mockito.mock;
20 import static org.mockito.Mockito.never;
21 import static org.mockito.Mockito.times;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
24
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.net.MalformedURLException;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 import net.pterodactylus.sone.core.FreenetInterface.Fetched;
36 import net.pterodactylus.sone.core.SoneDownloader.FetchSone;
37 import net.pterodactylus.sone.core.SoneDownloader.FetchSoneWithUri;
38 import net.pterodactylus.sone.data.Album;
39 import net.pterodactylus.sone.data.AlbumImpl;
40 import net.pterodactylus.sone.data.Client;
41 import net.pterodactylus.sone.data.Image;
42 import net.pterodactylus.sone.data.ImageImpl;
43 import net.pterodactylus.sone.data.Post;
44 import net.pterodactylus.sone.data.PostReply;
45 import net.pterodactylus.sone.data.Profile;
46 import net.pterodactylus.sone.data.Sone;
47 import net.pterodactylus.sone.data.Sone.SoneStatus;
48 import net.pterodactylus.sone.database.PostBuilder;
49 import net.pterodactylus.sone.database.PostReplyBuilder;
50 import net.pterodactylus.sone.freenet.wot.Identity;
51
52 import freenet.client.ClientMetadata;
53 import freenet.client.FetchResult;
54 import freenet.keys.FreenetURI;
55 import freenet.support.api.Bucket;
56
57 import com.google.common.base.Optional;
58 import com.google.common.collect.ImmutableSet;
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.invocation.InvocationOnMock;
63 import org.mockito.stubbing.Answer;
64
65 /**
66  * Unit test for {@link SoneDownloader} and its subclasses.
67  *
68  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
69  */
70 public class SoneDownloaderTest {
71
72         private final Core core = mock(Core.class);
73         private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
74         private final SoneDownloader soneDownloader = new SoneDownloader(core, freenetInterface);
75         private final FreenetURI requestUri = mock(FreenetURI.class);
76         private final Sone sone = mock(Sone.class);
77         private final PostBuilder postBuilder = mock(PostBuilder.class);
78         private final List<Post> createdPosts = new ArrayList<Post>();
79         private Post post = mock(Post.class);
80         private final PostReplyBuilder postReplyBuilder = mock(PostReplyBuilder.class);
81         private final Set<PostReply> createdPostReplies = new HashSet<PostReply>();
82         private PostReply postReply = mock(PostReply.class);
83         private final Map<String, Album> albums = new HashMap<String, Album>();
84
85         @Before
86         public void setupSone() {
87                 Identity identity = mock(Identity.class);
88                 when(identity.getId()).thenReturn("identity");
89                 when(sone.getId()).thenReturn("identity");
90                 when(sone.getIdentity()).thenReturn(identity);
91                 when(sone.getRequestUri()).thenReturn(requestUri);
92         }
93
94         @Before
95         public void setupRequestUri() {
96                 when(requestUri.setKeyType(anyString())).thenReturn(requestUri);
97                 when(requestUri.sskForUSK()).thenReturn(requestUri);
98                 when(requestUri.setDocName(anyString())).thenReturn(requestUri);
99                 when(requestUri.setMetaString(any(String[].class))).thenReturn(requestUri);
100                 when(requestUri.getKeyType()).thenReturn("USK");
101         }
102
103         @Before
104         public void setupPost() {
105                 when(post.getRecipientId()).thenReturn(Optional.<String>absent());
106         }
107
108         @Before
109         public void setupPostBuilder() {
110                 when(postBuilder.withId(anyString())).thenAnswer(new Answer<PostBuilder>() {
111                         @Override
112                         public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
113                                 when(post.getId()).thenReturn((String) invocation.getArguments()[0]);
114                                 return postBuilder;
115                         }
116                 });
117                 when(postBuilder.from(anyString())).thenAnswer(new Answer<PostBuilder>() {
118                         @Override
119                         public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
120                                 final Sone sone = mock(Sone.class);
121                                 when(sone.getId()).thenReturn((String) invocation.getArguments()[0]);
122                                 when(post.getSone()).thenReturn(sone);
123                                 return postBuilder;
124                         }
125                 });
126                 when(postBuilder.withTime(anyLong())).thenAnswer(new Answer<PostBuilder>() {
127                         @Override
128                         public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
129                                 when(post.getTime()).thenReturn((Long) invocation.getArguments()[0]);
130                                 return postBuilder;
131                         }
132                 });
133                 when(postBuilder.withText(anyString())).thenAnswer(new Answer<PostBuilder>() {
134                         @Override
135                         public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
136                                 when(post.getText()).thenReturn((String) invocation.getArguments()[0]);
137                                 return postBuilder;
138                         }
139                 });
140                 when(postBuilder.to(anyString())).thenAnswer(new Answer<PostBuilder>() {
141                         @Override
142                         public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
143                                 when(post.getRecipientId()).thenReturn(of((String) invocation.getArguments()[0]));
144                                 return postBuilder;
145                         }
146                 });
147                 when(postBuilder.build()).thenAnswer(new Answer<Post>() {
148                         @Override
149                         public Post answer(InvocationOnMock invocation) throws Throwable {
150                                 Post post = SoneDownloaderTest.this.post;
151                                 SoneDownloaderTest.this.post = mock(Post.class);
152                                 setupPost();
153                                 createdPosts.add(post);
154                                 return post;
155                         }
156                 });
157                 when(core.postBuilder()).thenReturn(postBuilder);
158         }
159
160         @Before
161         public void setupPostReplyBuilder() {
162                 when(postReplyBuilder.withId(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
163                         @Override
164                         public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
165                                 when(postReply.getId()).thenReturn((String) invocation.getArguments()[0]);
166                                 return postReplyBuilder;
167                         }
168                 });
169                 when(postReplyBuilder.from(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
170                         @Override
171                         public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
172                                 Sone sone = when(mock(Sone.class).getId()).thenReturn((String) invocation.getArguments()[0]).getMock();
173                                 when(postReply.getSone()).thenReturn(sone);
174                                 return postReplyBuilder;
175                         }
176                 });
177                 when(postReplyBuilder.to(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
178                         @Override
179                         public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
180                                 when(postReply.getPostId()).thenReturn((String) invocation.getArguments()[0]);
181                                 Post post = when(mock(Post.class).getId()).thenReturn((String) invocation.getArguments()[0]).getMock();
182                                 when(postReply.getPost()).thenReturn(of(post));
183                                 return postReplyBuilder;
184                         }
185                 });
186                 when(postReplyBuilder.withTime(anyLong())).thenAnswer(new Answer<PostReplyBuilder>() {
187                         @Override
188                         public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
189                                 when(postReply.getTime()).thenReturn((Long) invocation.getArguments()[0]);
190                                 return postReplyBuilder;
191                         }
192                 });
193                 when(postReplyBuilder.withText(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
194                         @Override
195                         public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
196                                 when(postReply.getText()).thenReturn((String) invocation.getArguments()[0]);
197                                 return postReplyBuilder;
198                         }
199                 });
200                 when(postReplyBuilder.build()).thenAnswer(new Answer<PostReply>() {
201                         @Override
202                         public PostReply answer(InvocationOnMock invocation) throws Throwable {
203                                 PostReply postReply = SoneDownloaderTest.this.postReply;
204                                 createdPostReplies.add(postReply);
205                                 SoneDownloaderTest.this.postReply = mock(PostReply.class);
206                                 return postReply;
207                         }
208                 });
209                 when(core.postReplyBuilder()).thenReturn(postReplyBuilder);
210         }
211
212         @Before
213         public void setupAlbums() {
214                 albums.put("album-id-1", new AlbumImpl("album-id-1"));
215                 albums.put("album-id-2", new AlbumImpl("album-id-2"));
216                 when(core.getAlbum(anyString())).thenAnswer(new Answer<Album>() {
217                         @Override
218                         public Album answer(InvocationOnMock invocation) throws Throwable {
219                                 return albums.get(invocation.getArguments()[0]);
220                         }
221                 });
222                 when(core.getAlbum(anyString(), anyBoolean())).thenAnswer(new Answer<Album>() {
223                         @Override
224                         public Album answer(InvocationOnMock invocation) throws Throwable {
225                                 return albums.get(invocation.getArguments()[0]);
226                         }
227                 });
228         }
229
230         @Before
231         public void setupImages() {
232                 Image image = new ImageImpl("image-id");
233                 when(core.getImage("image-id")).thenReturn(image);
234                 when(core.getImage(eq("image-id"), anyBoolean())).thenReturn(image);
235         }
236
237         @Test
238         public void addingASoneWillRegisterItsKey() {
239                 soneDownloader.addSone(sone);
240                 verify(freenetInterface).registerUsk(sone, soneDownloader);
241                 verify(freenetInterface, never()).unregisterUsk(sone);
242         }
243
244         @Test
245         public void addingASoneTwiceWillAlsoDeregisterItsKey() {
246                 soneDownloader.addSone(sone);
247                 soneDownloader.addSone(sone);
248                 verify(freenetInterface, times(2)).registerUsk(sone, soneDownloader);
249                 verify(freenetInterface).unregisterUsk(sone);
250         }
251
252         @Test
253         public void parsingASoneFailsWhenDocumentIsNotXml() throws SoneException {
254                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-not-xml.xml");
255                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
256         }
257
258         @Test
259         public void parsingASoneFailsWhenDocumentHasNegativeProtocolVersion() throws SoneException {
260                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-negative-protocol-version.xml");
261                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
262         }
263
264         @Test
265         public void parsingASoneFailsWhenProtocolVersionIsTooLarge() throws SoneException {
266                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-too-large-protocol-version.xml");
267                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
268         }
269
270         @Test
271         public void parsingASoneFailsWhenThereIsNoTime() throws SoneException {
272                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-time.xml");
273                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
274         }
275
276         @Test
277         public void parsingASoneFailsWhenTimeIsNotNumeric() throws SoneException {
278                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-time-not-numeric.xml");
279                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
280         }
281
282         @Test
283         public void parsingASoneFailsWhenProfileIsMissing() throws SoneException {
284                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-profile.xml");
285                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
286         }
287
288         @Test
289         public void parsingASoneFailsWhenProfileFieldIsMissingAFieldName() throws SoneException {
290                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-missing-field-name.xml");
291                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
292         }
293
294         @Test
295         public void parsingASoneFailsWhenProfileFieldNameIsEmpty() throws SoneException {
296                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-empty-field-name.xml");
297                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
298         }
299
300         @Test
301         public void parsingASoneFailsWhenProfileFieldNameIsNotUnique() throws SoneException {
302                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-duplicate-field-name.xml");
303                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
304         }
305
306         @Test
307         public void parsingASoneSucceedsWithoutPayload() throws SoneException {
308                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
309                 assertThat(soneDownloader.parseSone(sone, inputStream).getTime(), is(1407197508000L));
310         }
311
312         @Test
313         public void parsingASoneSucceedsWithoutProtocolVersion() throws SoneException {
314                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-protocol-version.xml");
315                 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
316         }
317
318         @Test
319         public void parsingASoneFailsWithMissingClientName() throws SoneException {
320                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-name.xml");
321                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
322         }
323
324         @Test
325         public void parsingASoneFailsWithMissingClientVersion() throws SoneException {
326                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-version.xml");
327                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
328         }
329
330         @Test
331         public void parsingASoneSucceedsWithClientInfo() throws SoneException {
332                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-client-info.xml");
333                 assertThat(soneDownloader.parseSone(sone, inputStream).getClient(), is(new Client("some-client", "some-version")));
334         }
335
336         @Test
337         public void parsingASoneSucceedsWithRequestUri() throws SoneException, MalformedURLException {
338                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-request-uri.xml");
339                 assertThat(soneDownloader.parseSone(sone, inputStream).getRequestUri().toString(), is("USK@w~RyTGmv12Lg9oO91q1Untupi7my9qczT1RheGkEkVE,E8ElVfUgukSCPHxIEJp-gHMiR80wpM7sID3Jo5O7w1s,AQACAAE/Sone/0"));
340         }
341
342         @Test
343         public void parsingASoneFailsWithInvalidRequestUri() throws SoneException, MalformedURLException {
344                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-request-uri.xml");
345                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
346         }
347
348         @Test
349         public void soneInsertUriIsCopiedToNewSone() throws SoneException {
350                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
351                 FreenetURI insertUri = mock(FreenetURI.class);
352                 when(insertUri.setKeyType(anyString())).thenReturn(insertUri);
353                 when(insertUri.setDocName(anyString())).thenReturn(insertUri);
354                 when(insertUri.setMetaString(any(String[].class))).thenReturn(insertUri);
355                 when(insertUri.setSuggestedEdition(anyLong())).thenReturn(insertUri);
356                 when(sone.getInsertUri()).thenReturn(insertUri);
357                 assertThat(soneDownloader.parseSone(sone, inputStream).getInsertUri(), is(insertUri));
358         }
359
360         @Test
361         public void parsingASoneSucceedsWithProfile() throws SoneException, MalformedURLException {
362                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-profile.xml");
363                 final Profile profile = soneDownloader.parseSone(sone, inputStream).getProfile();
364                 assertThat(profile.getFirstName(), is("first"));
365                 assertThat(profile.getMiddleName(), is("middle"));
366                 assertThat(profile.getLastName(), is("last"));
367                 assertThat(profile.getBirthDay(), is(18));
368                 assertThat(profile.getBirthMonth(), is(12));
369                 assertThat(profile.getBirthYear(), is(1976));
370         }
371
372         @Test
373         public void parsingASoneSucceedsWithoutProfileFields() throws SoneException, MalformedURLException {
374                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-fields.xml");
375                 assertThat(soneDownloader.parseSone(sone, inputStream), notNullValue());
376         }
377
378         @Test
379         public void parsingASoneFailsWithoutPostId() throws SoneException, MalformedURLException {
380                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-id.xml");
381                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
382         }
383
384         @Test
385         public void parsingASoneFailsWithoutPostTime() throws SoneException, MalformedURLException {
386                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-time.xml");
387                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
388         }
389
390         @Test
391         public void parsingASoneFailsWithoutPostText() throws SoneException, MalformedURLException {
392                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-text.xml");
393                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
394         }
395
396         @Test
397         public void parsingASoneFailsWithInvalidPostTime() throws SoneException, MalformedURLException {
398                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-time.xml");
399                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
400         }
401
402         @Test
403         public void parsingASoneSucceedsWithValidPostTime() throws SoneException, MalformedURLException {
404                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-time.xml");
405                 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
406                 assertThat(posts, is(createdPosts));
407                 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
408                 assertThat(posts.get(0).getId(), is("post-id"));
409                 assertThat(posts.get(0).getTime(), is(1407197508000L));
410                 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
411                 assertThat(posts.get(0).getText(), is("text"));
412         }
413
414         @Test
415         public void parsingASoneSucceedsWithRecipient() throws SoneException, MalformedURLException {
416                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-recipient.xml");
417                 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
418                 assertThat(posts, is(createdPosts));
419                 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
420                 assertThat(posts.get(0).getId(), is("post-id"));
421                 assertThat(posts.get(0).getTime(), is(1407197508000L));
422                 assertThat(posts.get(0).getRecipientId(), is(of("1234567890123456789012345678901234567890123")));
423                 assertThat(posts.get(0).getText(), is("text"));
424         }
425
426         @Test
427         public void parsingASoneSucceedsWithInvalidRecipient() throws SoneException, MalformedURLException {
428                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-recipient.xml");
429                 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
430                 assertThat(posts, is(createdPosts));
431                 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
432                 assertThat(posts.get(0).getId(), is("post-id"));
433                 assertThat(posts.get(0).getTime(), is(1407197508000L));
434                 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
435                 assertThat(posts.get(0).getText(), is("text"));
436         }
437
438         @Test
439         public void parsingASoneFailsWithoutPostReplyId() throws SoneException, MalformedURLException {
440                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-id.xml");
441                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
442         }
443
444         @Test
445         public void parsingASoneFailsWithoutPostReplyPostId() throws SoneException, MalformedURLException {
446                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-post-id.xml");
447                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
448         }
449
450         @Test
451         public void parsingASoneFailsWithoutPostReplyTime() throws SoneException, MalformedURLException {
452                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-time.xml");
453                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
454         }
455
456         @Test
457         public void parsingASoneFailsWithoutPostReplyText() throws SoneException, MalformedURLException {
458                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-text.xml");
459                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
460         }
461
462         @Test
463         public void parsingASoneFailsWithInvalidPostReplyTime() throws SoneException, MalformedURLException {
464                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-reply-time.xml");
465                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
466         }
467
468         @Test
469         public void parsingASoneSucceedsWithValidPostReplyTime() throws SoneException, MalformedURLException {
470                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-reply-time.xml");
471                 final Set<PostReply> postReplies = soneDownloader.parseSone(sone, inputStream).getReplies();
472                 assertThat(postReplies, is(createdPostReplies));
473                 PostReply postReply = createdPostReplies.iterator().next();
474                 assertThat(postReply.getId(), is("reply-id"));
475                 assertThat(postReply.getPostId(), is("post-id"));
476                 assertThat(postReply.getPost().get().getId(), is("post-id"));
477                 assertThat(postReply.getSone().getId(), is("identity"));
478                 assertThat(postReply.getTime(), is(1407197508000L));
479                 assertThat(postReply.getText(), is("reply-text"));
480         }
481
482         @Test
483         public void parsingASoneSucceedsWithoutLikedPostIds() throws SoneException, MalformedURLException {
484                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-ids.xml");
485                 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
486         }
487
488         @Test
489         public void parsingASoneSucceedsWithLikedPostIds() throws SoneException, MalformedURLException {
490                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-ids.xml");
491                 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedPostIds(), is((Set<String>) ImmutableSet.of("liked-post-id")));
492         }
493
494         @Test
495         public void parsingASoneSucceedsWithoutLikedPostReplyIds() throws SoneException, MalformedURLException {
496                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-reply-ids.xml");
497                 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
498         }
499
500         @Test
501         public void parsingASoneSucceedsWithLikedPostReplyIds() throws SoneException, MalformedURLException {
502                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-reply-ids.xml");
503                 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedReplyIds(), is((Set<String>) ImmutableSet.of("liked-post-reply-id")));
504         }
505
506         @Test
507         public void parsingASoneSucceedsWithoutAlbums() throws SoneException, MalformedURLException {
508                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-albums.xml");
509                 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
510         }
511
512         @Test
513         public void parsingASoneFailsWithoutAlbumId() throws SoneException, MalformedURLException {
514                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-id.xml");
515                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
516         }
517
518         @Test
519         public void parsingASoneFailsWithoutAlbumTitle() throws SoneException, MalformedURLException {
520                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-title.xml");
521                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
522         }
523
524         @Test
525         public void parsingASoneSucceedsWithNestedAlbums() throws SoneException, MalformedURLException {
526                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-multiple-albums.xml");
527                 final Sone parsedSone = soneDownloader.parseSone(sone, inputStream);
528                 assertThat(parsedSone, not(nullValue()));
529                 assertThat(parsedSone.getRootAlbum().getAlbums(), hasSize(1));
530                 Album album = parsedSone.getRootAlbum().getAlbums().get(0);
531                 assertThat(album.getId(), is("album-id-1"));
532                 assertThat(album.getTitle(), is("album-title"));
533                 assertThat(album.getDescription(), is("album-description"));
534                 assertThat(album.getAlbums(), hasSize(1));
535                 Album nestedAlbum = album.getAlbums().get(0);
536                 assertThat(nestedAlbum.getId(), is("album-id-2"));
537                 assertThat(nestedAlbum.getTitle(), is("album-title-2"));
538                 assertThat(nestedAlbum.getDescription(), is("album-description-2"));
539                 assertThat(nestedAlbum.getAlbums(), hasSize(0));
540         }
541
542         @Test
543         public void parsingASoneFailsWithInvalidParentAlbumId() throws SoneException, MalformedURLException {
544                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-parent-album-id.xml");
545                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
546         }
547
548         @Test
549         public void parsingASoneSucceedsWithoutImages() throws SoneException, MalformedURLException {
550                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-images.xml");
551                 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
552         }
553
554         @Test
555         public void parsingASoneFailsWithoutImageId() throws SoneException, MalformedURLException {
556                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-id.xml");
557                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
558         }
559
560         @Test
561         public void parsingASoneFailsWithoutImageTime() throws SoneException, MalformedURLException {
562                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-time.xml");
563                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
564         }
565
566         @Test
567         public void parsingASoneFailsWithoutImageKey() throws SoneException, MalformedURLException {
568                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-key.xml");
569                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
570         }
571
572         @Test
573         public void parsingASoneFailsWithoutImageTitle() throws SoneException, MalformedURLException {
574                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-title.xml");
575                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
576         }
577
578         @Test
579         public void parsingASoneFailsWithoutImageWidth() throws SoneException, MalformedURLException {
580                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-width.xml");
581                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
582         }
583
584         @Test
585         public void parsingASoneFailsWithoutImageHeight() throws SoneException, MalformedURLException {
586                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-height.xml");
587                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
588         }
589
590         @Test
591         public void parsingASoneFailsWithInvalidImageWidth() throws SoneException, MalformedURLException {
592                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-width.xml");
593                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
594         }
595
596         @Test
597         public void parsingASoneFailsWithInvalidImageHeight() throws SoneException, MalformedURLException {
598                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-height.xml");
599                 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
600         }
601
602         @Test
603         public void parsingASoneSucceedsWithImage() throws SoneException, MalformedURLException {
604                 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-image.xml");
605                 final Sone sone = soneDownloader.parseSone(this.sone, inputStream);
606                 assertThat(sone, not(nullValue()));
607                 assertThat(sone.getRootAlbum().getAlbums(), hasSize(1));
608                 assertThat(sone.getRootAlbum().getAlbums().get(0).getImages(), hasSize(1));
609                 Image image = sone.getRootAlbum().getAlbums().get(0).getImages().get(0);
610                 assertThat(image.getId(), is("image-id"));
611                 assertThat(image.getCreationTime(), is(1407197508000L));
612                 assertThat(image.getKey(), is("KSK@GPLv3.txt"));
613                 assertThat(image.getTitle(), is("image-title"));
614                 assertThat(image.getDescription(), is("image-description"));
615                 assertThat(image.getWidth(), is(1920));
616                 assertThat(image.getHeight(), is(1080));
617                 assertThat(sone.getProfile().getAvatar(), is("image-id"));
618         }
619
620         @Test
621         public void stoppingTheSoneDownloaderUnregistersTheSone() {
622                 soneDownloader.addSone(sone);
623                 soneDownloader.stop();
624                 verify(freenetInterface).unregisterUsk(sone);
625         }
626
627         @Test
628         public void fetchSoneWithUriDownloadsSoneWithUri() {
629                 SoneDownloader soneDownloader = mock(SoneDownloader.class);
630                 Sone sone = mock(Sone.class);
631                 FreenetURI soneUri = mock(FreenetURI.class);
632                 when(sone.getRequestUri()).thenReturn(soneUri);
633                 FetchSoneWithUri fetchSoneWithUri = soneDownloader.new FetchSoneWithUri(sone);
634                 fetchSoneWithUri.run();
635                 verify(soneDownloader).fetchSone(eq(sone), eq(soneUri));
636         }
637
638         @Test
639         public void fetchSoneDownloadsSone() {
640                 SoneDownloader soneDownloader = mock(SoneDownloader.class);
641                 Sone sone = mock(Sone.class);
642                 FetchSone fetchSone = soneDownloader.new FetchSone(sone);
643                 fetchSone.run();
644                 verify(soneDownloader).fetchSone(eq(sone));
645         }
646
647         @Test
648         public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
649                 soneDownloader.fetchSone(sone);
650                 verify(freenetInterface).fetchUri(requestUri);
651                 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
652                 verify(core, never()).updateSone(any(Sone.class));
653         }
654
655         private void verifyThatSoneStatusWasChangedToDownloadingAndBackTo(SoneStatus soneStatus) {
656                 ArgumentCaptor<SoneStatus> soneStatuses = forClass(SoneStatus.class);
657                 verify(sone, times(2)).setStatus(soneStatuses.capture());
658                 assertThat(soneStatuses.getAllValues().get(0), is(downloading));
659                 assertThat(soneStatuses.getAllValues().get(1), is(soneStatus));
660         }
661
662         @Test
663         public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
664                 when(sone.getTime()).thenReturn(1000L);
665                 soneDownloader.fetchSone(sone);
666                 verify(freenetInterface).fetchUri(requestUri);
667                 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
668                 verify(core, never()).updateSone(any(Sone.class));
669         }
670
671         @Test(expected = NullPointerException.class)
672         public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
673                 when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
674                 try {
675                         soneDownloader.fetchSone(sone);
676                 } finally {
677                         verify(freenetInterface).fetchUri(requestUri);
678                         verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
679                         verify(core, never()).updateSone(any(Sone.class));
680                 }
681         }
682
683         @Test(expected = NullPointerException.class)
684         public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
685                 when(sone.getTime()).thenReturn(1000L);
686                 when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
687                 try {
688                         soneDownloader.fetchSone(sone);
689                 } finally {
690                         verify(freenetInterface).fetchUri(requestUri);
691                         verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
692                         verify(core, never()).updateSone(any(Sone.class));
693                 }
694         }
695
696         @Test
697         public void successfulFetchingOfSoneWithUskRequestUriUpdatesTheCoreWithASone() throws IOException {
698                 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
699                 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
700                 soneDownloader.fetchSone(sone);
701                 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
702         }
703
704         private void verifyThatParsedSoneHasTheSameIdAsTheOriginalSone() {
705                 ArgumentCaptor<Sone> soneCaptor = forClass(Sone.class);
706                 verify(core).updateSone(soneCaptor.capture());
707                 assertThat(soneCaptor.getValue().getId(), is(sone.getId()));
708         }
709
710         @Test
711         public void successfulFetchingOfSoneWithSskRequestUriUpdatesTheCoreWithASone() throws IOException {
712                 when(requestUri.getKeyType()).thenReturn("SSK");
713                 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
714                 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
715                 soneDownloader.fetchSone(sone);
716                 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
717         }
718
719         @Test
720         public void successfulFetchingAnUnknownSoneWithSskRequestUriUpdatesTheCoreWithASone() throws IOException {
721                 when(requestUri.getKeyType()).thenReturn("SSK");
722                 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-with-zero-time.xml"));
723                 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
724                 soneDownloader.fetchSone(sone);
725                 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
726         }
727
728         @Test
729         public void fetchingSoneWithInvalidXmlWillNotUpdateTheCore() throws IOException {
730                 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-not-xml.xml"));
731                 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
732                 soneDownloader.fetchSone(sone);
733                 verify(core, never()).updateSone(any(Sone.class));
734         }
735
736         @Test
737         public void exceptionWhileFetchingSoneWillNotUpdateTheCore() throws IOException {
738                 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
739                 when(sone.getId()).thenThrow(NullPointerException.class);
740                 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
741                 soneDownloader.fetchSone(sone);
742                 verify(core, never()).updateSone(any(Sone.class));
743         }
744
745         @Test
746         public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException {
747                 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
748                 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
749                 soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
750                 verify(core, never()).updateSone(any(Sone.class));
751                 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
752         }
753
754         private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
755                 ClientMetadata clientMetadata = new ClientMetadata("application/xml");
756                 Bucket bucket = mock(Bucket.class);
757                 when(bucket.getInputStream()).thenReturn(inputStream);
758                 FetchResult fetchResult = new FetchResult(clientMetadata, bucket);
759                 return new Fetched(uri, fetchResult);
760         }
761
762 }