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