1 package net.pterodactylus.sone.core;
3 import static com.google.common.base.Optional.of;
4 import static freenet.keys.InsertableClientSSK.createRandom;
5 import static java.lang.System.currentTimeMillis;
6 import static java.util.UUID.randomUUID;
7 import static java.util.concurrent.TimeUnit.DAYS;
8 import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading;
9 import static net.pterodactylus.sone.data.Sone.SoneStatus.idle;
10 import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.hamcrest.Matchers.hasSize;
13 import static org.hamcrest.Matchers.is;
14 import static org.hamcrest.Matchers.not;
15 import static org.hamcrest.Matchers.notNullValue;
16 import static org.hamcrest.Matchers.nullValue;
17 import static org.mockito.ArgumentCaptor.forClass;
18 import static org.mockito.Matchers.any;
19 import static org.mockito.Matchers.anyLong;
20 import static org.mockito.Matchers.anyString;
21 import static org.mockito.Matchers.eq;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.times;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.net.MalformedURLException;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
39 import net.pterodactylus.sone.core.FreenetInterface.Fetched;
40 import net.pterodactylus.sone.data.Album;
41 import net.pterodactylus.sone.data.Album.Modifier;
42 import net.pterodactylus.sone.data.Client;
43 import net.pterodactylus.sone.data.Image;
44 import net.pterodactylus.sone.data.Post;
45 import net.pterodactylus.sone.data.PostReply;
46 import net.pterodactylus.sone.data.Profile;
47 import net.pterodactylus.sone.data.Sone;
48 import net.pterodactylus.sone.data.Sone.SoneStatus;
49 import net.pterodactylus.sone.database.AlbumBuilder;
50 import net.pterodactylus.sone.database.ImageBuilder;
51 import net.pterodactylus.sone.database.PostBuilder;
52 import net.pterodactylus.sone.database.PostReplyBuilder;
53 import net.pterodactylus.sone.database.SoneBuilder;
54 import net.pterodactylus.sone.database.memory.MemorySoneBuilder;
55 import net.pterodactylus.sone.freenet.wot.Identity;
57 import freenet.client.ClientMetadata;
58 import freenet.client.FetchResult;
59 import freenet.client.async.USKCallback;
60 import freenet.crypt.DummyRandomSource;
61 import freenet.keys.ClientSSK;
62 import freenet.keys.FreenetURI;
63 import freenet.keys.InsertableClientSSK;
64 import freenet.support.api.Bucket;
66 import com.google.common.base.Optional;
67 import com.google.common.collect.ArrayListMultimap;
68 import com.google.common.collect.ImmutableSet;
69 import com.google.common.collect.ListMultimap;
70 import org.junit.Before;
71 import org.junit.Test;
72 import org.mockito.ArgumentCaptor;
73 import org.mockito.invocation.InvocationOnMock;
74 import org.mockito.stubbing.Answer;
77 * Unit test for {@link SoneDownloaderImpl} and its subclasses.
79 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
81 public class SoneDownloaderTest {
83 private final Core core = mock(Core.class);
84 private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
85 private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface);
86 private FreenetURI requestUri = mock(FreenetURI.class);
87 private Sone sone = mock(Sone.class);
88 private final PostBuilder postBuilder = mock(PostBuilder.class);
89 private final List<Post> createdPosts = new ArrayList<Post>();
90 private Post post = mock(Post.class);
91 private final PostReplyBuilder postReplyBuilder = mock(PostReplyBuilder.class);
92 private final Set<PostReply> createdPostReplies = new HashSet<PostReply>();
93 private PostReply postReply = mock(PostReply.class);
94 private final AlbumBuilder albumBuilder = mock(AlbumBuilder.class);
95 private final ListMultimap<Album, Album> nestedAlbums = ArrayListMultimap.create();
96 private final ListMultimap<Album, Image> albumImages = ArrayListMultimap.create();
97 private Album album = mock(Album.class);
98 private final Map<String, Album> albums = new HashMap<String, Album>();
99 private final ImageBuilder imageBuilder = mock(ImageBuilder.class);
100 private Image image = mock(Image.class);
101 private final Map<String, Image> images = new HashMap<String, Image>();
104 public void setupSone() {
105 Sone sone = SoneDownloaderTest.this.sone;
106 Identity identity = mock(Identity.class);
107 InsertableClientSSK clientSSK = createRandom(new DummyRandomSource(), "WoT");
108 when(identity.getRequestUri()).thenReturn(clientSSK.getURI().toString());
109 when(identity.getId()).thenReturn("identity");
110 when(sone.getId()).thenReturn("identity");
111 when(sone.getIdentity()).thenReturn(identity);
112 requestUri = clientSSK.getURI().setKeyType("USK").setDocName("Sone");
113 when(sone.getRequestUri()).thenAnswer(new Answer<FreenetURI>() {
115 public FreenetURI answer(InvocationOnMock invocation)
120 when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
123 private void setupSoneAsUnknown() {
124 when(sone.getTime()).thenReturn(0L);
128 public void setupSoneBuilder() {
129 when(core.soneBuilder()).thenAnswer(new Answer<SoneBuilder>() {
131 public SoneBuilder answer(InvocationOnMock invocation) {
132 return new MemorySoneBuilder();
138 public void setupPost() {
139 when(post.getRecipientId()).thenReturn(Optional.<String>absent());
143 public void setupPostBuilder() {
144 when(postBuilder.withId(anyString())).thenAnswer(new Answer<PostBuilder>() {
146 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
147 when(post.getId()).thenReturn((String) invocation.getArguments()[0]);
151 when(postBuilder.from(anyString())).thenAnswer(new Answer<PostBuilder>() {
153 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
154 final Sone sone = mock(Sone.class);
155 when(sone.getId()).thenReturn((String) invocation.getArguments()[0]);
156 when(post.getSone()).thenReturn(sone);
160 when(postBuilder.withTime(anyLong())).thenAnswer(new Answer<PostBuilder>() {
162 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
163 when(post.getTime()).thenReturn((Long) invocation.getArguments()[0]);
167 when(postBuilder.withText(anyString())).thenAnswer(new Answer<PostBuilder>() {
169 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
170 when(post.getText()).thenReturn((String) invocation.getArguments()[0]);
174 when(postBuilder.to(anyString())).thenAnswer(new Answer<PostBuilder>() {
176 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
177 when(post.getRecipientId()).thenReturn(of((String) invocation.getArguments()[0]));
181 when(postBuilder.build()).thenAnswer(new Answer<Post>() {
183 public Post answer(InvocationOnMock invocation) throws Throwable {
184 Post post = SoneDownloaderTest.this.post;
185 SoneDownloaderTest.this.post = mock(Post.class);
187 createdPosts.add(post);
191 when(core.postBuilder()).thenReturn(postBuilder);
195 public void setupPostReplyBuilder() {
196 when(postReplyBuilder.withId(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
198 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
199 when(postReply.getId()).thenReturn((String) invocation.getArguments()[0]);
200 return postReplyBuilder;
203 when(postReplyBuilder.from(anyString())).thenAnswer(
204 new Answer<PostReplyBuilder>() {
206 public PostReplyBuilder answer(
207 InvocationOnMock invocation) throws Throwable {
208 Sone sone = when(mock(Sone.class).getId()).thenReturn(
209 (String) invocation.getArguments()[0])
211 when(postReply.getSone()).thenReturn(sone);
212 return postReplyBuilder;
215 when(postReplyBuilder.to(anyString())).thenAnswer(
216 new Answer<PostReplyBuilder>() {
218 public PostReplyBuilder answer(
219 InvocationOnMock invocation) throws Throwable {
220 when(postReply.getPostId()).thenReturn(
221 (String) invocation.getArguments()[0]);
222 Post post = when(mock(Post.class).getId()).thenReturn(
223 (String) invocation.getArguments()[0])
225 when(postReply.getPost()).thenReturn(of(post));
226 return postReplyBuilder;
229 when(postReplyBuilder.withTime(anyLong())).thenAnswer(
230 new Answer<PostReplyBuilder>() {
232 public PostReplyBuilder answer(
233 InvocationOnMock invocation) throws Throwable {
234 when(postReply.getTime()).thenReturn(
235 (Long) invocation.getArguments()[0]);
236 return postReplyBuilder;
239 when(postReplyBuilder.withText(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
241 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
242 when(postReply.getText()).thenReturn((String) invocation.getArguments()[0]);
243 return postReplyBuilder;
246 when(postReplyBuilder.build()).thenAnswer(new Answer<PostReply>() {
248 public PostReply answer(InvocationOnMock invocation) throws Throwable {
249 PostReply postReply = SoneDownloaderTest.this.postReply;
250 createdPostReplies.add(postReply);
251 SoneDownloaderTest.this.postReply = mock(PostReply.class);
255 when(core.postReplyBuilder()).thenReturn(postReplyBuilder);
259 public void setupAlbum() {
260 final Album album = SoneDownloaderTest.this.album;
261 when(album.getAlbumImage()).thenReturn(mock(Image.class));
262 doAnswer(new Answer<Void>() {
264 public Void answer(InvocationOnMock invocation) {
265 nestedAlbums.put(album, (Album) invocation.getArguments()[0]);
268 }).when(album).addAlbum(any(Album.class));
269 doAnswer(new Answer<Void>() {
271 public Void answer(InvocationOnMock invocation) {
272 albumImages.put(album, (Image) invocation.getArguments()[0]);
275 }).when(album).addImage(any(Image.class));
276 when(album.getAlbums()).thenAnswer(new Answer<List<Album>>() {
278 public List<Album> answer(InvocationOnMock invocation) {
279 return nestedAlbums.get(album);
282 when(album.getImages()).thenAnswer(new Answer<List<Image>>() {
284 public List<Image> answer(InvocationOnMock invocation) {
285 return albumImages.get(album);
288 final Modifier albumModifier = new Modifier() {
289 private String title = album.getTitle();
290 private String description = album.getDescription();
291 private String imageId = album.getAlbumImage().getId();
294 public Modifier setTitle(String title) {
300 public Modifier setDescription(String description) {
301 this.description = description;
306 public Modifier setAlbumImage(String imageId) {
307 this.imageId = imageId;
312 public Album update() throws IllegalStateException {
313 when(album.getTitle()).thenReturn(title);
314 when(album.getDescription()).thenReturn(description);
315 Image image = mock(Image.class);
316 when(image.getId()).thenReturn(imageId);
317 when(album.getAlbumImage()).thenReturn(image);
321 when(album.modify()).thenReturn(albumModifier);
325 public void setupAlbumBuilder() {
326 when(albumBuilder.withId(anyString())).thenAnswer(new Answer<AlbumBuilder>() {
328 public AlbumBuilder answer(InvocationOnMock invocation) {
329 when(album.getId()).thenReturn((String) invocation.getArguments()[0]);
333 when(albumBuilder.randomId()).thenAnswer(new Answer<AlbumBuilder>() {
335 public AlbumBuilder answer(InvocationOnMock invocation) {
336 when(album.getId()).thenReturn(randomUUID().toString());
340 when(albumBuilder.by(any(Sone.class))).thenAnswer(new Answer<AlbumBuilder>() {
342 public AlbumBuilder answer(InvocationOnMock invocation) {
343 when(album.getSone()).thenReturn((Sone) invocation.getArguments()[0]);
347 when(albumBuilder.build()).thenAnswer(new Answer<Album>() {
349 public Album answer(InvocationOnMock invocation) {
350 Album album = SoneDownloaderTest.this.album;
351 albums.put(album.getId(), album);
352 SoneDownloaderTest.this.album = mock(Album.class);
357 when(core.albumBuilder()).thenReturn(albumBuilder);
361 public void setupAlbums() {
362 when(core.getAlbum(anyString())).thenAnswer(new Answer<Album>() {
364 public Album answer(InvocationOnMock invocation)
366 return albums.get(invocation.getArguments()[0]);
372 public void setupImage() {
373 final Image image = SoneDownloaderTest.this.image;
374 Image.Modifier modifier = new Image.Modifier() {
375 private Sone sone = image.getSone();
376 private long creationTime = image.getCreationTime();
377 private String key = image.getKey();
378 private String title = image.getTitle();
379 private String description = image.getDescription();
380 private int width = image.getWidth();
381 private int height = image.getHeight();
384 public Image.Modifier setSone(Sone sone) {
390 public Image.Modifier setCreationTime(long creationTime) {
391 this.creationTime = creationTime;
396 public Image.Modifier setKey(String key) {
402 public Image.Modifier setTitle(String title) {
408 public Image.Modifier setDescription(String description) {
409 this.description = description;
414 public Image.Modifier setWidth(int width) {
420 public Image.Modifier setHeight(int height) {
421 this.height = height;
426 public Image update() throws IllegalStateException {
427 when(image.getSone()).thenReturn(sone);
428 when(image.getCreationTime()).thenReturn(creationTime);
429 when(image.getKey()).thenReturn(key);
430 when(image.getTitle()).thenReturn(title);
431 when(image.getDescription()).thenReturn(description);
432 when(image.getWidth()).thenReturn(width);
433 when(image.getHeight()).thenReturn(height);
437 when(image.getSone()).thenReturn(sone);
438 when(image.modify()).thenReturn(modifier);
442 public void setupImageBuilder() {
443 when(imageBuilder.randomId()).thenAnswer(new Answer<ImageBuilder>() {
445 public ImageBuilder answer(InvocationOnMock invocation) {
446 when(image.getId()).thenReturn(randomUUID().toString());
450 when(imageBuilder.withId(anyString())).thenAnswer(new Answer<ImageBuilder>() {
452 public ImageBuilder answer(InvocationOnMock invocation) {
453 when(image.getId()).thenReturn(
454 (String) invocation.getArguments()[0]);
458 when(imageBuilder.build()).thenAnswer(new Answer<Image>() {
460 public Image answer(InvocationOnMock invocation) {
461 Image image = SoneDownloaderTest.this.image;
462 images.put(image.getId(), image);
463 SoneDownloaderTest.this.image = mock(Image.class);
468 when(core.imageBuilder()).thenReturn(imageBuilder);
472 public void setupImages() {
473 when(core.getImage(anyString())).thenAnswer(new Answer<Image>() {
475 public Image answer(InvocationOnMock invocation)
477 return images.get(invocation.getArguments()[0]);
483 public void addingASoneWillRegisterItsKey() {
484 soneDownloader.addSone(sone);
485 verify(freenetInterface).registerActiveUsk(eq(sone.getRequestUri()), any(
487 verify(freenetInterface, never()).unregisterUsk(sone);
491 public void addingASoneTwiceWillAlsoDeregisterItsKey() {
492 soneDownloader.addSone(sone);
493 soneDownloader.addSone(sone);
494 verify(freenetInterface, times(2)).registerActiveUsk(eq(
495 sone.getRequestUri()), any(USKCallback.class));
496 verify(freenetInterface).unregisterUsk(sone);
500 public void parsingASoneFailsWhenDocumentIsNotXml() throws SoneException {
501 InputStream inputStream = getClass().getResourceAsStream("sone-parser-not-xml.xml");
502 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
506 public void parsingASoneFailsWhenDocumentHasNegativeProtocolVersion() throws SoneException {
507 InputStream inputStream = getClass().getResourceAsStream("sone-parser-negative-protocol-version.xml");
508 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
512 public void parsingASoneFailsWhenProtocolVersionIsTooLarge() throws SoneException {
513 InputStream inputStream = getClass().getResourceAsStream("sone-parser-too-large-protocol-version.xml");
514 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
518 public void parsingASoneFailsWhenThereIsNoTime() throws SoneException {
519 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-time.xml");
520 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
524 public void parsingASoneFailsWhenTimeIsNotNumeric() throws SoneException {
525 InputStream inputStream = getClass().getResourceAsStream("sone-parser-time-not-numeric.xml");
526 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
530 public void parsingASoneFailsWhenProfileIsMissing() throws SoneException {
531 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-profile.xml");
532 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
536 public void parsingASoneFailsWhenProfileFieldIsMissingAFieldName() throws SoneException {
537 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-missing-field-name.xml");
538 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
542 public void parsingASoneFailsWhenProfileFieldNameIsEmpty() throws SoneException {
543 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-empty-field-name.xml");
544 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
548 public void parsingASoneFailsWhenProfileFieldNameIsNotUnique() throws SoneException {
549 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-duplicate-field-name.xml");
550 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
554 public void parsingASoneSucceedsWithoutPayload() throws SoneException {
555 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
556 assertThat(soneDownloader.parseSone(sone, inputStream).getTime(), is(
561 public void parsingASoneSucceedsWithoutProtocolVersion() throws SoneException {
562 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-protocol-version.xml");
563 assertThat(soneDownloader.parseSone(sone, inputStream), not(
568 public void parsingASoneFailsWithMissingClientName() throws SoneException {
569 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-name.xml");
570 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
574 public void parsingASoneFailsWithMissingClientVersion() throws SoneException {
575 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-version.xml");
576 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
580 public void parsingASoneSucceedsWithClientInfo() throws SoneException {
581 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-client-info.xml");
582 assertThat(soneDownloader.parseSone(sone, inputStream).getClient(), is(new Client("some-client", "some-version")));
586 public void parsingASoneSucceedsWithProfile() throws SoneException, MalformedURLException {
587 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-profile.xml");
588 final Profile profile = soneDownloader.parseSone(sone, inputStream).getProfile();
589 assertThat(profile.getFirstName(), is("first"));
590 assertThat(profile.getMiddleName(), is("middle"));
591 assertThat(profile.getLastName(), is("last"));
592 assertThat(profile.getBirthDay(), is(18));
593 assertThat(profile.getBirthMonth(), is(12));
594 assertThat(profile.getBirthYear(), is(1976));
598 public void parsingASoneSucceedsWithoutProfileFields() throws SoneException, MalformedURLException {
599 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-fields.xml");
600 assertThat(soneDownloader.parseSone(sone, inputStream), notNullValue());
604 public void parsingASoneFailsWithoutPostId() throws SoneException, MalformedURLException {
605 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-id.xml");
606 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
610 public void parsingASoneFailsWithoutPostTime() throws SoneException, MalformedURLException {
611 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-time.xml");
612 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
616 public void parsingASoneFailsWithoutPostText() throws SoneException, MalformedURLException {
617 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-text.xml");
618 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
622 public void parsingASoneFailsWithInvalidPostTime() throws SoneException, MalformedURLException {
623 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-time.xml");
624 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
628 public void parsingASoneSucceedsWithValidPostTime() throws SoneException, MalformedURLException {
629 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-time.xml");
630 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
631 assertThat(posts, is(createdPosts));
632 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
633 assertThat(posts.get(0).getId(), is("post-id"));
634 assertThat(posts.get(0).getTime(), is(1407197508000L));
635 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
636 assertThat(posts.get(0).getText(), is("text"));
640 public void parsingASoneSucceedsWithRecipient() throws SoneException, MalformedURLException {
641 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-recipient.xml");
642 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
643 assertThat(posts, is(createdPosts));
644 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
645 assertThat(posts.get(0).getId(), is("post-id"));
646 assertThat(posts.get(0).getTime(), is(1407197508000L));
647 assertThat(posts.get(0).getRecipientId(), is(of(
648 "1234567890123456789012345678901234567890123")));
649 assertThat(posts.get(0).getText(), is("text"));
653 public void parsingASoneSucceedsWithInvalidRecipient() throws SoneException, MalformedURLException {
654 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-recipient.xml");
655 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
656 assertThat(posts, is(createdPosts));
657 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
658 assertThat(posts.get(0).getId(), is("post-id"));
659 assertThat(posts.get(0).getTime(), is(1407197508000L));
660 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
661 assertThat(posts.get(0).getText(), is("text"));
665 public void parsingASoneFailsWithoutPostReplyId() throws SoneException, MalformedURLException {
666 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-id.xml");
667 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
671 public void parsingASoneFailsWithoutPostReplyPostId() throws SoneException, MalformedURLException {
672 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-post-id.xml");
673 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
677 public void parsingASoneFailsWithoutPostReplyTime() throws SoneException, MalformedURLException {
678 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-time.xml");
679 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
683 public void parsingASoneFailsWithoutPostReplyText() throws SoneException, MalformedURLException {
684 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-text.xml");
685 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
689 public void parsingASoneFailsWithInvalidPostReplyTime() throws SoneException, MalformedURLException {
690 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-reply-time.xml");
691 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
695 public void parsingASoneSucceedsWithValidPostReplyTime() throws SoneException, MalformedURLException {
696 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-reply-time.xml");
697 final Set<PostReply> postReplies = soneDownloader.parseSone(sone, inputStream).getReplies();
698 assertThat(postReplies, is(createdPostReplies));
699 PostReply postReply = createdPostReplies.iterator().next();
700 assertThat(postReply.getId(), is("reply-id"));
701 assertThat(postReply.getPostId(), is("post-id"));
702 assertThat(postReply.getPost().get().getId(), is("post-id"));
703 assertThat(postReply.getSone().getId(), is("identity"));
704 assertThat(postReply.getTime(), is(1407197508000L));
705 assertThat(postReply.getText(), is("reply-text"));
709 public void parsingASoneSucceedsWithoutLikedPostIds() throws SoneException, MalformedURLException {
710 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-ids.xml");
711 assertThat(soneDownloader.parseSone(sone, inputStream), not(
716 public void parsingASoneSucceedsWithLikedPostIds() throws SoneException, MalformedURLException {
717 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-ids.xml");
718 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedPostIds(), is(
719 (Set<String>) ImmutableSet.of("liked-post-id")));
723 public void parsingASoneSucceedsWithoutLikedPostReplyIds() throws SoneException, MalformedURLException {
724 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-reply-ids.xml");
725 assertThat(soneDownloader.parseSone(sone, inputStream), not(
730 public void parsingASoneSucceedsWithLikedPostReplyIds() throws SoneException, MalformedURLException {
731 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-reply-ids.xml");
732 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedReplyIds(), is(
733 (Set<String>) ImmutableSet.of("liked-post-reply-id")));
737 public void parsingASoneSucceedsWithoutAlbums() throws SoneException, MalformedURLException {
738 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-albums.xml");
739 assertThat(soneDownloader.parseSone(sone, inputStream), not(
744 public void parsingASoneFailsWithoutAlbumId() throws SoneException, MalformedURLException {
745 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-id.xml");
746 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
750 public void parsingASoneFailsWithoutAlbumTitle() throws SoneException, MalformedURLException {
751 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-title.xml");
752 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
756 public void parsingASoneSucceedsWithNestedAlbums() throws SoneException, MalformedURLException {
757 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-multiple-albums.xml");
758 final Sone parsedSone = soneDownloader.parseSone(sone, inputStream);
759 assertThat(parsedSone, not(nullValue()));
760 assertThat(parsedSone.getRootAlbum().getAlbums(), hasSize(1));
761 Album album = parsedSone.getRootAlbum().getAlbums().get(0);
762 assertThat(album.getId(), is("album-id-1"));
763 assertThat(album.getTitle(), is("album-title"));
764 assertThat(album.getDescription(), is("album-description"));
765 assertThat(album.getAlbums(), hasSize(1));
766 Album nestedAlbum = album.getAlbums().get(0);
767 assertThat(nestedAlbum.getId(), is("album-id-2"));
768 assertThat(nestedAlbum.getTitle(), is("album-title-2"));
769 assertThat(nestedAlbum.getDescription(), is("album-description-2"));
770 assertThat(nestedAlbum.getAlbums(), hasSize(0));
774 public void parsingASoneFailsWithInvalidParentAlbumId() throws SoneException, MalformedURLException {
775 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-parent-album-id.xml");
776 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
780 public void parsingASoneSucceedsWithoutImages() throws SoneException, MalformedURLException {
781 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-images.xml");
782 assertThat(soneDownloader.parseSone(sone, inputStream), not(
787 public void parsingASoneFailsWithoutImageId() throws SoneException, MalformedURLException {
788 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-id.xml");
789 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
793 public void parsingASoneFailsWithoutImageTime() throws SoneException, MalformedURLException {
794 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-time.xml");
795 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
799 public void parsingASoneFailsWithoutImageKey() throws SoneException, MalformedURLException {
800 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-key.xml");
801 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
805 public void parsingASoneFailsWithoutImageTitle() throws SoneException, MalformedURLException {
806 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-title.xml");
807 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
811 public void parsingASoneFailsWithoutImageWidth() throws SoneException, MalformedURLException {
812 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-width.xml");
813 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
817 public void parsingASoneFailsWithoutImageHeight() throws SoneException, MalformedURLException {
818 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-height.xml");
819 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
823 public void parsingASoneFailsWithInvalidImageWidth() throws SoneException, MalformedURLException {
824 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-width.xml");
825 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
829 public void parsingASoneFailsWithInvalidImageHeight() throws SoneException, MalformedURLException {
830 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-height.xml");
831 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
835 public void parsingASoneSucceedsWithImage() throws SoneException, MalformedURLException {
836 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-image.xml");
837 final Sone sone = soneDownloader.parseSone(this.sone, inputStream);
838 assertThat(sone, not(nullValue()));
839 assertThat(sone.getRootAlbum().getAlbums(), hasSize(1));
840 assertThat(sone.getRootAlbum().getAlbums().get(0).getImages(), hasSize(1));
841 Image image = sone.getRootAlbum().getAlbums().get(0).getImages().get(0);
842 assertThat(image.getId(), is("image-id"));
843 assertThat(image.getCreationTime(), is(1407197508000L));
844 assertThat(image.getKey(), is("KSK@GPLv3.txt"));
845 assertThat(image.getTitle(), is("image-title"));
846 assertThat(image.getDescription(), is("image-description"));
847 assertThat(image.getWidth(), is(1920));
848 assertThat(image.getHeight(), is(1080));
849 assertThat(sone.getProfile().getAvatar(), is("image-id"));
853 public void stoppingTheSoneDownloaderUnregistersTheSone() {
854 soneDownloader.addSone(sone);
855 soneDownloader.stop();
856 verify(freenetInterface).unregisterUsk(sone);
860 public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
861 FreenetURI finalRequestUri = requestUri.sskForUSK()
862 .setMetaString(new String[] { "sone.xml" });
863 setupSoneAsUnknown();
864 soneDownloader.fetchSoneAction(sone).run();
865 verify(freenetInterface).fetchUri(finalRequestUri);
866 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
867 verify(core, never()).updateSone(any(Sone.class));
870 private void verifyThatSoneStatusWasChangedToDownloadingAndBackTo(SoneStatus soneStatus) {
871 ArgumentCaptor<SoneStatus> soneStatuses = forClass(SoneStatus.class);
872 verify(sone, times(2)).setStatus(soneStatuses.capture());
873 assertThat(soneStatuses.getAllValues().get(0), is(downloading));
874 assertThat(soneStatuses.getAllValues().get(1), is(soneStatus));
878 public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
879 FreenetURI finalRequestUri = requestUri.sskForUSK()
880 .setMetaString(new String[] { "sone.xml" });
881 soneDownloader.fetchSoneAction(sone).run();
882 verify(freenetInterface).fetchUri(finalRequestUri);
883 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
884 verify(core, never()).updateSone(any(Sone.class));
887 @Test(expected = NullPointerException.class)
888 public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
889 FreenetURI finalRequestUri = requestUri.sskForUSK()
890 .setMetaString(new String[] { "sone.xml" });
891 setupSoneAsUnknown();
892 when(freenetInterface.fetchUri(finalRequestUri)).thenThrow(NullPointerException.class);
894 soneDownloader.fetchSoneAction(sone).run();
896 verify(freenetInterface).fetchUri(finalRequestUri);
897 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
898 verify(core, never()).updateSone(any(Sone.class));
902 @Test(expected = NullPointerException.class)
903 public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
904 FreenetURI finalRequestUri = requestUri.sskForUSK()
905 .setMetaString(new String[] { "sone.xml" });
906 when(freenetInterface.fetchUri(finalRequestUri)).thenThrow( NullPointerException.class);
908 soneDownloader.fetchSoneAction(sone).run();
910 verify(freenetInterface).fetchUri(finalRequestUri);
911 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
912 verify(core, never()).updateSone(any(Sone.class));
917 public void successfulFetchingOfSoneWithUskRequestUriUpdatesTheCoreWithASone() throws IOException {
918 FreenetURI finalRequestUri = requestUri.sskForUSK()
919 .setMetaString(new String[] { "sone.xml" });
920 final Fetched fetchResult = createFetchResult(finalRequestUri,
921 getClass().getResourceAsStream("sone-parser-no-payload.xml"));
922 when(freenetInterface.fetchUri(finalRequestUri)).thenReturn(fetchResult);
923 soneDownloader.fetchSoneAction(sone).run();
924 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
927 private void verifyThatParsedSoneHasTheSameIdAsTheOriginalSone() {
928 ArgumentCaptor<Sone> soneCaptor = forClass(Sone.class);
929 verify(core).updateSone(soneCaptor.capture());
930 assertThat(soneCaptor.getValue().getId(), is(sone.getId()));
934 public void fetchingSoneWithInvalidXmlWillNotUpdateTheCore() throws IOException {
935 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-not-xml.xml"));
936 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
937 soneDownloader.fetchSoneAction(sone).run();
938 verify(core, never()).updateSone(any(Sone.class));
942 public void exceptionWhileFetchingSoneWillNotUpdateTheCore() throws IOException {
943 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
944 when(core.soneBuilder()).thenReturn(null);
945 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
946 soneDownloader.fetchSoneAction(sone).run();
947 verify(core, never()).updateSone(any(Sone.class));
951 public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException {
952 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
953 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
954 soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
955 verify(core, never()).updateSone(any(Sone.class));
956 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
959 private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
960 ClientMetadata clientMetadata = new ClientMetadata("application/xml");
961 Bucket bucket = mock(Bucket.class);
962 when(bucket.getInputStream()).thenReturn(inputStream);
963 FetchResult fetchResult = new FetchResult(clientMetadata, bucket);
964 return new Fetched(uri, fetchResult);