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