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