8a4bd520b9bda5ceefa3b3a16688d22a5cd1df16
[Sone.git] / src / test / java / net / pterodactylus / sone / core / SoneInserterTest.java
1 package net.pterodactylus.sone.core;
2
3 import static com.google.common.base.Optional.of;
4 import static com.google.common.io.ByteStreams.toByteArray;
5 import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
6 import static java.lang.System.currentTimeMillis;
7 import static org.hamcrest.MatcherAssert.assertThat;
8 import static org.hamcrest.Matchers.containsInAnyOrder;
9 import static org.hamcrest.Matchers.containsString;
10 import static org.hamcrest.Matchers.instanceOf;
11 import static org.hamcrest.Matchers.is;
12 import static org.hamcrest.Matchers.nullValue;
13 import static org.mockito.Matchers.any;
14 import static org.mockito.Matchers.anyString;
15 import static org.mockito.Matchers.argThat;
16 import static org.mockito.Matchers.eq;
17 import static org.mockito.Mockito.doAnswer;
18 import static org.mockito.Mockito.mock;
19 import static org.mockito.Mockito.never;
20 import static org.mockito.Mockito.times;
21 import static org.mockito.Mockito.verify;
22 import static org.mockito.Mockito.when;
23 import static org.mockito.Mockito.withSettings;
24
25 import java.io.IOException;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import net.pterodactylus.sone.core.SoneInserter.InsertInformation;
30 import net.pterodactylus.sone.core.SoneInserter.ManifestCreator;
31 import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
32 import net.pterodactylus.sone.core.event.SoneEvent;
33 import net.pterodactylus.sone.core.event.SoneInsertAbortedEvent;
34 import net.pterodactylus.sone.core.event.SoneInsertedEvent;
35 import net.pterodactylus.sone.core.event.SoneInsertingEvent;
36 import net.pterodactylus.sone.data.Album;
37 import net.pterodactylus.sone.data.LocalSone;
38 import net.pterodactylus.sone.data.Sone;
39 import net.pterodactylus.sone.main.SonePlugin;
40
41 import freenet.client.async.ManifestElement;
42 import freenet.keys.FreenetURI;
43
44 import com.google.common.base.Charsets;
45 import com.google.common.base.Optional;
46 import com.google.common.eventbus.AsyncEventBus;
47 import com.google.common.eventbus.EventBus;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.mockito.ArgumentCaptor;
51 import org.mockito.invocation.InvocationOnMock;
52 import org.mockito.stubbing.Answer;
53
54 /**
55  * Unit test for {@link SoneInserter} and its subclasses.
56  *
57  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
58  */
59 public class SoneInserterTest {
60
61         private final Core core = mock(Core.class);
62         private final EventBus eventBus = mock(EventBus.class);
63         private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
64
65         @Before
66         public void setupCore() {
67                 UpdateChecker updateChecker = mock(UpdateChecker.class);
68                 when(core.getUpdateChecker()).thenReturn(updateChecker);
69                 when(core.getSone(anyString())).thenReturn(Optional.<Sone>absent());
70                 when(core.getLocalSone(anyString())).thenReturn(Optional.<LocalSone>absent());
71         }
72
73         @Test
74         public void insertionDelayIsForwardedToSoneInserter() {
75                 EventBus eventBus = new AsyncEventBus(sameThreadExecutor());
76                 eventBus.register(new SoneInserter(core, eventBus, freenetInterface, "SoneId"));
77                 eventBus.post(new InsertionDelayChangedEvent(15));
78                 assertThat(SoneInserter.getInsertionDelay().get(), is(15));
79         }
80
81         private Sone createSone(FreenetURI insertUri, String fingerprint) {
82                 Sone sone = mock(Sone.class, withSettings().extraInterfaces(LocalSone.class));
83                 when(sone.getInsertUri()).thenReturn(insertUri);
84                 when(sone.getFingerprint()).thenReturn(fingerprint);
85                 when(sone.getRootAlbum()).thenReturn(mock(Album.class));
86                 when(core.getSone(anyString())).thenReturn(of(sone));
87                 when(core.getLocalSone(anyString())).thenReturn(of((LocalSone) sone));
88                 return sone;
89         }
90
91         @Test
92         public void isModifiedIsTrueIfModificationDetectorSaysSo() {
93                 SoneModificationDetector soneModificationDetector = mock(SoneModificationDetector.class);
94                 when(soneModificationDetector.isModified()).thenReturn(true);
95                 SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId", soneModificationDetector, 1);
96                 assertThat(soneInserter.isModified(), is(true));
97         }
98
99         @Test
100         public void isModifiedIsFalseIfModificationDetectorSaysSo() {
101                 SoneModificationDetector soneModificationDetector = mock(SoneModificationDetector.class);
102                 SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId", soneModificationDetector, 1);
103                 assertThat(soneInserter.isModified(), is(false));
104         }
105
106         @Test
107         public void lastFingerprintIsStoredCorrectly() {
108                 SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId");
109                 soneInserter.setLastInsertFingerprint("last-fingerprint");
110                 assertThat(soneInserter.getLastInsertFingerprint(), is("last-fingerprint"));
111         }
112
113         @Test
114         public void soneInserterStopsWhenItShould() {
115                 SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId");
116                 soneInserter.stop();
117                 soneInserter.serviceRun();
118         }
119
120         @Test
121         public void soneInserterInsertsASoneIfItIsEligible() throws SoneException {
122                 FreenetURI insertUri = mock(FreenetURI.class);
123                 final FreenetURI finalUri = mock(FreenetURI.class);
124                 String fingerprint = "fingerprint";
125                 Sone sone = createSone(insertUri, fingerprint);
126                 SoneModificationDetector soneModificationDetector = mock(SoneModificationDetector.class);
127                 when(soneModificationDetector.isEligibleForInsert()).thenReturn(true);
128                 when(freenetInterface.insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"))).thenReturn(finalUri);
129                 final SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId", soneModificationDetector, 1);
130                 doAnswer(new Answer<Void>() {
131                         @Override
132                         public Void answer(InvocationOnMock invocation) throws Throwable {
133                                 soneInserter.stop();
134                                 return null;
135                         }
136                 }).when(core).touchConfiguration();
137                 soneInserter.serviceRun();
138                 ArgumentCaptor<SoneEvent> soneEvents = ArgumentCaptor.forClass(SoneEvent.class);
139                 verify(freenetInterface).insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"));
140                 verify(eventBus, times(2)).post(soneEvents.capture());
141                 assertThat(soneEvents.getAllValues().get(0), instanceOf(SoneInsertingEvent.class));
142                 assertThat(soneEvents.getAllValues().get(0).sone(), is(sone));
143                 assertThat(soneEvents.getAllValues().get(1), instanceOf(SoneInsertedEvent.class));
144                 assertThat(soneEvents.getAllValues().get(1).sone(), is(sone));
145         }
146
147         @Test
148         public void soneInserterBailsOutIfItIsStoppedWhileInserting() throws SoneException {
149                 FreenetURI insertUri = mock(FreenetURI.class);
150                 final FreenetURI finalUri = mock(FreenetURI.class);
151                 String fingerprint = "fingerprint";
152                 Sone sone = createSone(insertUri, fingerprint);
153                 SoneModificationDetector soneModificationDetector = mock(SoneModificationDetector.class);
154                 when(soneModificationDetector.isEligibleForInsert()).thenReturn(true);
155                 final SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId", soneModificationDetector, 1);
156                 when(freenetInterface.insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"))).thenAnswer(new Answer<FreenetURI>() {
157                         @Override
158                         public FreenetURI answer(InvocationOnMock invocation) throws Throwable {
159                                 soneInserter.stop();
160                                 return finalUri;
161                         }
162                 });
163                 soneInserter.serviceRun();
164                 ArgumentCaptor<SoneEvent> soneEvents = ArgumentCaptor.forClass(SoneEvent.class);
165                 verify(freenetInterface).insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"));
166                 verify(eventBus, times(2)).post(soneEvents.capture());
167                 assertThat(soneEvents.getAllValues().get(0), instanceOf(SoneInsertingEvent.class));
168                 assertThat(soneEvents.getAllValues().get(0).sone(), is(sone));
169                 assertThat(soneEvents.getAllValues().get(1), instanceOf(SoneInsertedEvent.class));
170                 assertThat(soneEvents.getAllValues().get(1).sone(), is(sone));
171                 verify(core, never()).touchConfiguration();
172         }
173
174         @Test
175         public void soneInserterDoesNotInsertSoneIfItIsNotEligible() throws SoneException {
176                 FreenetURI insertUri = mock(FreenetURI.class);
177                 String fingerprint = "fingerprint";
178                 Sone sone = createSone(insertUri, fingerprint);
179                 SoneModificationDetector soneModificationDetector = mock(SoneModificationDetector.class);
180                 final SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId", soneModificationDetector, 1);
181                 new Thread(new Runnable() {
182                         @Override
183                         public void run() {
184                                 try {
185                                         Thread.sleep(500);
186                                 } catch (InterruptedException ie1) {
187                                         throw new RuntimeException(ie1);
188                                 }
189                                 soneInserter.stop();
190                         }
191                 }).start();
192                 soneInserter.serviceRun();
193                 verify(freenetInterface, never()).insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"));
194                 verify(eventBus, never()).post(argThat(org.hamcrest.Matchers.any(SoneEvent.class)));
195         }
196
197         @Test
198         public void soneInserterPostsAbortedEventIfAnExceptionOccurs() throws SoneException {
199                 FreenetURI insertUri = mock(FreenetURI.class);
200                 String fingerprint = "fingerprint";
201                 Sone sone = createSone(insertUri, fingerprint);
202                 SoneModificationDetector soneModificationDetector = mock(SoneModificationDetector.class);
203                 when(soneModificationDetector.isEligibleForInsert()).thenReturn(true);
204                 final SoneInserter soneInserter = new SoneInserter(core, eventBus, freenetInterface, "SoneId", soneModificationDetector, 1);
205                 final SoneException soneException = new SoneException(new Exception());
206                 when(freenetInterface.insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"))).thenAnswer(new Answer<FreenetURI>() {
207                         @Override
208                         public FreenetURI answer(InvocationOnMock invocation) throws Throwable {
209                                 soneInserter.stop();
210                                 throw soneException;
211                         }
212                 });
213                 soneInserter.serviceRun();
214                 ArgumentCaptor<SoneEvent> soneEvents = ArgumentCaptor.forClass(SoneEvent.class);
215                 verify(freenetInterface).insertDirectory(eq(insertUri), any(HashMap.class), eq("index.html"));
216                 verify(eventBus, times(2)).post(soneEvents.capture());
217                 assertThat(soneEvents.getAllValues().get(0), instanceOf(SoneInsertingEvent.class));
218                 assertThat(soneEvents.getAllValues().get(0).sone(), is(sone));
219                 assertThat(soneEvents.getAllValues().get(1), instanceOf(SoneInsertAbortedEvent.class));
220                 assertThat(soneEvents.getAllValues().get(1).sone(), is(sone));
221                 verify(core, never()).touchConfiguration();
222         }
223
224         @Test
225         public void soneInserterExitsIfSoneIsUnknown() {
226                 SoneModificationDetector soneModificationDetector =
227                                 mock(SoneModificationDetector.class);
228                 SoneInserter soneInserter =
229                                 new SoneInserter(core, eventBus, freenetInterface, "SoneId",
230                                                 soneModificationDetector, 1);
231                 when(soneModificationDetector.isEligibleForInsert()).thenReturn(true);
232                 when(core.getSone("SoneId")).thenReturn(Optional.<Sone>absent());
233                 soneInserter.serviceRun();
234         }
235
236         @Test
237         public void soneInserterCatchesExceptionAndContinues() {
238                 SoneModificationDetector soneModificationDetector =
239                                 mock(SoneModificationDetector.class);
240                 final SoneInserter soneInserter =
241                                 new SoneInserter(core, eventBus, freenetInterface, "SoneId",
242                                                 soneModificationDetector, 1);
243                 Answer<Optional<Sone>> stopInserterAndThrowException =
244                                 new Answer<Optional<Sone>>() {
245                                         @Override
246                                         public Optional<Sone> answer(
247                                                         InvocationOnMock invocation) {
248                                                 soneInserter.stop();
249                                                 throw new NullPointerException();
250                                         }
251                                 };
252                 when(soneModificationDetector.isEligibleForInsert()).thenAnswer(
253                                 stopInserterAndThrowException);
254                 soneInserter.serviceRun();
255         }
256
257         @Test
258         public void templateIsRenderedCorrectlyForManifestElement()
259         throws IOException {
260                 Map<String, Object> soneProperties = new HashMap<String, Object>();
261                 soneProperties.put("id", "SoneId");
262                 ManifestCreator manifestCreator = new ManifestCreator(core, soneProperties);
263                 long now = currentTimeMillis();
264                 when(core.getStartupTime()).thenReturn(now);
265                 ManifestElement manifestElement = manifestCreator.createManifestElement("test.txt", "plain/text; charset=utf-8", "sone-inserter-manifest.txt");
266                 assertThat(manifestElement.getName(), is("test.txt"));
267                 assertThat(manifestElement.getMimeTypeOverride(), is("plain/text; charset=utf-8"));
268                 String templateContent = new String(toByteArray(manifestElement.getData().getInputStream()), Charsets.UTF_8);
269                 assertThat(templateContent, containsString("Sone Version: " + SonePlugin.VERSION.toString() + "\n"));
270                 assertThat(templateContent, containsString("Core Startup: " + now + "\n"));
271                 assertThat(templateContent, containsString("Sone ID: " + "SoneId" + "\n"));
272         }
273
274         @Test
275         public void invalidTemplateReturnsANullManifestElement() {
276                 Map<String, Object> soneProperties = new HashMap<String, Object>();
277                 ManifestCreator manifestCreator = new ManifestCreator(core, soneProperties);
278                 assertThat(manifestCreator.createManifestElement("test.txt",
279                                 "plain/text; charset=utf-8",
280                                 "sone-inserter-invalid-manifest.txt"),
281                                 nullValue());
282         }
283
284         @Test
285         public void errorWhileRenderingTemplateReturnsANullManifestElement() {
286                 Map<String, Object> soneProperties = new HashMap<String, Object>();
287                 ManifestCreator manifestCreator = new ManifestCreator(core, soneProperties);
288                 when(core.toString()).thenThrow(NullPointerException.class);
289                 assertThat(manifestCreator.createManifestElement("test.txt",
290                                 "plain/text; charset=utf-8",
291                                 "sone-inserter-faulty-manifest.txt"),
292                                 nullValue());
293         }
294
295 }