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