Move image parsing to new configuration parser.
[Sone.git] / src / test / java / net / pterodactylus / sone / core / ConfigurationSoneParserTest.java
1 package net.pterodactylus.sone.core;
2
3 import static com.google.common.base.Optional.fromNullable;
4 import static com.google.common.base.Optional.of;
5 import static java.lang.System.currentTimeMillis;
6 import static java.util.UUID.randomUUID;
7 import static net.pterodactylus.sone.Matchers.isAlbum;
8 import static net.pterodactylus.sone.Matchers.isImage;
9 import static net.pterodactylus.sone.Matchers.isPost;
10 import static net.pterodactylus.sone.Matchers.isPostReply;
11 import static org.hamcrest.MatcherAssert.assertThat;
12 import static org.hamcrest.Matchers.contains;
13 import static org.hamcrest.Matchers.containsInAnyOrder;
14 import static org.hamcrest.Matchers.emptyIterable;
15 import static org.hamcrest.Matchers.hasSize;
16 import static org.hamcrest.Matchers.is;
17 import static org.hamcrest.Matchers.notNullValue;
18 import static org.hamcrest.Matchers.nullValue;
19 import static org.mockito.Matchers.any;
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.when;
25
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.atomic.AtomicReference;
32
33 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound;
34 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound;
35 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
36 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound;
37 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound;
38 import net.pterodactylus.sone.data.Album;
39 import net.pterodactylus.sone.data.Album.Modifier;
40 import net.pterodactylus.sone.data.Image;
41 import net.pterodactylus.sone.data.Post;
42 import net.pterodactylus.sone.data.PostReply;
43 import net.pterodactylus.sone.data.Profile;
44 import net.pterodactylus.sone.data.Profile.Field;
45 import net.pterodactylus.sone.data.Sone;
46 import net.pterodactylus.sone.database.AlbumBuilder;
47 import net.pterodactylus.sone.database.AlbumBuilderFactory;
48 import net.pterodactylus.sone.database.ImageBuilder;
49 import net.pterodactylus.sone.database.ImageBuilderFactory;
50 import net.pterodactylus.sone.database.PostBuilder;
51 import net.pterodactylus.sone.database.PostBuilderFactory;
52 import net.pterodactylus.sone.database.PostReplyBuilder;
53 import net.pterodactylus.sone.database.PostReplyBuilderFactory;
54 import net.pterodactylus.util.config.Configuration;
55 import net.pterodactylus.util.config.ConfigurationException;
56 import net.pterodactylus.util.config.Value;
57
58 import com.google.common.base.Optional;
59 import org.hamcrest.Matchers;
60 import org.junit.Test;
61 import org.mockito.invocation.InvocationOnMock;
62 import org.mockito.stubbing.Answer;
63
64 /**
65  * Unit test for {@link ConfigurationSoneParser}.
66  *
67  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
68  */
69 public class ConfigurationSoneParserTest {
70
71         private final Configuration configuration = mock(Configuration.class);
72         private final Sone sone = mock(Sone.class);
73         private final ConfigurationSoneParser configurationSoneParser;
74
75         public ConfigurationSoneParserTest() {
76                 when(sone.getId()).thenReturn("1");
77                 configurationSoneParser =
78                                 new ConfigurationSoneParser(configuration, sone);
79         }
80
81         @Test
82         public void emptyProfileIsLoadedCorrectly() {
83                 setupEmptyProfile();
84                 Profile profile = configurationSoneParser.parseProfile();
85                 assertThat(profile, notNullValue());
86                 assertThat(profile.getFirstName(), nullValue());
87                 assertThat(profile.getMiddleName(), nullValue());
88                 assertThat(profile.getLastName(), nullValue());
89                 assertThat(profile.getBirthDay(), nullValue());
90                 assertThat(profile.getBirthMonth(), nullValue());
91                 assertThat(profile.getBirthYear(), nullValue());
92                 assertThat(profile.getFields(), emptyIterable());
93         }
94
95         private void setupEmptyProfile() {
96                 when(configuration.getStringValue(anyString())).thenReturn(
97                                 new TestValue<String>(null));
98                 when(configuration.getIntValue(anyString())).thenReturn(
99                                 new TestValue<Integer>(null));
100         }
101
102         @Test
103         public void filledProfileWithFieldsIsParsedCorrectly() {
104                 setupFilledProfile();
105                 Profile profile = configurationSoneParser.parseProfile();
106                 assertThat(profile, notNullValue());
107                 assertThat(profile.getFirstName(), is("First"));
108                 assertThat(profile.getMiddleName(), is("M."));
109                 assertThat(profile.getLastName(), is("Last"));
110                 assertThat(profile.getBirthDay(), is(18));
111                 assertThat(profile.getBirthMonth(), is(12));
112                 assertThat(profile.getBirthYear(), is(1976));
113                 final List<Field> fields = profile.getFields();
114                 assertThat(fields, hasSize(2));
115                 assertThat(fields.get(0).getName(), is("Field1"));
116                 assertThat(fields.get(0).getValue(), is("Value1"));
117                 assertThat(fields.get(1).getName(), is("Field2"));
118                 assertThat(fields.get(1).getValue(), is("Value2"));
119         }
120
121         private void setupFilledProfile() {
122                 setupString("Sone/1/Profile/FirstName", "First");
123                 setupString("Sone/1/Profile/MiddleName", "M.");
124                 setupString("Sone/1/Profile/LastName", "Last");
125                 setupInteger("Sone/1/Profile/BirthDay", 18);
126                 setupInteger("Sone/1/Profile/BirthMonth", 12);
127                 setupInteger("Sone/1/Profile/BirthYear", 1976);
128                 setupString("Sone/1/Profile/Fields/0/Name", "Field1");
129                 setupString("Sone/1/Profile/Fields/0/Value", "Value1");
130                 setupString("Sone/1/Profile/Fields/1/Name", "Field2");
131                 setupString("Sone/1/Profile/Fields/1/Value", "Value2");
132                 setupString("Sone/1/Profile/Fields/2/Name", null);
133         }
134
135         private void setupString(String nodeName, String value) {
136                 when(configuration.getStringValue(eq(nodeName))).thenReturn(
137                                 new TestValue<String>(value));
138         }
139
140         private void setupInteger(String nodeName, Integer value) {
141                 when(configuration.getIntValue(eq(nodeName))).thenReturn(
142                                 new TestValue<Integer>(value));
143         }
144
145         @Test
146         public void postsAreParsedCorrectly() {
147                 setupCompletePosts();
148                 PostBuilderFactory postBuilderFactory = createPostBuilderFactory();
149                 Collection<Post> posts =
150                                 configurationSoneParser.parsePosts(postBuilderFactory);
151                 assertThat(posts,
152                                 Matchers.<Post>containsInAnyOrder(
153                                                 isPost("P0", 1000L, "T0", Optional.<String>absent()),
154                                                 isPost("P1", 1001L, "T1",
155                                                                 of("1234567890123456789012345678901234567890123"))));
156         }
157
158         private PostBuilderFactory createPostBuilderFactory() {
159                 PostBuilderFactory postBuilderFactory =
160                                 mock(PostBuilderFactory.class);
161                 when(postBuilderFactory.newPostBuilder()).thenAnswer(
162                                 new Answer<PostBuilder>() {
163                                         @Override
164                                         public PostBuilder answer(InvocationOnMock invocation)
165                                         throws Throwable {
166                                                 return new TestPostBuilder();
167                                         }
168                                 });
169                 return postBuilderFactory;
170         }
171
172         private void setupCompletePosts() {
173                 setupPost("0", "P0", 1000L, "T0", null);
174                 setupPost("1", "P1", 1001L, "T1",
175                                 "1234567890123456789012345678901234567890123");
176                 setupPost("2", null, 0L, null, null);
177         }
178
179         private void setupPost(String postNumber, String postId, long time,
180                         String text, String recipientId) {
181                 setupString("Sone/1/Posts/" + postNumber + "/ID", postId);
182                 setupLong("Sone/1/Posts/" + postNumber + "/Time", time);
183                 setupString("Sone/1/Posts/" + postNumber + "/Text", text);
184                 setupString("Sone/1/Posts/" + postNumber + "/Recipient", recipientId);
185         }
186
187         private void setupLong(String nodeName, Long value) {
188                 when(configuration.getLongValue(eq(nodeName))).thenReturn(
189                                 new TestValue<Long>(value));
190         }
191
192         @Test(expected = InvalidPostFound.class)
193         public void postWithoutTimeIsRecognized() {
194                 setupPostWithoutTime();
195                 configurationSoneParser.parsePosts(createPostBuilderFactory());
196         }
197
198         private void setupPostWithoutTime() {
199                 setupPost("0", "P0", 0L, "T0", null);
200         }
201
202         @Test(expected = InvalidPostFound.class)
203         public void postWithoutTextIsRecognized() {
204                 setupPostWithoutText();
205                 configurationSoneParser.parsePosts(createPostBuilderFactory());
206         }
207
208         private void setupPostWithoutText() {
209                 setupPost("0", "P0", 1000L, null, null);
210         }
211
212         @Test
213         public void postWithInvalidRecipientIdIsRecognized() {
214                 setupPostWithInvalidRecipientId();
215                 Collection<Post> posts = configurationSoneParser.parsePosts(
216                                 createPostBuilderFactory());
217                 assertThat(posts, contains(
218                                 isPost("P0", 1000L, "T0", Optional.<String>absent())));
219         }
220
221         private void setupPostWithInvalidRecipientId() {
222                 setupPost("0", "P0", 1000L, "T0", "123");
223                 setupPost("1", null, 0L, null, null);
224         }
225
226         @Test
227         public void postRepliesAreParsedCorrectly() {
228                 setupPostReplies();
229                 PostReplyBuilderFactory postReplyBuilderFactory =
230                                 new PostReplyBuilderFactory() {
231                                         @Override
232                                         public PostReplyBuilder newPostReplyBuilder() {
233                                                 return new TestPostReplyBuilder();
234                                         }
235                                 };
236                 Collection<PostReply> postReplies =
237                                 configurationSoneParser.parsePostReplies(
238                                                 postReplyBuilderFactory);
239                 assertThat(postReplies, hasSize(2));
240                 assertThat(postReplies,
241                                 containsInAnyOrder(isPostReply("R0", "P0", 1000L, "T0"),
242                                                 isPostReply("R1", "P1", 1001L, "T1")));
243         }
244
245         private void setupPostReplies() {
246                 setupPostReply("0", "R0", "P0", 1000L, "T0");
247                 setupPostReply("1", "R1", "P1", 1001L, "T1");
248                 setupPostReply("2", null, null, 0L, null);
249         }
250
251         private void setupPostReply(String postReplyNumber, String postReplyId,
252                         String postId, long time, String text) {
253                 setupString("Sone/1/Replies/" + postReplyNumber + "/ID", postReplyId);
254                 setupString("Sone/1/Replies/" + postReplyNumber + "/Post/ID", postId);
255                 setupLong("Sone/1/Replies/" + postReplyNumber + "/Time", time);
256                 setupString("Sone/1/Replies/" + postReplyNumber + "/Text", text);
257         }
258
259         @Test(expected = InvalidPostReplyFound.class)
260         public void missingPostIdIsRecognized() {
261                 setupPostReplyWithMissingPostId();
262                 configurationSoneParser.parsePostReplies(null);
263         }
264
265         private void setupPostReplyWithMissingPostId() {
266                 setupPostReply("0", "R0", null, 1000L, "T0");
267         }
268
269         @Test(expected = InvalidPostReplyFound.class)
270         public void missingPostReplyTimeIsRecognized() {
271                 setupPostReplyWithMissingPostReplyTime();
272                 configurationSoneParser.parsePostReplies(null);
273         }
274
275         private void setupPostReplyWithMissingPostReplyTime() {
276                 setupPostReply("0", "R0", "P0", 0L, "T0");
277         }
278
279         @Test(expected = InvalidPostReplyFound.class)
280         public void missingPostReplyTextIsRecognized() {
281                 setupPostReplyWithMissingPostReplyText();
282                 configurationSoneParser.parsePostReplies(null);
283         }
284
285         private void setupPostReplyWithMissingPostReplyText() {
286                 setupPostReply("0", "R0", "P0", 1000L, null);
287         }
288
289         @Test
290         public void likedPostIdsParsedCorrectly() {
291                 setupLikedPostIds();
292                 Set<String> likedPostIds =
293                                 configurationSoneParser.parseLikedPostIds();
294                 assertThat(likedPostIds, containsInAnyOrder("P1", "P2", "P3"));
295         }
296
297         private void setupLikedPostIds() {
298                 setupString("Sone/1/Likes/Post/0/ID", "P1");
299                 setupString("Sone/1/Likes/Post/1/ID", "P2");
300                 setupString("Sone/1/Likes/Post/2/ID", "P3");
301                 setupString("Sone/1/Likes/Post/3/ID", null);
302         }
303
304         @Test
305         public void likedPostReplyIdsAreParsedCorrectly() {
306                 setupLikedPostReplyIds();
307                 Set<String> likedPostReplyIds =
308                                 configurationSoneParser.parseLikedPostReplyIds();
309                 assertThat(likedPostReplyIds, containsInAnyOrder("R1", "R2", "R3"));
310         }
311
312         private void setupLikedPostReplyIds() {
313                 setupString("Sone/1/Likes/Reply/0/ID", "R1");
314                 setupString("Sone/1/Likes/Reply/1/ID", "R2");
315                 setupString("Sone/1/Likes/Reply/2/ID", "R3");
316                 setupString("Sone/1/Likes/Reply/3/ID", null);
317         }
318
319         @Test
320         public void friendsAreParsedCorrectly() {
321                 setupFriends();
322                 Set<String> friends = configurationSoneParser.parseFriends();
323                 assertThat(friends, containsInAnyOrder("F1", "F2", "F3"));
324         }
325
326         private void setupFriends() {
327                 setupString("Sone/1/Friends/0/ID", "F1");
328                 setupString("Sone/1/Friends/1/ID", "F2");
329                 setupString("Sone/1/Friends/2/ID", "F3");
330                 setupString("Sone/1/Friends/3/ID", null);
331         }
332
333         @Test
334         public void topLevelAlbumsAreParsedCorrectly() {
335                 setupTopLevelAlbums();
336                 AlbumBuilderFactory albumBuilderFactory = createAlbumBuilderFactory();
337                 List<Album> topLevelAlbums =
338                                 configurationSoneParser.parseTopLevelAlbums(
339                                                 albumBuilderFactory);
340                 assertThat(topLevelAlbums, hasSize(2));
341                 Album firstAlbum = topLevelAlbums.get(0);
342                 assertThat(firstAlbum, isAlbum("A1", null, "T1", "D1", "I1"));
343                 assertThat(firstAlbum.getAlbums(), emptyIterable());
344                 assertThat(firstAlbum.getImages(), emptyIterable());
345                 Album secondAlbum = topLevelAlbums.get(1);
346                 assertThat(secondAlbum, isAlbum("A2", null, "T2", "D2", null));
347                 assertThat(secondAlbum.getAlbums(), hasSize(1));
348                 assertThat(secondAlbum.getImages(), emptyIterable());
349                 Album thirdAlbum = secondAlbum.getAlbums().get(0);
350                 assertThat(thirdAlbum, isAlbum("A3", "A2", "T3", "D3", "I3"));
351                 assertThat(thirdAlbum.getAlbums(), emptyIterable());
352                 assertThat(thirdAlbum.getImages(), emptyIterable());
353         }
354
355         private void setupTopLevelAlbums() {
356                 setupAlbum(0, "A1", null, "T1", "D1", "I1");
357                 setupAlbum(1, "A2", null, "T2", "D2", null);
358                 setupAlbum(2, "A3", "A2", "T3", "D3", "I3");
359                 setupAlbum(3, null, null, null, null, null);
360         }
361
362         private void setupAlbum(int albumNumber, String albumId,
363                         String parentAlbumId,
364                         String title, String description, String imageId) {
365                 final String albumPrefix = "Sone/1/Albums/" + albumNumber;
366                 setupString(albumPrefix + "/ID", albumId);
367                 setupString(albumPrefix + "/Title", title);
368                 setupString(albumPrefix + "/Description", description);
369                 setupString(albumPrefix + "/Parent", parentAlbumId);
370                 setupString(albumPrefix + "/AlbumImage", imageId);
371         }
372
373         private AlbumBuilderFactory createAlbumBuilderFactory() {
374                 AlbumBuilderFactory albumBuilderFactory =
375                                 mock(AlbumBuilderFactory.class);
376                 when(albumBuilderFactory.newAlbumBuilder()).thenAnswer(
377                                 new Answer<AlbumBuilder>() {
378                                         @Override
379                                         public AlbumBuilder answer(InvocationOnMock invocation) {
380                                                 return new TestAlbumBuilder();
381                                         }
382                                 });
383                 return albumBuilderFactory;
384         }
385
386         @Test(expected = InvalidAlbumFound.class)
387         public void albumWithInvalidTitleIsRecognized() {
388                 setupAlbum(0, "A1", null, null, "D1", "I1");
389                 configurationSoneParser.parseTopLevelAlbums(
390                                 createAlbumBuilderFactory());
391         }
392
393         @Test(expected = InvalidAlbumFound.class)
394         public void albumWithInvalidDescriptionIsRecognized() {
395                 setupAlbum(0, "A1", null, "T1", null, "I1");
396                 configurationSoneParser.parseTopLevelAlbums(
397                                 createAlbumBuilderFactory());
398         }
399
400         @Test(expected = InvalidParentAlbumFound.class)
401         public void albumWithInvalidParentIsRecognized() {
402                 setupAlbum(0, "A1", "A0", "T1", "D1", "I1");
403                 configurationSoneParser.parseTopLevelAlbums(
404                                 createAlbumBuilderFactory());
405         }
406
407         @Test
408         public void imagesAreParsedCorrectly() {
409                 setupTopLevelAlbums();
410                 configurationSoneParser.parseTopLevelAlbums(
411                                 createAlbumBuilderFactory());
412                 setupImages();
413                 configurationSoneParser.parseImages(createImageBuilderFactory());
414                 Map<String, Album> albums = configurationSoneParser.getAlbums();
415                 assertThat(albums.get("A1").getImages(),
416                                 contains(isImage("I1", 1000L, "K1", "T1", "D1", 16, 9)));
417                 assertThat(albums.get("A2").getImages(), contains(
418                                 isImage("I2", 2000L, "K2", "T2", "D2", 16 * 2, 9 * 2)));
419                 assertThat(albums.get("A3").getImages(), contains(
420                                 isImage("I3", 3000L, "K3", "T3", "D3", 16 * 3, 9 * 3)));
421         }
422
423         private void setupImages() {
424                 setupImage(0, "I1", "A1", 1000L, "K1", "T1", "D1", 16, 9);
425                 setupImage(1, "I2", "A2", 2000L, "K2", "T2", "D2", 16 * 2, 9 * 2);
426                 setupImage(2, "I3", "A3", 3000L, "K3", "T3", "D3", 16 * 3, 9 * 3);
427                 setupImage(3, null, null, 0L, null, null, null, 0, 0);
428         }
429
430         private void setupImage(int imageNumber, String id,
431                         String parentAlbumId, Long creationTime, String key, String title,
432                         String description, Integer width, Integer height) {
433                 final String imagePrefix = "Sone/1/Images/" + imageNumber;
434                 setupString(imagePrefix + "/ID", id);
435                 setupString(imagePrefix + "/Album", parentAlbumId);
436                 setupLong(imagePrefix + "/CreationTime", creationTime);
437                 setupString(imagePrefix + "/Key", key);
438                 setupString(imagePrefix + "/Title", title);
439                 setupString(imagePrefix + "/Description", description);
440                 setupInteger(imagePrefix + "/Width", width);
441                 setupInteger(imagePrefix + "/Height", height);
442         }
443
444         private ImageBuilderFactory createImageBuilderFactory() {
445                 ImageBuilderFactory imageBuilderFactory =
446                                 mock(ImageBuilderFactory.class);
447                 when(imageBuilderFactory.newImageBuilder()).thenAnswer(
448                                 new Answer<ImageBuilder>() {
449                                         @Override
450                                         public ImageBuilder answer(InvocationOnMock invocation)
451                                         throws Throwable {
452                                                 return new TestImageBuilder();
453                                         }
454                                 });
455                 return imageBuilderFactory;
456         }
457
458         @Test(expected = InvalidImageFound.class)
459         public void missingAlbumIdIsRecognized() {
460                 setupTopLevelAlbums();
461                 configurationSoneParser.parseTopLevelAlbums(
462                                 createAlbumBuilderFactory());
463                 setupImage(0, "I1", null, 1000L, "K1", "T1", "D1", 16, 9);
464                 configurationSoneParser.parseImages(createImageBuilderFactory());
465         }
466
467         @Test(expected = InvalidParentAlbumFound.class)
468         public void invalidAlbumIdIsRecognized() {
469                 setupTopLevelAlbums();
470                 configurationSoneParser.parseTopLevelAlbums(
471                                 createAlbumBuilderFactory());
472                 setupImage(0, "I1", "A4", 1000L, "K1", "T1", "D1", 16, 9);
473                 configurationSoneParser.parseImages(createImageBuilderFactory());
474         }
475
476         @Test(expected = InvalidImageFound.class)
477         public void missingCreationTimeIsRecognized() {
478                 setupTopLevelAlbums();
479                 configurationSoneParser.parseTopLevelAlbums(
480                                 createAlbumBuilderFactory());
481                 setupImage(0, "I1", "A1", null, "K1", "T1", "D1", 16, 9);
482                 configurationSoneParser.parseImages(createImageBuilderFactory());
483         }
484
485         @Test(expected = InvalidImageFound.class)
486         public void missingKeyIsRecognized() {
487                 setupTopLevelAlbums();
488                 configurationSoneParser.parseTopLevelAlbums(
489                                 createAlbumBuilderFactory());
490                 setupImage(0, "I1", "A1", 1000L, null, "T1", "D1", 16, 9);
491                 configurationSoneParser.parseImages(createImageBuilderFactory());
492         }
493
494         @Test(expected = InvalidImageFound.class)
495         public void missingTitleIsRecognized() {
496                 setupTopLevelAlbums();
497                 configurationSoneParser.parseTopLevelAlbums(
498                                 createAlbumBuilderFactory());
499                 setupImage(0, "I1", "A1", 1000L, "K1", null, "D1", 16, 9);
500                 configurationSoneParser.parseImages(createImageBuilderFactory());
501         }
502
503         @Test(expected = InvalidImageFound.class)
504         public void missingDescriptionIsRecognized() {
505                 setupTopLevelAlbums();
506                 configurationSoneParser.parseTopLevelAlbums(
507                                 createAlbumBuilderFactory());
508                 setupImage(0, "I1", "A1", 1000L, "K1", "T1", null, 16, 9);
509                 configurationSoneParser.parseImages(createImageBuilderFactory());
510         }
511
512         @Test(expected = InvalidImageFound.class)
513         public void missingWidthIsRecognized() {
514                 setupTopLevelAlbums();
515                 configurationSoneParser.parseTopLevelAlbums(
516                                 createAlbumBuilderFactory());
517                 setupImage(0, "I1", "A1", 1000L, "K1", "T1", "D1", null, 9);
518                 configurationSoneParser.parseImages(createImageBuilderFactory());
519         }
520
521         @Test(expected = InvalidImageFound.class)
522         public void missingHeightIsRecognized() {
523                 setupTopLevelAlbums();
524                 configurationSoneParser.parseTopLevelAlbums(
525                                 createAlbumBuilderFactory());
526                 setupImage(0, "I1", "A1", 1000L, "K1", "T1", "D1", 16, null);
527                 configurationSoneParser.parseImages(createImageBuilderFactory());
528         }
529
530         private static class TestValue<T> implements Value<T> {
531
532                 private final AtomicReference<T> value = new AtomicReference<T>();
533
534                 public TestValue(T originalValue) {
535                         value.set(originalValue);
536                 }
537
538                 @Override
539                 public T getValue() throws ConfigurationException {
540                         return value.get();
541                 }
542
543                 @Override
544                 public T getValue(T defaultValue) {
545                         final T realValue = value.get();
546                         return (realValue != null) ? realValue : defaultValue;
547                 }
548
549                 @Override
550                 public void setValue(T newValue) throws ConfigurationException {
551                         value.set(newValue);
552                 }
553
554         }
555
556         private static class TestPostBuilder implements PostBuilder {
557
558                 private final Post post = mock(Post.class);
559                 private String recipientId = null;
560
561                 @Override
562                 public PostBuilder copyPost(Post post) throws NullPointerException {
563                         return this;
564                 }
565
566                 @Override
567                 public PostBuilder from(String senderId) {
568                         final Sone sone = mock(Sone.class);
569                         when(sone.getId()).thenReturn(senderId);
570                         when(post.getSone()).thenReturn(sone);
571                         return this;
572                 }
573
574                 @Override
575                 public PostBuilder randomId() {
576                         when(post.getId()).thenReturn(randomUUID().toString());
577                         return this;
578                 }
579
580                 @Override
581                 public PostBuilder withId(String id) {
582                         when(post.getId()).thenReturn(id);
583                         return this;
584                 }
585
586                 @Override
587                 public PostBuilder currentTime() {
588                         when(post.getTime()).thenReturn(currentTimeMillis());
589                         return this;
590                 }
591
592                 @Override
593                 public PostBuilder withTime(long time) {
594                         when(post.getTime()).thenReturn(time);
595                         return this;
596                 }
597
598                 @Override
599                 public PostBuilder withText(String text) {
600                         when(post.getText()).thenReturn(text);
601                         return this;
602                 }
603
604                 @Override
605                 public PostBuilder to(String recipientId) {
606                         this.recipientId = recipientId;
607                         return this;
608                 }
609
610                 @Override
611                 public Post build() throws IllegalStateException {
612                         when(post.getRecipientId()).thenReturn(fromNullable(recipientId));
613                         return post;
614                 }
615
616         }
617
618         private static class TestPostReplyBuilder implements PostReplyBuilder {
619
620                 private final PostReply postReply = mock(PostReply.class);
621
622                 @Override
623                 public PostReplyBuilder to(String postId) {
624                         when(postReply.getPostId()).thenReturn(postId);
625                         return this;
626                 }
627
628                 @Override
629                 public PostReply build() throws IllegalStateException {
630                         return postReply;
631                 }
632
633                 @Override
634                 public PostReplyBuilder randomId() {
635                         when(postReply.getId()).thenReturn(randomUUID().toString());
636                         return this;
637                 }
638
639                 @Override
640                 public PostReplyBuilder withId(String id) {
641                         when(postReply.getId()).thenReturn(id);
642                         return this;
643                 }
644
645                 @Override
646                 public PostReplyBuilder from(String senderId) {
647                         Sone sone = mock(Sone.class);
648                         when(sone.getId()).thenReturn(senderId);
649                         when(postReply.getSone()).thenReturn(sone);
650                         return this;
651                 }
652
653                 @Override
654                 public PostReplyBuilder currentTime() {
655                         when(postReply.getTime()).thenReturn(currentTimeMillis());
656                         return this;
657                 }
658
659                 @Override
660                 public PostReplyBuilder withTime(long time) {
661                         when(postReply.getTime()).thenReturn(time);
662                         return this;
663                 }
664
665                 @Override
666                 public PostReplyBuilder withText(String text) {
667                         when(postReply.getText()).thenReturn(text);
668                         return this;
669                 }
670
671         }
672
673         private static class TestAlbumBuilder implements AlbumBuilder {
674
675                 private final Album album = mock(Album.class);
676                 private final List<Album> albums = new ArrayList<Album>();
677                 private final List<Image> images = new ArrayList<Image>();
678                 private Album parentAlbum;
679                 private String title;
680                 private String description;
681                 private String imageId;
682
683                 public TestAlbumBuilder() {
684                         when(album.getTitle()).thenAnswer(new Answer<String>() {
685                                 @Override
686                                 public String answer(InvocationOnMock invocation) {
687                                         return title;
688                                 }
689                         });
690                         when(album.getDescription()).thenAnswer(new Answer<String>() {
691                                 @Override
692                                 public String answer(InvocationOnMock invocation) {
693                                         return description;
694                                 }
695                         });
696                         when(album.getAlbumImage()).thenAnswer(new Answer<Image>() {
697                                 @Override
698                                 public Image answer(InvocationOnMock invocation) {
699                                         if (imageId == null) {
700                                                 return null;
701                                         }
702                                         Image image = mock(Image.class);
703                                         when(image.getId()).thenReturn(imageId);
704                                         return image;
705                                 }
706                         });
707                         when(album.getAlbums()).thenReturn(albums);
708                         when(album.getImages()).thenReturn(images);
709                         doAnswer(new Answer<Void>() {
710                                 @Override
711                                 public Void answer(InvocationOnMock invocation) {
712                                         albums.add((Album) invocation.getArguments()[0]);
713                                         ((Album) invocation.getArguments()[0]).setParent(album);
714                                         return null;
715                                 }
716                         }).when(album).addAlbum(any(Album.class));
717                         doAnswer(new Answer<Void>() {
718                                 @Override
719                                 public Void answer(InvocationOnMock invocation) {
720                                         images.add((Image) invocation.getArguments()[0]);
721                                         return null;
722                                 }
723                         }).when(album).addImage(any(Image.class));
724                         doAnswer(new Answer<Void>() {
725                                 @Override
726                                 public Void answer(InvocationOnMock invocation) {
727                                         parentAlbum = (Album) invocation.getArguments()[0];
728                                         return null;
729                                 }
730                         }).when(album).setParent(any(Album.class));
731                         when(album.getParent()).thenAnswer(new Answer<Album>() {
732                                 @Override
733                                 public Album answer(InvocationOnMock invocation) {
734                                         return parentAlbum;
735                                 }
736                         });
737                         when(album.modify()).thenReturn(new Modifier() {
738                                 @Override
739                                 public Modifier setTitle(String title) {
740                                         TestAlbumBuilder.this.title = title;
741                                         return this;
742                                 }
743
744                                 @Override
745                                 public Modifier setDescription(String description) {
746                                         TestAlbumBuilder.this.description = description;
747                                         return this;
748                                 }
749
750                                 @Override
751                                 public Modifier setAlbumImage(String imageId) {
752                                         TestAlbumBuilder.this.imageId = imageId;
753                                         return this;
754                                 }
755
756                                 @Override
757                                 public Album update() throws IllegalStateException {
758                                         return album;
759                                 }
760                         });
761                 }
762
763                 @Override
764                 public AlbumBuilder randomId() {
765                         when(album.getId()).thenReturn(randomUUID().toString());
766                         return this;
767                 }
768
769                 @Override
770                 public AlbumBuilder withId(String id) {
771                         when(album.getId()).thenReturn(id);
772                         return this;
773                 }
774
775                 @Override
776                 public AlbumBuilder by(Sone sone) {
777                         when(album.getSone()).thenReturn(sone);
778                         return this;
779                 }
780
781                 @Override
782                 public Album build() throws IllegalStateException {
783                         return album;
784                 }
785
786         }
787
788         private static class TestImageBuilder implements ImageBuilder {
789
790                 private final Image image;
791
792                 private TestImageBuilder() {
793                         image = mock(Image.class);
794                         Image.Modifier imageModifier = new Image.Modifier() {
795                                 private Sone sone = image.getSone();
796                                 private long creationTime = image.getCreationTime();
797                                 private String key = image.getKey();
798                                 private String title = image.getTitle();
799                                 private String description = image.getDescription();
800                                 private int width = image.getWidth();
801                                 private int height = image.getHeight();
802
803                                 @Override
804                                 public Image.Modifier setSone(Sone sone) {
805                                         this.sone = sone;
806                                         return this;
807                                 }
808
809                                 @Override
810                                 public Image.Modifier setCreationTime(long creationTime) {
811                                         this.creationTime = creationTime;
812                                         return this;
813                                 }
814
815                                 @Override
816                                 public Image.Modifier setKey(String key) {
817                                         this.key = key;
818                                         return this;
819                                 }
820
821                                 @Override
822                                 public Image.Modifier setTitle(String title) {
823                                         this.title = title;
824                                         return this;
825                                 }
826
827                                 @Override
828                                 public Image.Modifier setDescription(String description) {
829                                         this.description = description;
830                                         return this;
831                                 }
832
833                                 @Override
834                                 public Image.Modifier setWidth(int width) {
835                                         this.width = width;
836                                         return this;
837                                 }
838
839                                 @Override
840                                 public Image.Modifier setHeight(int height) {
841                                         this.height = height;
842                                         return this;
843                                 }
844
845                                 @Override
846                                 public Image update() throws IllegalStateException {
847                                         when(image.getSone()).thenReturn(sone);
848                                         when(image.getCreationTime()).thenReturn(creationTime);
849                                         when(image.getKey()).thenReturn(key);
850                                         when(image.getTitle()).thenReturn(title);
851                                         when(image.getDescription()).thenReturn(description);
852                                         when(image.getWidth()).thenReturn(width);
853                                         when(image.getHeight()).thenReturn(height);
854                                         return image;
855                                 }
856                         };
857                         when(image.modify()).thenReturn(imageModifier);
858                 }
859
860                 @Override
861                 public ImageBuilder randomId() {
862                         when(image.getId()).thenReturn(randomUUID().toString());
863                         return this;
864                 }
865
866                 @Override
867                 public ImageBuilder withId(String id) {
868                         when(image.getId()).thenReturn(id);
869                         return this;
870                 }
871
872                 @Override
873                 public Image build() throws IllegalStateException {
874                         return image;
875                 }
876
877         }
878
879 }