Add test for not cancelling on HTML MIME type
[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 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 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 `element loader does not cancel on text html mime type`() {
96                 elementLoader.loadElement(IMAGE_ID)
97                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
98                 assertThat(callback.value.shouldCancel(freenetURI, "text/html", sizeOkay), `is`(false))
99         }
100
101         @Test
102         fun `image loader can load image`() {
103                 elementLoader.loadElement(decomposedKey)
104                 verify(freenetInterface).startFetch(eq(FreenetURI(decomposedKey)), callback.capture())
105                 callback.value.loaded(FreenetURI(normalizedKey), "image/png", read("/static/images/unknown-image-0.png"))
106                 val linkedElement = elementLoader.loadElement(decomposedKey)
107                 assertThat(linkedElement, `is`(LinkedElement(normalizedKey, properties = mapOf("size" to 2451, "sizeHuman" to "2 KiB"))))
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                                 "size" to 266,
118                                 "sizeHuman" to "266 B",
119                                 "title" to "Some Nice Page Title",
120                                 "description" to "This is an example of a very nice freesite."
121                 ))))
122         }
123
124         @Test
125         fun `element loader can extract description from first non-heading paragraph`() {
126             elementLoader.loadElement(textKey)
127                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
128                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader2.html"))
129                 val linkedElement = elementLoader.loadElement(textKey)
130                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
131                                 "size" to 185,
132                                 "sizeHuman" to "185 B",
133                                 "title" to "Some Nice Page Title",
134                                 "description" to "This is the first paragraph of the very nice freesite."
135                 ))))
136         }
137
138         @Test
139         fun `element loader can not extract description if html is more complicated`() {
140             elementLoader.loadElement(textKey)
141                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
142                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader3.html"))
143                 val linkedElement = elementLoader.loadElement(textKey)
144                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
145                                 "size" to 204,
146                                 "sizeHuman" to "204 B",
147                                 "title" to "Some Nice Page Title",
148                                 "description" to null
149                 ))))
150         }
151
152         @Test
153         fun `element loader can not extract title if it is missing`() {
154             elementLoader.loadElement(textKey)
155                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
156                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader4.html"))
157                 val linkedElement = elementLoader.loadElement(textKey)
158                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
159                                 "size" to 229,
160                                 "sizeHuman" to "229 B",
161                                 "title" to null,
162                                 "description" to "This is an example of a very nice freesite."
163                 ))))
164         }
165
166         @Test
167         fun `image is not loaded again after it failed`() {
168                 elementLoader.loadElement(IMAGE_ID)
169                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
170                 callback.value.failed(freenetURI)
171                 assertThat(elementLoader.loadElement(IMAGE_ID).failed, `is`(true))
172                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
173         }
174
175         @Test
176         fun `image is loaded again after failure cache is expired`() {
177                 elementLoader.loadElement(IMAGE_ID)
178                 verify(freenetInterface).startFetch(eq(freenetURI), callback.capture())
179                 callback.value.failed(freenetURI)
180                 `when`(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31))
181                 val linkedElement = elementLoader.loadElement(IMAGE_ID)
182                 assertThat(linkedElement.failed, `is`(false))
183                 assertThat(linkedElement.loading, `is`(true))
184                 verify(freenetInterface, times(2)).startFetch(eq(freenetURI), callback.capture())
185         }
186
187         private fun read(resource: String): ByteArray =
188                         javaClass.getResourceAsStream(resource)?.use { input ->
189                                 ByteArrayOutputStream().use {
190                                         ByteStreams.copy(input, it)
191                                         it
192                                 }.toByteArray()
193                         } ?: ByteArray(0)
194
195 }