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