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