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.anyBoolean;
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;
38 import java.util.concurrent.TimeUnit;
40 import net.pterodactylus.sone.core.FreenetInterface.Fetched;
41 import net.pterodactylus.sone.data.Album;
42 import net.pterodactylus.sone.data.Album.Modifier;
43 import net.pterodactylus.sone.data.Client;
44 import net.pterodactylus.sone.data.Image;
45 import net.pterodactylus.sone.data.ImageImpl;
46 import net.pterodactylus.sone.data.Post;
47 import net.pterodactylus.sone.data.PostReply;
48 import net.pterodactylus.sone.data.Profile;
49 import net.pterodactylus.sone.data.Sone;
50 import net.pterodactylus.sone.data.Sone.SoneStatus;
51 import net.pterodactylus.sone.database.AlbumBuilder;
52 import net.pterodactylus.sone.database.PostBuilder;
53 import net.pterodactylus.sone.database.PostReplyBuilder;
54 import net.pterodactylus.sone.freenet.wot.Identity;
56 import freenet.client.ClientMetadata;
57 import freenet.client.FetchResult;
58 import freenet.client.async.ClientContext;
59 import freenet.client.async.USKCallback;
60 import freenet.keys.FreenetURI;
61 import freenet.keys.USK;
62 import freenet.node.RequestStarter;
63 import freenet.support.api.Bucket;
65 import com.db4o.ObjectContainer;
67 import com.google.common.base.Optional;
68 import com.google.common.collect.ArrayListMultimap;
69 import com.google.common.collect.ImmutableSet;
70 import com.google.common.collect.ListMultimap;
71 import org.junit.Before;
72 import org.junit.Test;
73 import org.mockito.ArgumentCaptor;
74 import org.mockito.invocation.InvocationOnMock;
75 import org.mockito.stubbing.Answer;
78 * Unit test for {@link SoneDownloaderImpl} and its subclasses.
80 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
82 public class SoneDownloaderTest {
84 private final Core core = mock(Core.class);
85 private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
86 private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface);
87 private final FreenetURI requestUri = mock(FreenetURI.class);
88 private final Sone sone = mock(Sone.class);
89 private final PostBuilder postBuilder = mock(PostBuilder.class);
90 private final List<Post> createdPosts = new ArrayList<Post>();
91 private Post post = mock(Post.class);
92 private final PostReplyBuilder postReplyBuilder = mock(PostReplyBuilder.class);
93 private final Set<PostReply> createdPostReplies = new HashSet<PostReply>();
94 private PostReply postReply = mock(PostReply.class);
95 private final AlbumBuilder albumBuilder = mock(AlbumBuilder.class);
96 private final ListMultimap<Album, Album> nestedAlbums = ArrayListMultimap.create();
97 private final ListMultimap<Album, Image> albumImages = ArrayListMultimap.create();
98 private Album album = mock(Album.class);
99 private final Map<String, Album> albums = new HashMap<String, Album>();
102 public void setupSone() {
103 Identity identity = mock(Identity.class);
104 when(identity.getId()).thenReturn("identity");
105 when(sone.getId()).thenReturn("identity");
106 when(sone.getIdentity()).thenReturn(identity);
107 when(sone.getRequestUri()).thenReturn(requestUri);
108 when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
111 private void setupSoneAsUnknown() {
112 when(sone.getTime()).thenReturn(0L);
116 public void setupRequestUri() {
117 when(requestUri.setKeyType(anyString())).thenReturn(requestUri);
118 when(requestUri.sskForUSK()).thenReturn(requestUri);
119 when(requestUri.setDocName(anyString())).thenReturn(requestUri);
120 when(requestUri.setMetaString(any(String[].class))).thenReturn(requestUri);
121 when(requestUri.getKeyType()).thenReturn("USK");
125 public void setupPost() {
126 when(post.getRecipientId()).thenReturn(Optional.<String>absent());
130 public void setupPostBuilder() {
131 when(postBuilder.withId(anyString())).thenAnswer(new Answer<PostBuilder>() {
133 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
134 when(post.getId()).thenReturn((String) invocation.getArguments()[0]);
138 when(postBuilder.from(anyString())).thenAnswer(new Answer<PostBuilder>() {
140 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
141 final Sone sone = mock(Sone.class);
142 when(sone.getId()).thenReturn((String) invocation.getArguments()[0]);
143 when(post.getSone()).thenReturn(sone);
147 when(postBuilder.withTime(anyLong())).thenAnswer(new Answer<PostBuilder>() {
149 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
150 when(post.getTime()).thenReturn((Long) invocation.getArguments()[0]);
154 when(postBuilder.withText(anyString())).thenAnswer(new Answer<PostBuilder>() {
156 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
157 when(post.getText()).thenReturn((String) invocation.getArguments()[0]);
161 when(postBuilder.to(anyString())).thenAnswer(new Answer<PostBuilder>() {
163 public PostBuilder answer(InvocationOnMock invocation) throws Throwable {
164 when(post.getRecipientId()).thenReturn(of((String) invocation.getArguments()[0]));
168 when(postBuilder.build()).thenAnswer(new Answer<Post>() {
170 public Post answer(InvocationOnMock invocation) throws Throwable {
171 Post post = SoneDownloaderTest.this.post;
172 SoneDownloaderTest.this.post = mock(Post.class);
174 createdPosts.add(post);
178 when(core.postBuilder()).thenReturn(postBuilder);
182 public void setupPostReplyBuilder() {
183 when(postReplyBuilder.withId(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
185 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
186 when(postReply.getId()).thenReturn((String) invocation.getArguments()[0]);
187 return postReplyBuilder;
190 when(postReplyBuilder.from(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
192 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
193 Sone sone = when(mock(Sone.class).getId()).thenReturn((String) invocation.getArguments()[0]).getMock();
194 when(postReply.getSone()).thenReturn(sone);
195 return postReplyBuilder;
198 when(postReplyBuilder.to(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
200 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
201 when(postReply.getPostId()).thenReturn((String) invocation.getArguments()[0]);
202 Post post = when(mock(Post.class).getId()).thenReturn((String) invocation.getArguments()[0]).getMock();
203 when(postReply.getPost()).thenReturn(of(post));
204 return postReplyBuilder;
207 when(postReplyBuilder.withTime(anyLong())).thenAnswer(new Answer<PostReplyBuilder>() {
209 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
210 when(postReply.getTime()).thenReturn((Long) invocation.getArguments()[0]);
211 return postReplyBuilder;
214 when(postReplyBuilder.withText(anyString())).thenAnswer(new Answer<PostReplyBuilder>() {
216 public PostReplyBuilder answer(InvocationOnMock invocation) throws Throwable {
217 when(postReply.getText()).thenReturn((String) invocation.getArguments()[0]);
218 return postReplyBuilder;
221 when(postReplyBuilder.build()).thenAnswer(new Answer<PostReply>() {
223 public PostReply answer(InvocationOnMock invocation) throws Throwable {
224 PostReply postReply = SoneDownloaderTest.this.postReply;
225 createdPostReplies.add(postReply);
226 SoneDownloaderTest.this.postReply = mock(PostReply.class);
230 when(core.postReplyBuilder()).thenReturn(postReplyBuilder);
234 public void setupAlbum() {
238 private void setupAlbum(final Album album) {
239 when(album.getAlbumImage()).thenReturn(mock(Image.class));
240 doAnswer(new Answer<Void>() {
242 public Void answer(InvocationOnMock invocation) {
243 nestedAlbums.put(album, (Album) invocation.getArguments()[0]);
246 }).when(album).addAlbum(any(Album.class));
247 doAnswer(new Answer<Void>() {
249 public Void answer(InvocationOnMock invocation) {
250 albumImages.put(album, (Image) invocation.getArguments()[0]);
253 }).when(album).addImage(any(Image.class));
254 when(album.getAlbums()).thenAnswer(new Answer<List<Album>>() {
256 public List<Album> answer(InvocationOnMock invocation) {
257 return nestedAlbums.get(album);
260 when(album.getImages()).thenAnswer(new Answer<List<Image>>() {
262 public List<Image> answer(InvocationOnMock invocation) {
263 return albumImages.get(album);
266 final Modifier albumModifier = new Modifier() {
267 private String title = album.getTitle();
268 private String description = album.getDescription();
269 private String imageId = album.getAlbumImage().getId();
272 public Modifier setTitle(String title) {
278 public Modifier setDescription(String description) {
279 this.description = description;
284 public Modifier setAlbumImage(String imageId) {
285 this.imageId = imageId;
290 public Album update() throws IllegalStateException {
291 when(album.getTitle()).thenReturn(title);
292 when(album.getDescription()).thenReturn(description);
293 Image image = mock(Image.class);
294 when(image.getId()).thenReturn(imageId);
295 when(album.getAlbumImage()).thenReturn(image);
299 when(album.modify()).thenReturn(albumModifier);
303 public void setupAlbumBuilder() {
304 when(albumBuilder.withId(anyString())).thenAnswer(new Answer<AlbumBuilder>() {
306 public AlbumBuilder answer(InvocationOnMock invocation) {
307 when(album.getId()).thenReturn((String) invocation.getArguments()[0]);
311 when(albumBuilder.randomId()).thenAnswer(new Answer<AlbumBuilder>() {
313 public AlbumBuilder answer(InvocationOnMock invocation) {
314 when(album.getId()).thenReturn(randomUUID().toString());
318 when(albumBuilder.by(any(Sone.class))).thenAnswer(new Answer<AlbumBuilder>() {
320 public AlbumBuilder answer(InvocationOnMock invocation) {
321 when(album.getSone()).thenReturn((Sone) invocation.getArguments()[0]);
325 when(albumBuilder.build()).thenAnswer(new Answer<Album>() {
327 public Album answer(InvocationOnMock invocation) {
328 Album album = SoneDownloaderTest.this.album;
329 albums.put(album.getId(), album);
330 SoneDownloaderTest.this.album = mock(Album.class);
331 setupAlbum(SoneDownloaderTest.this.album);
335 when(core.albumBuilder()).thenReturn(albumBuilder);
339 public void setupAlbums() {
340 when(core.getAlbum(anyString())).thenAnswer(new Answer<Album>() {
342 public Album answer(InvocationOnMock invocation) throws Throwable {
343 return albums.get(invocation.getArguments()[0]);
349 public void setupImages() {
350 Image image = new ImageImpl("image-id");
351 when(core.getImage("image-id")).thenReturn(image);
352 when(core.getImage(eq("image-id"), anyBoolean())).thenReturn(image);
356 public void addingASoneWillRegisterItsKey() {
357 soneDownloader.addSone(sone);
358 verify(freenetInterface).registerActiveUsk(eq(sone.getRequestUri()), any(
360 verify(freenetInterface, never()).unregisterUsk(sone);
364 public void addingASoneTwiceWillAlsoDeregisterItsKey() {
365 soneDownloader.addSone(sone);
366 soneDownloader.addSone(sone);
367 verify(freenetInterface, times(2)).registerActiveUsk(eq(
368 sone.getRequestUri()), any(USKCallback.class));
369 verify(freenetInterface).unregisterUsk(sone);
373 public void parsingASoneFailsWhenDocumentIsNotXml() throws SoneException {
374 InputStream inputStream = getClass().getResourceAsStream("sone-parser-not-xml.xml");
375 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
379 public void parsingASoneFailsWhenDocumentHasNegativeProtocolVersion() throws SoneException {
380 InputStream inputStream = getClass().getResourceAsStream("sone-parser-negative-protocol-version.xml");
381 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
385 public void parsingASoneFailsWhenProtocolVersionIsTooLarge() throws SoneException {
386 InputStream inputStream = getClass().getResourceAsStream("sone-parser-too-large-protocol-version.xml");
387 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
391 public void parsingASoneFailsWhenThereIsNoTime() throws SoneException {
392 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-time.xml");
393 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
397 public void parsingASoneFailsWhenTimeIsNotNumeric() throws SoneException {
398 InputStream inputStream = getClass().getResourceAsStream("sone-parser-time-not-numeric.xml");
399 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
403 public void parsingASoneFailsWhenProfileIsMissing() throws SoneException {
404 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-profile.xml");
405 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
409 public void parsingASoneFailsWhenProfileFieldIsMissingAFieldName() throws SoneException {
410 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-missing-field-name.xml");
411 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
415 public void parsingASoneFailsWhenProfileFieldNameIsEmpty() throws SoneException {
416 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-empty-field-name.xml");
417 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
421 public void parsingASoneFailsWhenProfileFieldNameIsNotUnique() throws SoneException {
422 InputStream inputStream = getClass().getResourceAsStream("sone-parser-profile-duplicate-field-name.xml");
423 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
427 public void parsingASoneSucceedsWithoutPayload() throws SoneException {
428 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
429 assertThat(soneDownloader.parseSone(sone, inputStream).getTime(), is(1407197508000L));
433 public void parsingASoneSucceedsWithoutProtocolVersion() throws SoneException {
434 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-protocol-version.xml");
435 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
439 public void parsingASoneFailsWithMissingClientName() throws SoneException {
440 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-name.xml");
441 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
445 public void parsingASoneFailsWithMissingClientVersion() throws SoneException {
446 InputStream inputStream = getClass().getResourceAsStream("sone-parser-missing-client-version.xml");
447 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
451 public void parsingASoneSucceedsWithClientInfo() throws SoneException {
452 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-client-info.xml");
453 assertThat(soneDownloader.parseSone(sone, inputStream).getClient(), is(new Client("some-client", "some-version")));
457 public void parsingASoneSucceedsWithRequestUri() throws SoneException, MalformedURLException {
458 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-request-uri.xml");
459 assertThat(soneDownloader.parseSone(sone, inputStream).getRequestUri().toString(), is("USK@w~RyTGmv12Lg9oO91q1Untupi7my9qczT1RheGkEkVE,E8ElVfUgukSCPHxIEJp-gHMiR80wpM7sID3Jo5O7w1s,AQACAAE/Sone/0"));
463 public void parsingASoneFailsWithInvalidRequestUri() throws SoneException, MalformedURLException {
464 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-request-uri.xml");
465 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
469 public void soneInsertUriIsCopiedToNewSone() throws SoneException {
470 InputStream inputStream = getClass().getResourceAsStream("sone-parser-no-payload.xml");
471 FreenetURI insertUri = mock(FreenetURI.class);
472 when(insertUri.setKeyType(anyString())).thenReturn(insertUri);
473 when(insertUri.setDocName(anyString())).thenReturn(insertUri);
474 when(insertUri.setMetaString(any(String[].class))).thenReturn(insertUri);
475 when(insertUri.setSuggestedEdition(anyLong())).thenReturn(insertUri);
476 when(sone.getInsertUri()).thenReturn(insertUri);
477 assertThat(soneDownloader.parseSone(sone, inputStream).getInsertUri(), is(insertUri));
481 public void parsingASoneSucceedsWithProfile() throws SoneException, MalformedURLException {
482 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-profile.xml");
483 final Profile profile = soneDownloader.parseSone(sone, inputStream).getProfile();
484 assertThat(profile.getFirstName(), is("first"));
485 assertThat(profile.getMiddleName(), is("middle"));
486 assertThat(profile.getLastName(), is("last"));
487 assertThat(profile.getBirthDay(), is(18));
488 assertThat(profile.getBirthMonth(), is(12));
489 assertThat(profile.getBirthYear(), is(1976));
493 public void parsingASoneSucceedsWithoutProfileFields() throws SoneException, MalformedURLException {
494 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-fields.xml");
495 assertThat(soneDownloader.parseSone(sone, inputStream), notNullValue());
499 public void parsingASoneFailsWithoutPostId() throws SoneException, MalformedURLException {
500 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-id.xml");
501 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
505 public void parsingASoneFailsWithoutPostTime() throws SoneException, MalformedURLException {
506 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-time.xml");
507 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
511 public void parsingASoneFailsWithoutPostText() throws SoneException, MalformedURLException {
512 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-text.xml");
513 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
517 public void parsingASoneFailsWithInvalidPostTime() throws SoneException, MalformedURLException {
518 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-time.xml");
519 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
523 public void parsingASoneSucceedsWithValidPostTime() throws SoneException, MalformedURLException {
524 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-time.xml");
525 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
526 assertThat(posts, is(createdPosts));
527 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
528 assertThat(posts.get(0).getId(), is("post-id"));
529 assertThat(posts.get(0).getTime(), is(1407197508000L));
530 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
531 assertThat(posts.get(0).getText(), is("text"));
535 public void parsingASoneSucceedsWithRecipient() throws SoneException, MalformedURLException {
536 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-recipient.xml");
537 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
538 assertThat(posts, is(createdPosts));
539 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
540 assertThat(posts.get(0).getId(), is("post-id"));
541 assertThat(posts.get(0).getTime(), is(1407197508000L));
542 assertThat(posts.get(0).getRecipientId(), is(of("1234567890123456789012345678901234567890123")));
543 assertThat(posts.get(0).getText(), is("text"));
547 public void parsingASoneSucceedsWithInvalidRecipient() throws SoneException, MalformedURLException {
548 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-recipient.xml");
549 final List<Post> posts = soneDownloader.parseSone(sone, inputStream).getPosts();
550 assertThat(posts, is(createdPosts));
551 assertThat(posts.get(0).getSone().getId(), is(sone.getId()));
552 assertThat(posts.get(0).getId(), is("post-id"));
553 assertThat(posts.get(0).getTime(), is(1407197508000L));
554 assertThat(posts.get(0).getRecipientId(), is(Optional.<String>absent()));
555 assertThat(posts.get(0).getText(), is("text"));
559 public void parsingASoneFailsWithoutPostReplyId() throws SoneException, MalformedURLException {
560 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-id.xml");
561 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
565 public void parsingASoneFailsWithoutPostReplyPostId() throws SoneException, MalformedURLException {
566 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-post-id.xml");
567 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
571 public void parsingASoneFailsWithoutPostReplyTime() throws SoneException, MalformedURLException {
572 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-time.xml");
573 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
577 public void parsingASoneFailsWithoutPostReplyText() throws SoneException, MalformedURLException {
578 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-post-reply-text.xml");
579 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
583 public void parsingASoneFailsWithInvalidPostReplyTime() throws SoneException, MalformedURLException {
584 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-post-reply-time.xml");
585 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
589 public void parsingASoneSucceedsWithValidPostReplyTime() throws SoneException, MalformedURLException {
590 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-valid-post-reply-time.xml");
591 final Set<PostReply> postReplies = soneDownloader.parseSone(sone, inputStream).getReplies();
592 assertThat(postReplies, is(createdPostReplies));
593 PostReply postReply = createdPostReplies.iterator().next();
594 assertThat(postReply.getId(), is("reply-id"));
595 assertThat(postReply.getPostId(), is("post-id"));
596 assertThat(postReply.getPost().get().getId(), is("post-id"));
597 assertThat(postReply.getSone().getId(), is("identity"));
598 assertThat(postReply.getTime(), is(1407197508000L));
599 assertThat(postReply.getText(), is("reply-text"));
603 public void parsingASoneSucceedsWithoutLikedPostIds() throws SoneException, MalformedURLException {
604 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-ids.xml");
605 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
609 public void parsingASoneSucceedsWithLikedPostIds() throws SoneException, MalformedURLException {
610 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-ids.xml");
611 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedPostIds(), is((Set<String>) ImmutableSet.of("liked-post-id")));
615 public void parsingASoneSucceedsWithoutLikedPostReplyIds() throws SoneException, MalformedURLException {
616 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-liked-post-reply-ids.xml");
617 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
621 public void parsingASoneSucceedsWithLikedPostReplyIds() throws SoneException, MalformedURLException {
622 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-liked-post-reply-ids.xml");
623 assertThat(soneDownloader.parseSone(sone, inputStream).getLikedReplyIds(), is((Set<String>) ImmutableSet.of("liked-post-reply-id")));
627 public void parsingASoneSucceedsWithoutAlbums() throws SoneException, MalformedURLException {
628 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-albums.xml");
629 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
633 public void parsingASoneFailsWithoutAlbumId() throws SoneException, MalformedURLException {
634 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-id.xml");
635 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
639 public void parsingASoneFailsWithoutAlbumTitle() throws SoneException, MalformedURLException {
640 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-album-title.xml");
641 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
645 public void parsingASoneSucceedsWithNestedAlbums() throws SoneException, MalformedURLException {
646 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-multiple-albums.xml");
647 final Sone parsedSone = soneDownloader.parseSone(sone, inputStream);
648 assertThat(parsedSone, not(nullValue()));
649 assertThat(parsedSone.getRootAlbum().getAlbums(), hasSize(1));
650 Album album = parsedSone.getRootAlbum().getAlbums().get(0);
651 assertThat(album.getId(), is("album-id-1"));
652 assertThat(album.getTitle(), is("album-title"));
653 assertThat(album.getDescription(), is("album-description"));
654 assertThat(album.getAlbums(), hasSize(1));
655 Album nestedAlbum = album.getAlbums().get(0);
656 assertThat(nestedAlbum.getId(), is("album-id-2"));
657 assertThat(nestedAlbum.getTitle(), is("album-title-2"));
658 assertThat(nestedAlbum.getDescription(), is("album-description-2"));
659 assertThat(nestedAlbum.getAlbums(), hasSize(0));
663 public void parsingASoneFailsWithInvalidParentAlbumId() throws SoneException, MalformedURLException {
664 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-parent-album-id.xml");
665 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
669 public void parsingASoneSucceedsWithoutImages() throws SoneException, MalformedURLException {
670 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-images.xml");
671 assertThat(soneDownloader.parseSone(sone, inputStream), not(nullValue()));
675 public void parsingASoneFailsWithoutImageId() throws SoneException, MalformedURLException {
676 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-id.xml");
677 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
681 public void parsingASoneFailsWithoutImageTime() throws SoneException, MalformedURLException {
682 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-time.xml");
683 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
687 public void parsingASoneFailsWithoutImageKey() throws SoneException, MalformedURLException {
688 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-key.xml");
689 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
693 public void parsingASoneFailsWithoutImageTitle() throws SoneException, MalformedURLException {
694 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-title.xml");
695 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
699 public void parsingASoneFailsWithoutImageWidth() throws SoneException, MalformedURLException {
700 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-width.xml");
701 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
705 public void parsingASoneFailsWithoutImageHeight() throws SoneException, MalformedURLException {
706 InputStream inputStream = getClass().getResourceAsStream("sone-parser-without-image-height.xml");
707 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
711 public void parsingASoneFailsWithInvalidImageWidth() throws SoneException, MalformedURLException {
712 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-width.xml");
713 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
717 public void parsingASoneFailsWithInvalidImageHeight() throws SoneException, MalformedURLException {
718 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-invalid-image-height.xml");
719 assertThat(soneDownloader.parseSone(sone, inputStream), nullValue());
723 public void parsingASoneSucceedsWithImage() throws SoneException, MalformedURLException {
724 InputStream inputStream = getClass().getResourceAsStream("sone-parser-with-image.xml");
725 final Sone sone = soneDownloader.parseSone(this.sone, inputStream);
726 assertThat(sone, not(nullValue()));
727 assertThat(sone.getRootAlbum().getAlbums(), hasSize(1));
728 assertThat(sone.getRootAlbum().getAlbums().get(0).getImages(), hasSize(1));
729 Image image = sone.getRootAlbum().getAlbums().get(0).getImages().get(0);
730 assertThat(image.getId(), is("image-id"));
731 assertThat(image.getCreationTime(), is(1407197508000L));
732 assertThat(image.getKey(), is("KSK@GPLv3.txt"));
733 assertThat(image.getTitle(), is("image-title"));
734 assertThat(image.getDescription(), is("image-description"));
735 assertThat(image.getWidth(), is(1920));
736 assertThat(image.getHeight(), is(1080));
737 assertThat(sone.getProfile().getAvatar(), is("image-id"));
741 public void stoppingTheSoneDownloaderUnregistersTheSone() {
742 soneDownloader.addSone(sone);
743 soneDownloader.stop();
744 verify(freenetInterface).unregisterUsk(sone);
748 public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
749 setupSoneAsUnknown();
750 soneDownloader.fetchSoneAction(sone).run();
751 verify(freenetInterface).fetchUri(requestUri);
752 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
753 verify(core, never()).updateSone(any(Sone.class));
756 private void verifyThatSoneStatusWasChangedToDownloadingAndBackTo(SoneStatus soneStatus) {
757 ArgumentCaptor<SoneStatus> soneStatuses = forClass(SoneStatus.class);
758 verify(sone, times(2)).setStatus(soneStatuses.capture());
759 assertThat(soneStatuses.getAllValues().get(0), is(downloading));
760 assertThat(soneStatuses.getAllValues().get(1), is(soneStatus));
764 public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
765 soneDownloader.fetchSoneAction(sone).run();
766 verify(freenetInterface).fetchUri(requestUri);
767 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
768 verify(core, never()).updateSone(any(Sone.class));
771 @Test(expected = NullPointerException.class)
772 public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
773 setupSoneAsUnknown();
774 when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
776 soneDownloader.fetchSoneAction(sone).run();
778 verify(freenetInterface).fetchUri(requestUri);
779 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
780 verify(core, never()).updateSone(any(Sone.class));
784 @Test(expected = NullPointerException.class)
785 public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
786 when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class);
788 soneDownloader.fetchSoneAction(sone).run();
790 verify(freenetInterface).fetchUri(requestUri);
791 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
792 verify(core, never()).updateSone(any(Sone.class));
797 public void successfulFetchingOfSoneWithUskRequestUriUpdatesTheCoreWithASone() throws IOException {
798 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
799 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
800 soneDownloader.fetchSoneAction(sone).run();
801 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
804 private void verifyThatParsedSoneHasTheSameIdAsTheOriginalSone() {
805 ArgumentCaptor<Sone> soneCaptor = forClass(Sone.class);
806 verify(core).updateSone(soneCaptor.capture());
807 assertThat(soneCaptor.getValue().getId(), is(sone.getId()));
811 public void successfulFetchingOfSoneWithSskRequestUriUpdatesTheCoreWithASone() throws IOException {
812 when(requestUri.getKeyType()).thenReturn("SSK");
813 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
814 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
815 soneDownloader.fetchSoneAction(sone).run();
816 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
820 public void successfulFetchingAnUnknownSoneWithSskRequestUriUpdatesTheCoreWithASone() throws IOException {
821 when(requestUri.getKeyType()).thenReturn("SSK");
822 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-with-zero-time.xml"));
823 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
824 soneDownloader.fetchSoneAction(sone).run();
825 verifyThatParsedSoneHasTheSameIdAsTheOriginalSone();
829 public void fetchingSoneWithInvalidXmlWillNotUpdateTheCore() throws IOException {
830 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-not-xml.xml"));
831 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
832 soneDownloader.fetchSoneAction(sone).run();
833 verify(core, never()).updateSone(any(Sone.class));
837 public void exceptionWhileFetchingSoneWillNotUpdateTheCore() throws IOException {
838 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
839 when(sone.getId()).thenThrow(NullPointerException.class);
840 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
841 soneDownloader.fetchSoneAction(sone).run();
842 verify(core, never()).updateSone(any(Sone.class));
846 public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException {
847 final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
848 when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
849 soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
850 verify(core, never()).updateSone(any(Sone.class));
851 verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
854 private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
855 ClientMetadata clientMetadata = new ClientMetadata("application/xml");
856 Bucket bucket = mock(Bucket.class);
857 when(bucket.getInputStream()).thenReturn(inputStream);
858 FetchResult fetchResult = new FetchResult(clientMetadata, bucket);
859 return new Fetched(uri, fetchResult);