✨ Show audio player for links to audio files
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / core / DefaultElementLoaderTest.kt
1 package net.pterodactylus.sone.core
2
3 import com.google.common.base.Ticker
4 import com.google.common.io.ByteStreams
5 import freenet.keys.FreenetURI
6 import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback
7 import net.pterodactylus.sone.test.capture
8 import net.pterodactylus.sone.test.mock
9 import org.hamcrest.MatcherAssert.assertThat
10 import org.hamcrest.Matchers.equalTo
11 import org.mockito.ArgumentMatchers.any
12 import org.mockito.ArgumentMatchers.eq
13 import org.mockito.Mockito.`when`
14 import org.mockito.Mockito.times
15 import org.mockito.Mockito.verify
16 import java.io.ByteArrayOutputStream
17 import java.util.concurrent.TimeUnit
18 import kotlin.test.Test
19
20 /**
21  * Unit test for [DefaultElementLoaderTest].
22  */
23 class DefaultElementLoaderTest {
24
25         private val freenetInterface = mock<FreenetInterface>()
26         private val ticker = mock<Ticker>()
27         private val elementLoader = DefaultElementLoader(freenetInterface, ticker)
28         private val callback = capture<BackgroundFetchCallback>()
29
30         @Test
31         fun `image loader starts request for link that is not known`() {
32                 elementLoader.loadElement(IMAGE_ID)
33                 verify(freenetInterface).startFetch(eq(freenetURI), any<BackgroundFetchCallback>())
34         }
35
36         @Test
37         fun `element loader only starts request once`() {
38                 elementLoader.loadElement(IMAGE_ID)
39                 elementLoader.loadElement(IMAGE_ID)
40                 verify(freenetInterface).startFetch(eq(freenetURI), any<BackgroundFetchCallback>())
41         }
42
43         @Test
44         fun `element loader returns loading element on first call`() {
45                 assertThat(elementLoader.loadElement(IMAGE_ID).loading, equalTo(true))
46         }
47
48         @Test
49         fun `element loader does not cancel on image mime type with 2 mib size`() {
50                 elementLoader.loadElement(IMAGE_ID)
51                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
52                 assertThat(callback.value.shouldCancel(freenetURI, "image/png", sizeOkay), equalTo(false))
53         }
54
55         @Test
56         fun `element loader does cancel on image mime type with more than 2 mib size`() {
57                 elementLoader.loadElement(IMAGE_ID)
58                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
59                 assertThat(callback.value.shouldCancel(freenetURI, "image/png", sizeNotOkay), equalTo(true))
60         }
61
62         @Test
63         fun `element loader does cancel on audio mime type`() {
64                 elementLoader.loadElement(IMAGE_ID)
65                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
66                 assertThat(callback.value.shouldCancel(freenetURI, "audio/mpeg", sizeOkay), equalTo(true))
67         }
68
69         @Test
70         fun `element loader does cancel on video mime type`() {
71                 elementLoader.loadElement(IMAGE_ID)
72                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
73                 assertThat(callback.value.shouldCancel(freenetURI, "video/mkv", sizeOkay), equalTo(true))
74         }
75
76         @Test
77         fun `element loader does cancel on text mime type`() {
78                 elementLoader.loadElement(IMAGE_ID)
79                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
80                 assertThat(callback.value.shouldCancel(freenetURI, "text/plain", sizeOkay), equalTo(true))
81         }
82
83         @Test
84         fun `element loader does not cancel on text html mime type`() {
85                 elementLoader.loadElement(IMAGE_ID)
86                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
87                 assertThat(callback.value.shouldCancel(freenetURI, "text/html", sizeOkay), equalTo(false))
88         }
89
90         @Test
91         fun `image loader can load image`() {
92                 elementLoader.loadElement(decomposedKey)
93                 verify(freenetInterface).startFetch(eq(FreenetURI(decomposedKey)), callback.capture())
94                 callback.value.loaded(FreenetURI(normalizedKey), "image/png", read("/static/images/unknown-image-0.png"))
95                 val linkedElement = elementLoader.loadElement(decomposedKey)
96                 assertThat(linkedElement, equalTo(LinkedElement(normalizedKey, properties = mapOf(
97                                 "type" to "image", "size" to 2451, "sizeHuman" to "2 KiB"
98                 ))))
99         }
100
101         @Test
102         fun `image loader returns element for audio data`() {
103                 elementLoader.loadElement(audioKey)
104                 verify(freenetInterface).startFetch(eq(FreenetURI(audioKey)), callback.capture())
105                 callback.value.shouldCancel(FreenetURI(audioKey), "audio/mpeg", 123)
106                 val linkedElement = elementLoader.loadElement(audioKey)
107                 assertThat(linkedElement, equalTo(LinkedElement(audioKey, properties = mapOf(
108                                 "type" to "audio", "size" to 123L, "sizeHuman" to "123 B"
109                 ))))
110         }
111
112         @Test
113         fun `element loader can extract description from description header`() {
114                 elementLoader.loadElement(textKey)
115                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
116                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader.html"))
117                 val linkedElement = elementLoader.loadElement(textKey)
118                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
119                                 "type" to "html",
120                                 "size" to 266,
121                                 "sizeHuman" to "266 B",
122                                 "title" to "Some Nice Page Title",
123                                 "description" to "This is an example of a very nice freesite."
124                 ))))
125         }
126
127         @Test
128         fun `element loader can extract description from first non-heading paragraph`() {
129                 elementLoader.loadElement(textKey)
130                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
131                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader2.html"))
132                 val linkedElement = elementLoader.loadElement(textKey)
133                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
134                                 "type" to "html",
135                                 "size" to 185,
136                                 "sizeHuman" to "185 B",
137                                 "title" to "Some Nice Page Title",
138                                 "description" to "This is the first paragraph of the very nice freesite."
139                 ))))
140         }
141
142         @Test
143         fun `element loader can not extract description if html is more complicated`() {
144                 elementLoader.loadElement(textKey)
145                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
146                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader3.html"))
147                 val linkedElement = elementLoader.loadElement(textKey)
148                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
149                                 "type" to "html",
150                                 "size" to 204,
151                                 "sizeHuman" to "204 B",
152                                 "title" to "Some Nice Page Title",
153                                 "description" to null
154                 ))))
155         }
156
157         @Test
158         fun `element loader can not extract title if it is missing`() {
159                 elementLoader.loadElement(textKey)
160                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
161                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader4.html"))
162                 val linkedElement = elementLoader.loadElement(textKey)
163                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
164                                 "type" to "html",
165                                 "size" to 229,
166                                 "sizeHuman" to "229 B",
167                                 "title" to null,
168                                 "description" to "This is an example of a very nice freesite."
169                 ))))
170         }
171
172         @Test
173         fun `image is not loaded again after it failed`() {
174                 elementLoader.loadElement(IMAGE_ID)
175                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
176                 callback.value.failed(freenetURI)
177                 assertThat(elementLoader.loadElement(IMAGE_ID).failed, equalTo(true))
178                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
179         }
180
181         @Test
182         fun `image is loaded again after failure cache is expired`() {
183                 elementLoader.loadElement(IMAGE_ID)
184                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
185                 callback.value.failed(freenetURI)
186                 `when`(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31))
187                 val linkedElement = elementLoader.loadElement(IMAGE_ID)
188                 assertThat(linkedElement.failed, equalTo(false))
189                 assertThat(linkedElement.loading, equalTo(true))
190                 verify(freenetInterface, times(2)).startFetch(eq(freenetURI), callback.capture())
191         }
192
193         private fun read(resource: String): ByteArray =
194                         javaClass.getResourceAsStream(resource)?.use { input ->
195                                 ByteArrayOutputStream().use {
196                                         ByteStreams.copy(input, it)
197                                         it
198                                 }.toByteArray()
199                         } ?: ByteArray(0)
200
201 }
202
203 private const val IMAGE_ID = "KSK@gpl.png"
204 private val freenetURI = FreenetURI(IMAGE_ID)
205 private const val decomposedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/fru%CC%88hstu%CC%88ck.jpg"
206 private const val audioKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/audio.mp3"
207 private const val normalizedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/frühstück.jpg"
208 private const val textKey = "KSK@gpl.html"
209 private const val sizeOkay = 2097152L
210 private const val sizeNotOkay = sizeOkay + 1