1 package net.pterodactylus.sone.core;
3 import static com.google.common.base.Optional.of;
4 import static java.lang.System.currentTimeMillis;
5 import static java.util.UUID.randomUUID;
6 import static java.util.concurrent.TimeUnit.DAYS;
7 import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading;
8 import static net.pterodactylus.sone.data.Sone.SoneStatus.idle;
9 import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown;
10 import static org.hamcrest.MatcherAssert.assertThat;
11 import static org.hamcrest.Matchers.hasSize;
12 import static org.hamcrest.Matchers.is;
13 import static org.hamcrest.Matchers.not;
14 import static org.hamcrest.Matchers.notNullValue;
15 import static org.hamcrest.Matchers.nullValue;
16 import static org.mockito.ArgumentCaptor.forClass;
17 import static org.mockito.Matchers.any;
18 import static org.mockito.Matchers.anyLong;
19 import static org.mockito.Matchers.anyString;
20 import static org.mockito.Matchers.eq;
21 import static org.mockito.Mockito.doAnswer;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.times;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.net.MalformedURLException;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
38 import net.pterodactylus.sone.core.FreenetInterface.Fetched;
39 import net.pterodactylus.sone.data.Album;
40 import net.pterodactylus.sone.data.Album.Modifier;
41 import net.pterodactylus.sone.data.Client;
42 import net.pterodactylus.sone.data.Image;
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.AlbumBuilder;
49 import net.pterodactylus.sone.database.ImageBuilder;
50 import net.pterodactylus.sone.database.PostBuilder;
51 import net.pterodactylus.sone.database.PostReplyBuilder;
52 import net.pterodactylus.sone.freenet.wot.Identity;
54 import freenet.client.ClientMetadata;
55 import freenet.client.FetchResult;
56 import freenet.client.async.USKCallback;
57 import freenet.keys.FreenetURI;
58 import freenet.support.api.Bucket;
60 import com.google.common.base.Optional;
61 import com.google.common.collect.ArrayListMultimap;
62 import com.google.common.collect.ImmutableSet;
63 import com.google.common.collect.ListMultimap;
64 import org.junit.Before;
65 import org.junit.Test;
66 import org.mockito.ArgumentCaptor;
67 import org.mockito.invocation.InvocationOnMock;
68 import org.mockito.stubbing.Answer;
71 * Unit test for {@link SoneDownloaderImpl} and its subclasses.
73 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
75 public class SoneDownloaderTest {
77 private final Core core = mock(Core.class);
78 private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
79 private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface);
80 private final FreenetURI requestUri = mock(FreenetURI.class);
81 private final Sone sone = mock(Sone.class);
82 private final PostBuilder postBuilder = mock(PostBuilder.class);
83 private final List<Post> createdPosts = new ArrayList<Post>();
84 private Post post = mock(Post.class);
85 private final PostReplyBuilder postReplyBuilder = mock(PostReplyBuilder.class);
86 private final Set<PostReply> createdPostReplies = new HashSet<PostReply>();
87 private PostReply postReply = mock(PostReply.class);
88 private final AlbumBuilder albumBuilder = mock(AlbumBuilder.class);
89 private final ListMultimap<Album, Album> nestedAlbums = ArrayListMultimap.create();
90 private final ListMultimap<Album, Image> albumImages = ArrayListMultimap.create();
91 private Album album = mock(Album.class);
92 private final Map<String, Album> albums = new HashMap<String, Album>();
93 private final ImageBuilder imageBuilder = mock(ImageBuilder.class);
94 private Image image = mock(Image.class);
95 private final Map<String, Image> images = new HashMap<String, Image>();
98 public void setupSone() {
99 Identity identity = mock(Identity.class);
100 when(identity.getId()).thenReturn("identity");
101 when(sone.getId()).thenReturn("identity");
102 when(sone.getIdentity()).thenReturn(identity);
103 when(sone.getRequestUri()).thenReturn(requestUri);
104 when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
107 private void setupSoneAsUnknown() {
108 when(sone.getTime()).thenReturn(0L);
112 public void setupRequestUri() {
113 when(requestUri.setKeyType(anyString())).thenReturn(requestUri);
114 when(requestUri.sskForUSK()).thenReturn(requestUri);
115 when(requestUri.setDocName(anyString())).thenReturn(requestUri);
116 when(requestUri.setMetaString(any(String[].class))).thenReturn(requestUri);
117 when(requestUri.getKeyType()).thenReturn("USK");
121 public void setupPost() {
122 when(post.getRecipientId()).thenReturn(Optional.<String>absent());
126 public void setupPostBuilder() {
127 when(postBuilder.withId(anyString())).thenAnswer(new Answer<PostBuilder>() {
129 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
130 when(post.getId()).thenReturn((String) invocation.getArguments()[0]);
134 when(postBuilder.from(anyString())).thenAnswer(new Answer<PostBuilder>() {
136 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
137 final Sone sone = mock(Sone.class);
138 when(sone.getId()).thenReturn((String) invocation.getArguments()[0]);
139 when(post.getSone()).thenReturn(sone);
143 when(postBuilder.withTime(anyLong())).thenAnswer(new Answer<PostBuilder>() {
145 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
146 when(post.getTime()).thenReturn((Long) invocation.getArguments()[0]);
150 when(postBuilder.withText(anyString())).thenAnswer(new Answer<PostBuilder>() {
152 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
153 when(post.getText()).thenReturn((String) invocation.getArguments()[0]);
157 when(postBuilder.to(anyString())).thenAnswer(new Answer<PostBuilder>() {
159 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
160 when(post.getRecipientId()).thenReturn(of((String) invocation.getArguments()[0]));
164 when(postBuilder.build()).thenAnswer(new Answer<Post>() {
166 public Post answer(InvocationOnMock invocation) throws Throwable {
167 Post post = SoneDownloaderTest.this.post;
168 SoneDownloaderTest.this.post = mock(Post.class);
170 createdPosts.add(post);
174 when(core.postBuilder()).thenReturn(postBuilder);
178 public void setupPostReplyBuilder() {
179 when(postReplyBuilder.withId(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
181 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
182 when(postReply.getId()).thenReturn((String) invocation.getArguments()[0]);
183 return postReplyBuilder;
186 when(postReplyBuilder.from(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
188 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
189 Sone sone = when(mock(Sone.class).getId()).thenReturn((String) invocation.getArguments()[0]).getMock();
190 when(postReply.getSone()).thenReturn(sone);
191 return postReplyBuilder;
194 when(postReplyBuilder.to(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
196 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
197 when(postReply.getPostId()).thenReturn((String) invocation.getArguments()[0]);
198 Post post = when(mock(Post.class).getId()).thenReturn((String) invocation.getArguments()[0]).getMock();
199 when(postReply.getPost()).thenReturn(of(post));
200 return postReplyBuilder;
203 when(postReplyBuilder.withTime(anyLong())).thenAnswer(new Answer<PostReplyBuilder>() {
205 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
206 when(postReply.getTime()).thenReturn((Long) invocation.getArguments()[0]);
207 return postReplyBuilder;
210 when(postReplyBuilder.withText(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
212 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
213 when(postReply.getText()).thenReturn((String) invocation.getArguments()[0]);
214 return postReplyBuilder;
217 when(postReplyBuilder.build()).thenAnswer(new Answer<PostReply>() {
219 public PostReply answer(InvocationOnMock invocation) throws Throwable {
220 PostReply postReply = SoneDownloaderTest.this.postReply;
221 createdPostReplies.add(postReply);
222 SoneDownloaderTest.this.postReply = mock(PostReply.class);
226 when(core.postReplyBuilder()).thenReturn(postReplyBuilder);
230 public void setupAlbum() {
231 final Album album = SoneDownloaderTest.this.album;
232 when(album.getAlbumImage()).thenReturn(mock(Image.class));
233 doAnswer(new Answer<Void>() {
235 public Void answer(InvocationOnMock invocation) {
236 nestedAlbums.put(album, (Album) invocation.getArguments()[0]);
239 }).when(album).addAlbum(any(Album.class));
240 doAnswer(new Answer<Void>() {
242 public Void answer(InvocationOnMock invocation) {
243 albumImages.put(album, (Image) invocation.getArguments()[0]);
246 }).when(album).addImage(any(Image.class));
247 when(album.getAlbums()).thenAnswer(new Answer<List<Album>>() {
249 public List<Album> answer(InvocationOnMock invocation) {
250 return nestedAlbums.get(album);
253 when(album.getImages()).thenAnswer(new Answer<List<Image>>() {
255 public List<Image> answer(InvocationOnMock invocation) {
256 return albumImages.get(album);
259 final Modifier albumModifier = new Modifier() {
260 private String title = album.getTitle();
261 private String description = album.getDescription();
262 private String imageId = album.getAlbumImage().getId();
265 public Modifier setTitle(String title) {
271 public Modifier setDescription(String description) {
272 this.description = description;
277 public Modifier setAlbumImage(String imageId) {
278 this.imageId = imageId;
283 public Album update() throws IllegalStateException {
284 when(album.getTitle()).thenReturn(title);
285 when(album.getDescription()).thenReturn(description);
286 Image image = mock(Image.class);
287 when(image.getId()).thenReturn(imageId);
288 when(album.getAlbumImage()).thenReturn(image);
292 when(album.modify()).thenReturn(albumModifier);
296 public void setupAlbumBuilder() {
297 when(albumBuilder.withId(anyString())).thenAnswer(new Answer<AlbumBuilder>() {
299 public AlbumBuilder answer(InvocationOnMock invocation) {
300 when(album.getId()).thenReturn((String) invocation.getArguments()[0]);
304 when(albumBuilder.randomId()).thenAnswer(new Answer<AlbumBuilder>() {
306 public AlbumBuilder answer(InvocationOnMock invocation) {
307 when(album.getId()).thenReturn(randomUUID().toString());
311 when(albumBuilder.by(any(Sone.class))).thenAnswer(new Answer<AlbumBuilder>() {
313 public AlbumBuilder answer(InvocationOnMock invocation) {
314 when(album.getSone()).thenReturn((Sone) invocation.getArguments()[0]);
318 when(albumBuilder.build()).thenAnswer(new Answer<Album>() {
320 public Album answer(InvocationOnMock invocation) {
321 Album album = SoneDownloaderTest.this.album;
322 albums.put(album.getId(), album);
323 SoneDownloaderTest.this.album = mock(Album.class);
328 when(core.albumBuilder()).thenReturn(albumBuilder);
332 public void setupAlbums() {
333 when(core.getAlbum(anyString())).thenAnswer(new Answer<Album>() {
335 public Album answer(InvocationOnMock invocation) throws Throwable {
336 return albums.get(invocation.getArguments()[0]);
342 public void setupImage() {
343 final Image image = SoneDownloaderTest.this.image;
344 Image.Modifier modifier = new Image.Modifier() {
345 private Sone sone = image.getSone();
346 private long creationTime = image.getCreationTime();
347 private String key = image.getKey();
348 private String title = image.getTitle();
349 private String description = image.getDescription();
350 private int width = image.getWidth();
351 private int height = image.getHeight();
354 public Image.Modifier setSone(Sone sone) {
360 public Image.Modifier setCreationTime(long creationTime) {
361 this.creationTime = creationTime;
366 public Image.Modifier setKey(String key) {
372 public Image.Modifier setTitle(String title) {
378 public Image.Modifier setDescription(String description) {
379 this.description = description;
384 public Image.Modifier setWidth(int width) {
390 public Image.Modifier setHeight(int height) {
391 this.height = height;
396 public Image update() throws IllegalStateException {
397 when(image.getSone()).thenReturn(sone);
398 when(image.getCreationTime()).thenReturn(creationTime);
399 when(image.getKey()).thenReturn(key);
400 when(image.getTitle()).thenReturn(title);
401 when(image.getDescription()).thenReturn(description);
402 when(image.getWidth()).thenReturn(width);
403 when(image.getHeight()).thenReturn(height);
407 when(image.getSone()).thenReturn(sone);
408 when(image.modify()).thenReturn(modifier);
412 public void setupImageBuilder() {
413 when(imageBuilder.randomId()).thenAnswer(new Answer<ImageBuilder>() {
415 public ImageBuilder answer(InvocationOnMock invocation) {
416 when(image.getId()).thenReturn(randomUUID().toString());
420 when(imageBuilder.withId(anyString())).thenAnswer(new Answer<ImageBuilder>() {
422 public ImageBuilder answer(InvocationOnMock invocation) {
423 when(image.getId()).thenReturn(
424 (String) invocation.getArguments()[0]);
428 when(imageBuilder.build()).thenAnswer(new Answer<Image>() {
430 public Image answer(InvocationOnMock invocation) {
431 Image image = SoneDownloaderTest.this.image;
432 images.put(image.getId(), image);
433 SoneDownloaderTest.this.image = mock(Image.class);
438 when(core.imageBuilder()).thenReturn(imageBuilder);
442 public void setupImages() {
443 when(core.getImage(anyString())).thenAnswer(new Answer<Image>() {
445 public Image answer(InvocationOnMock invocation)
447 return images.get(invocation.getArguments()[0]);
453 public void addingASoneWillRegisterItsKey() {
454 soneDownloader.addSone(sone);
455 verify(freenetInterface).registerActiveUsk(eq(sone.getRequestUri()), any(
457 verify(freenetInterface, never()).unregisterUsk(sone);
461 public void addingASoneTwiceWillAlsoDeregisterItsKey() {
462 soneDownloader.addSone(sone);
463 soneDownloader.addSone(sone);
464 verify(freenetInterface, times(2)).registerActiveUsk(eq(
465 sone.getRequestUri()), any(USKCallback.class));
466 verify(freenetInterface).unregisterUsk(sone);
470 public void parsingASoneFailsWhenDocumentIsNotXml() throws SoneException {
471 InputStream inputStream = getClass().getResourceAsStream("sone-parser-not-xml.xml");
472 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
476 public void parsingASoneFailsWhenDocumentHasNegativeProtocolVersion() throws SoneException {
477 InputStream inputStream = getClass().getResourceAsStream("sone-parser-negative-protocol-version.xml");
478 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
482 public void parsingASoneFailsWhenProtocolVersionIsTooLarge() throws SoneException {
483 InputStream inputStream = getClass().getResourceAsStream("sone-parser-too-large-protocol-version.xml");
484 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
488 public void parsingASoneFailsWhenThereIsNoTime() throws SoneException {
489 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-time.xml");
490 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
494 public void parsingASoneFailsWhenTimeIsNotNumeric() throws SoneException {
495 InputStream inputStream = getClass().getResourceAsStream("sone-parser-time-not-numeric.xml");
496 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
500 public void parsingASoneFailsWhenProfileIsMissing() throws SoneException {
501 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-profile.xml");
502 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
506 public void parsingASoneFailsWhenProfileFieldIsMissingAFieldName() throws SoneException {
507 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-missing-field-name.xml");
508 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
512 public void parsingASoneFailsWhenProfileFieldNameIsEmpty() throws SoneException {
513 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-empty-field-name.xml");
514 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
518 public void parsingASoneFailsWhenProfileFieldNameIsNotUnique() throws SoneException {
519 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-duplicate-field-name.xml");
520 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
524 public void parsingASoneSucceedsWithoutPayload() throws SoneException {
525 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
526 assertThat(soneDownloader.parseSone(sone, inputStream).getTime(), is(1407197508000L));
530 public void parsingASoneSucceedsWithoutProtocolVersion() throws SoneException {
531 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-protocol-version.xml");
532 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
536 public void parsingASoneFailsWithMissingClientName() throws SoneException {
537 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-name.xml");
538 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
542 public void parsingASoneFailsWithMissingClientVersion() throws SoneException {
543 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-version.xml");
544 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
548 public void parsingASoneSucceedsWithClientInfo() throws SoneException {
549 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-client-info.xml");
550 assertThat(soneDownloader.parseSone(sone, inputStream).getClient(), is(new Client("some-client", "some-version")));
554 public void parsingASoneSucceedsWithRequestUri() throws SoneException, MalformedURLException {
555 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-request-uri.xml");
556 assertThat(soneDownloader.parseSone(sone, inputStream).getRequestUri().toString(), is("USK@w~RyTGmv12Lg9oO91q1Untupi7my9qczT1RheGkEkVE,E8ElVfUgukSCPHxIEJp-gHMiR80wpM7sID3Jo5O7w1s,AQACAAE/Sone/0"));
560 public void parsingASoneFailsWithInvalidRequestUri() throws SoneException, MalformedURLException {
561 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-request-uri.xml");
562 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
566 public void soneInsertUriIsCopiedToNewSone() throws SoneException {
567 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
568 FreenetURI insertUri = mock(FreenetURI.class);
569 when(insertUri.setKeyType(anyString())).thenReturn(insertUri);
570 when(insertUri.setDocName(anyString())).thenReturn(insertUri);
571 when(insertUri.setMetaString(any(String[].class))).thenReturn(insertUri);
572 when(insertUri.setSuggestedEdition(anyLong())).thenReturn(insertUri);
573 when(sone.getInsertUri()).thenReturn(insertUri);
574 assertThat(soneDownloader.parseSone(sone, inputStream).getInsertUri(), is(insertUri));
578 public void parsingASoneSucceedsWithProfile() throws SoneException, MalformedURLException {
579 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-profile.xml");
580 final Profile profile = soneDownloader.parseSone(sone, inputStream).getProfile();
581 assertThat(profile.getFirstName(), is("first"));
582 assertThat(profile.getMiddleName(), is("middle"));
583 assertThat(profile.getLastName(), is("last"));
584 assertThat(profile.getBirthDay(), is(18));
585 assertThat(profile.getBirthMonth(), is(12));
586 assertThat(profile.getBirthYear(), is(1976));
590 public void parsingASoneSucceedsWithoutProfileFields() throws SoneException, MalformedURLException {
591 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-fields.xml");
592 assertThat(soneDownloader.parseSone(sone, inputStream), notNullValue());
596 public void parsingASoneFailsWithoutPostId() throws SoneException, MalformedURLException {
597 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-id.xml");
598 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
602 public void parsingASoneFailsWithoutPostTime() throws SoneException, MalformedURLException {
603 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-time.xml");
604 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
608 public void parsingASoneFailsWithoutPostText() throws SoneException, MalformedURLException {
609 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-text.xml");
610 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
614 public void parsingASoneFailsWithInvalidPostTime() throws SoneException, MalformedURLException {
615 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-time.xml");
616 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
620 public void parsingASoneSucceedsWithValidPostTime() throws SoneException, MalformedURLException {
621 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-time.xml");
622 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
623 assertThat(posts, is(createdPosts));
624 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
625 assertThat(posts.get(0).getId(), is("post-id"));
626 assertThat(posts.get(0).getTime(), is(1407197508000L));
627 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
628 assertThat(posts.get(0).getText(), is("text"));
632 public void parsingASoneSucceedsWithRecipient() throws SoneException, MalformedURLException {
633 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-recipient.xml");
634 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
635 assertThat(posts, is(createdPosts));
636 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
637 assertThat(posts.get(0).getId(), is("post-id"));
638 assertThat(posts.get(0).getTime(), is(1407197508000L));
639 assertThat(posts.get(0).getRecipientId(), is(of("1234567890123456789012345678901234567890123")));
640 assertThat(posts.get(0).getText(), is("text"));
644 public void parsingASoneSucceedsWithInvalidRecipient() throws SoneException, MalformedURLException {
645 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-recipient.xml");
646 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
647 assertThat(posts, is(createdPosts));
648 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
649 assertThat(posts.get(0).getId(), is("post-id"));
650 assertThat(posts.get(0).getTime(), is(1407197508000L));
651 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
652 assertThat(posts.get(0).getText(), is("text"));
656 public void parsingASoneFailsWithoutPostReplyId() throws SoneException, MalformedURLException {
657 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-id.xml");
658 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
662 public void parsingASoneFailsWithoutPostReplyPostId() throws SoneException, MalformedURLException {
663 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-post-id.xml");
664 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
668 public void parsingASoneFailsWithoutPostReplyTime() throws SoneException, MalformedURLException {
669 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-time.xml");
670 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
674 public void parsingASoneFailsWithoutPostReplyText() throws SoneException, MalformedURLException {
675 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-text.xml");
676 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
680 public void parsingASoneFailsWithInvalidPostReplyTime() throws SoneException, MalformedURLException {
681 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-reply-time.xml");
682 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
686 public void parsingASoneSucceedsWithValidPostReplyTime() throws SoneException, MalformedURLException {
687 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-reply-time.xml");
688 final Set<PostReply> postReplies = soneDownloader.parseSone(sone, inputStream).getReplies();
689 assertThat(postReplies, is(createdPostReplies));
690 PostReply postReply = createdPostReplies.iterator().next();
691 assertThat(postReply.getId(), is("reply-id"));
692 assertThat(postReply.getPostId(), is("post-id"));
693 assertThat(postReply.getPost().get().getId(), is("post-id"));
694 assertThat(postReply.getSone().getId(), is("identity"));
695 assertThat(postReply.getTime(), is(1407197508000L));
696 assertThat(postReply.getText(), is("reply-text"));
700 public void parsingASoneSucceedsWithoutLikedPostIds() throws SoneException, MalformedURLException {
701 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-ids.xml");
702 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
706 public void parsingASoneSucceedsWithLikedPostIds() throws SoneException, MalformedURLException {
707 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-ids.xml");
708 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedPostIds(), is((Set<String>) ImmutableSet.of("liked-post-id")));
712 public void parsingASoneSucceedsWithoutLikedPostReplyIds() throws SoneException, MalformedURLException {
713 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-reply-ids.xml");
714 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
718 public void parsingASoneSucceedsWithLikedPostReplyIds() throws SoneException, MalformedURLException {
719 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-reply-ids.xml");
720 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedReplyIds(), is((Set<String>) ImmutableSet.of("liked-post-reply-id")));
724 public void parsingASoneSucceedsWithoutAlbums() throws SoneException, MalformedURLException {
725 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-albums.xml");
726 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
730 public void parsingASoneFailsWithoutAlbumId() throws SoneException, MalformedURLException {
731 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-id.xml");
732 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
736 public void parsingASoneFailsWithoutAlbumTitle() throws SoneException, MalformedURLException {
737 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-title.xml");
738 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
742 public void parsingASoneSucceedsWithNestedAlbums() throws SoneException, MalformedURLException {
743 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-multiple-albums.xml");
744 final Sone parsedSone = soneDownloader.parseSone(sone, inputStream);
745 assertThat(parsedSone, not(nullValue()));
746 assertThat(parsedSone.getRootAlbum().getAlbums(), hasSize(1));
747 Album album = parsedSone.getRootAlbum().getAlbums().get(0);
748 assertThat(album.getId(), is("album-id-1"));
749 assertThat(album.getTitle(), is("album-title"));
750 assertThat(album.getDescription(), is("album-description"));
751 assertThat(album.getAlbums(), hasSize(1));
752 Album nestedAlbum = album.getAlbums().get(0);
753 assertThat(nestedAlbum.getId(), is("album-id-2"));
754 assertThat(nestedAlbum.getTitle(), is("album-title-2"));
755 assertThat(nestedAlbum.getDescription(), is("album-description-2"));
756 assertThat(nestedAlbum.getAlbums(), hasSize(0));
760 public void parsingASoneFailsWithInvalidParentAlbumId() throws SoneException, MalformedURLException {
761 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-parent-album-id.xml");
762 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
766 public void parsingASoneSucceedsWithoutImages() throws SoneException, MalformedURLException {
767 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-images.xml");
768 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
772 public void parsingASoneFailsWithoutImageId() throws SoneException, MalformedURLException {
773 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-id.xml");
774 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
778 public void parsingASoneFailsWithoutImageTime() throws SoneException, MalformedURLException {
779 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-time.xml");
780 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
784 public void parsingASoneFailsWithoutImageKey() throws SoneException, MalformedURLException {
785 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-key.xml");
786 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
790 public void parsingASoneFailsWithoutImageTitle() throws SoneException, MalformedURLException {
791 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-title.xml");
792 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
796 public void parsingASoneFailsWithoutImageWidth() throws SoneException, MalformedURLException {
797 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-width.xml");
798 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
802 public void parsingASoneFailsWithoutImageHeight() throws SoneException, MalformedURLException {
803 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-height.xml");
804 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
808 public void parsingASoneFailsWithInvalidImageWidth() throws SoneException, MalformedURLException {
809 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-width.xml");
810 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
814 public void parsingASoneFailsWithInvalidImageHeight() throws SoneException, MalformedURLException {
815 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-height.xml");
816 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
820 public void parsingASoneSucceedsWithImage() throws SoneException, MalformedURLException {
821 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-image.xml");
822 final Sone sone = soneDownloader.parseSone(this.sone, inputStream);
823 assertThat(sone, not(nullValue()));
824 assertThat(sone.getRootAlbum().getAlbums(), hasSize(1));
825 assertThat(sone.getRootAlbum().getAlbums().get(0).getImages(), hasSize(1));
826 Image image = sone.getRootAlbum().getAlbums().get(0).getImages().get(0);
827 assertThat(image.getId(), is("image-id"));
828 assertThat(image.getCreationTime(), is(1407197508000L));
829 assertThat(image.getKey(), is("KSK@GPLv3.txt"));
830 assertThat(image.getTitle(), is("image-title"));
831 assertThat(image.getDescription(), is("image-description"));
832 assertThat(image.getWidth(), is(1920));
833 assertThat(image.getHeight(), is(1080));
834 assertThat(sone.getProfile().getAvatar(), is("image-id"));
838 public void stoppingTheSoneDownloaderUnregistersTheSone() {
839 soneDownloader.addSone(sone);
840 soneDownloader.stop();
841 verify(freenetInterface).unregisterUsk(sone);
845 public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
846 setupSoneAsUnknown();
847 soneDownloader.fetchSoneAction(sone).run();
848 verify(freenetInterface).fetchUri(requestUri);
849 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
850 verify(core, never()).updateSone(any(Sone.class));
853 private void verifyThatSoneStatusWasChangedToDownloadingAndBackTo(SoneStatus soneStatus) {
854 ArgumentCaptor<SoneStatus> soneStatuses = forClass(SoneStatus.class);
855 verify(sone, times(2)).setStatus(soneStatuses.capture());
856 assertThat(soneStatuses.getAllValues().get(0), is(downloading));
857 assertThat(soneStatuses.getAllValues().get(1), is(soneStatus));
861 public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
862 soneDownloader.fetchSoneAction(sone).run();
863 verify(freenetInterface).fetchUri(requestUri);
864 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
865 verify(core, never()).updateSone(any(Sone.class));
868 @Test(expected = NullPointerException.class)
869 public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
870 setupSoneAsUnknown();
871 when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
873 soneDownloader.fetchSoneAction(sone).run();
875 verify(freenetInterface).fetchUri(requestUri);
876 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
877 verify(core, never()).updateSone(any(Sone.class));
881 @Test(expected = NullPointerException.class)
882 public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
883 when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
885 soneDownloader.fetchSoneAction(sone).run();
887 verify(freenetInterface).fetchUri(requestUri);
888 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
889 verify(core, never()).updateSone(any(Sone.class));
894 public void successfulFetchingOfSoneWithUskRequestUriUpdatesTheCoreWithASone() throws IOException {
895 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
896 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
897 soneDownloader.fetchSoneAction(sone).run();
898 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
901 private void verifyThatParsedSoneHasTheSameIdAsTheOriginalSone() {
902 ArgumentCaptor<Sone> soneCaptor = forClass(Sone.class);
903 verify(core).updateSone(soneCaptor.capture());
904 assertThat(soneCaptor.getValue().getId(), is(sone.getId()));
908 public void successfulFetchingOfSoneWithSskRequestUriUpdatesTheCoreWithASone() throws IOException {
909 when(requestUri.getKeyType()).thenReturn("SSK");
910 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
911 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
912 soneDownloader.fetchSoneAction(sone).run();
913 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
917 public void successfulFetchingAnUnknownSoneWithSskRequestUriUpdatesTheCoreWithASone() throws IOException {
918 when(requestUri.getKeyType()).thenReturn("SSK");
919 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-with-zero-time.xml"));
920 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
921 soneDownloader.fetchSoneAction(sone).run();
922 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
926 public void fetchingSoneWithInvalidXmlWillNotUpdateTheCore() throws IOException {
927 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-not-xml.xml"));
928 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
929 soneDownloader.fetchSoneAction(sone).run();
930 verify(core, never()).updateSone(any(Sone.class));
934 public void exceptionWhileFetchingSoneWillNotUpdateTheCore() throws IOException {
935 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
936 when(sone.getId()).thenThrow(NullPointerException.class);
937 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
938 soneDownloader.fetchSoneAction(sone).run();
939 verify(core, never()).updateSone(any(Sone.class));
943 public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException {
944 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
945 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
946 soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
947 verify(core, never()).updateSone(any(Sone.class));
948 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
951 private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
952 ClientMetadata clientMetadata = new ClientMetadata("application/xml");
953 Bucket bucket = mock(Bucket.class);
954 when(bucket.getInputStream()).thenReturn(inputStream);
955 FetchResult fetchResult = new FetchResult(clientMetadata, bucket);
956 return new Fetched(uri, fetchResult);