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