🔀 Merge “release/v81” into “master”
[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.junit.Test
11 import org.mockito.ArgumentMatchers.any
12 import org.mockito.ArgumentMatchers.eq
13 import org.mockito.Mockito.times
14 import org.mockito.Mockito.verify
15 import java.io.ByteArrayOutputStream
16 import java.util.concurrent.TimeUnit
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 `element loader can extract description from description header`() {
101                 elementLoader.loadElement(textKey)
102                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
103                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader.html"))
104                 val linkedElement = elementLoader.loadElement(textKey)
105                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
106                                 "type" to "html",
107                                 "size" to 266,
108                                 "sizeHuman" to "266 B",
109                                 "title" to "Some Nice Page Title",
110                                 "description" to "This is an example of a very nice freesite."
111                 ))))
112         }
113
114         @Test
115         fun `element loader can extract description from first non-heading paragraph`() {
116                 elementLoader.loadElement(textKey)
117                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
118                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader2.html"))
119                 val linkedElement = elementLoader.loadElement(textKey)
120                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
121                                 "type" to "html",
122                                 "size" to 185,
123                                 "sizeHuman" to "185 B",
124                                 "title" to "Some Nice Page Title",
125                                 "description" to "This is the first paragraph of the very nice freesite."
126                 ))))
127         }
128
129         @Test
130         fun `element loader can not extract description if html is more complicated`() {
131                 elementLoader.loadElement(textKey)
132                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
133                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader3.html"))
134                 val linkedElement = elementLoader.loadElement(textKey)
135                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
136                                 "type" to "html",
137                                 "size" to 204,
138                                 "sizeHuman" to "204 B",
139                                 "title" to "Some Nice Page Title",
140                                 "description" to null
141                 ))))
142         }
143
144         @Test
145         fun `element loader can not extract title if it is missing`() {
146                 elementLoader.loadElement(textKey)
147                 verify(freenetInterface).startFetch(eq(FreenetURI(textKey)), callback.capture())
148                 callback.value.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader4.html"))
149                 val linkedElement = elementLoader.loadElement(textKey)
150                 assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = mapOf(
151                                 "type" to "html",
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, equalTo(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                 whenever(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31))
174                 val linkedElement = elementLoader.loadElement(IMAGE_ID)
175                 assertThat(linkedElement.failed, equalTo(false))
176                 assertThat(linkedElement.loading, equalTo(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 }
189
190 private const val IMAGE_ID = "KSK@gpl.png"
191 private val freenetURI = FreenetURI(IMAGE_ID)
192 private const val decomposedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/fru%CC%88hstu%CC%88ck.jpg"
193 private const val normalizedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/frĂĽhstĂĽck.jpg"
194 private const val textKey = "KSK@gpl.html"
195 private const val sizeOkay = 2097152L
196 private const val sizeNotOkay = sizeOkay + 1