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