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