1 package net.pterodactylus.sone.core
3 import com.google.common.base.Ticker
4 import freenet.keys.FreenetURI
5 import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback
6 import net.pterodactylus.sone.test.*
7 import org.hamcrest.MatcherAssert.assertThat
8 import org.hamcrest.Matchers.contains
9 import org.hamcrest.Matchers.equalTo
12 import java.io.ByteArrayOutputStream
13 import java.util.concurrent.TimeUnit.MINUTES
14 import java.util.concurrent.atomic.AtomicReference
15 import kotlin.math.min
18 * Unit test for [DefaultElementLoaderTest].
20 class DefaultElementLoaderTest {
23 fun `image loader starts request for link that is not known`() {
24 runWithCallback(IMAGE_ID) { _, _, _, fetchedUris ->
25 assertThat(fetchedUris, contains(freenetURI))
30 fun `element loader only starts request once`() {
31 runWithCallback(IMAGE_ID) { elementLoader, _, _, fetchedUris ->
32 elementLoader.loadElement(IMAGE_ID)
33 assertThat(fetchedUris, contains(freenetURI))
38 fun `element loader returns loading element on first call`() {
39 runWithCallback(IMAGE_ID) { _, linkedElement, _, _ ->
40 assertThat(linkedElement.loading, equalTo(true))
45 fun `element loader does not cancel on image mime type with 2 mib size`() {
46 runWithCallback(IMAGE_ID) { _, _, callback, _ ->
47 assertThat(callback.shouldCancel(freenetURI, "image/png", sizeOkay), equalTo(false))
52 fun `element loader does cancel on image mime type with more than 2 mib size`() {
53 runWithCallback(IMAGE_ID) { _, _, callback, _ ->
54 assertThat(callback.shouldCancel(freenetURI, "image/png", sizeNotOkay), equalTo(true))
59 fun `element loader does cancel on audio mime type`() {
60 runWithCallback(IMAGE_ID) { _, _, callback, _ ->
61 assertThat(callback.shouldCancel(freenetURI, "audio/mpeg", sizeOkay), equalTo(true))
66 fun `element loader does cancel on video mime type`() {
67 runWithCallback(IMAGE_ID) { _, _, callback, _ ->
68 assertThat(callback.shouldCancel(freenetURI, "video/mkv", sizeOkay), equalTo(true))
73 fun `element loader does cancel on text mime type`() {
74 runWithCallback(IMAGE_ID) { _, _, callback, _ ->
75 assertThat(callback.shouldCancel(freenetURI, "text/plain", sizeOkay), equalTo(true))
80 fun `element loader does not cancel on text html mime type`() {
81 runWithCallback(IMAGE_ID) { _, _, callback, _ ->
82 assertThat(callback.shouldCancel(freenetURI, "text/html", sizeOkay), equalTo(false))
87 fun `image loader can load image`() {
88 runWithCallback(decomposedKey) { elementLoader, _, callback, _ ->
89 callback.loaded(FreenetURI(normalizedKey), "image/png", read("/static/images/unknown-image-0.png"))
90 val linkedElement = elementLoader.loadElement(decomposedKey)
94 "sizeHuman" to "2 KiB"
95 ).also { assertThat(linkedElement, equalTo(LinkedElement(normalizedKey, properties = it))) }
100 fun `element loader can extract description from description header`() {
101 runWithCallback(textKey) { elementLoader, _, callback, _ ->
102 callback.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader.html"))
103 val linkedElement = elementLoader.loadElement(textKey)
107 "sizeHuman" to "266 B",
108 "title" to "Some Nice Page Title",
109 "description" to "This is an example of a very nice freesite."
110 ).also { assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = it))) }
115 fun `element loader can extract description from first non-heading paragraph`() {
116 runWithCallback(textKey) { elementLoader, _, callback, _ ->
117 callback.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader2.html"))
118 val linkedElement = elementLoader.loadElement(textKey)
122 "sizeHuman" to "185 B",
123 "title" to "Some Nice Page Title",
124 "description" to "This is the first paragraph of the very nice freesite."
125 ).also { assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = it))) }
130 fun `element loader can not extract description if html is more complicated`() {
131 runWithCallback(textKey) { elementLoader, _, callback, _ ->
132 callback.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader3.html"))
133 val linkedElement = elementLoader.loadElement(textKey)
137 "sizeHuman" to "204 B",
138 "title" to "Some Nice Page Title",
139 "description" to null
140 ).also { assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = it))) }
145 fun `element loader can not extract title if it is missing`() {
146 runWithCallback(textKey) { elementLoader, _, callback, _ ->
147 callback.loaded(FreenetURI(textKey), "text/html; charset=UTF-8", read("element-loader4.html"))
148 val linkedElement = elementLoader.loadElement(textKey)
152 "sizeHuman" to "229 B",
154 "description" to "This is an example of a very nice freesite."
155 ).also { assertThat(linkedElement, equalTo(LinkedElement(textKey, properties = it))) }
160 fun `image is not loaded again after it failed`() {
161 runWithCallback(IMAGE_ID) { elementLoader, _, callback, _ ->
162 elementLoader.loadElement(IMAGE_ID)
163 callback.failed(freenetURI)
164 assertThat(elementLoader.loadElement(IMAGE_ID).failed, equalTo(true))
169 fun `image is loaded again after failure cache is expired`() {
170 runWithCallback(IMAGE_ID, createTicker(1, MINUTES.toNanos(31))) { elementLoader, _, callback, _ ->
171 elementLoader.loadElement(IMAGE_ID)
172 callback.failed(freenetURI)
173 val linkedElement = elementLoader.loadElement(IMAGE_ID)
174 assertThat(linkedElement.failed, equalTo(false))
175 assertThat(linkedElement.loading, equalTo(true))
179 private fun read(resource: String): ByteArray =
180 javaClass.getResourceAsStream(resource)?.use { input ->
181 ByteArrayOutputStream().use {
188 val silencedLoggin = silencedLogging()
192 private fun runWithCallback(requestUri: String, ticker: Ticker = createTicker(), callbackAction: (elementLoader: ElementLoader, linkedElement: LinkedElement, callback: BackgroundFetchCallback, fetchedUris: List<FreenetURI>) -> Unit) {
193 val fetchedUris = mutableListOf<FreenetURI>()
194 val callback = AtomicReference<BackgroundFetchCallback>()
195 val freenetInterface = overrideStartFetch { uri, backgroundFetchCallback ->
197 callback.set(backgroundFetchCallback)
199 val elementLoader = DefaultElementLoader(freenetInterface, ticker)
200 val linkedElement = elementLoader.loadElement(requestUri)
201 callbackAction(elementLoader, linkedElement, callback.get(), fetchedUris)
204 private fun overrideStartFetch(action: (FreenetURI, BackgroundFetchCallback) -> Unit) = object : FreenetInterface(null, null, null, null, null, dummyHighLevelSimpleClientCreator) {
205 override fun startFetch(uri: FreenetURI, backgroundFetchCallback: BackgroundFetchCallback) {
206 action(uri, backgroundFetchCallback)
210 private fun createTicker(vararg times: Long = LongArray(1) { 1 }) = object : Ticker() {
211 private var counter = 0
212 override fun read() =
213 times[min(times.size - 1, counter)]
217 private const val IMAGE_ID = "KSK@gpl.png"
218 private val freenetURI = FreenetURI(IMAGE_ID)
219 private const val decomposedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/fru%CC%88hstu%CC%88ck.jpg"
220 private const val normalizedKey = "CHK@DCiVgTWW9nnWHJc9EVwtFJ6jAfBSVyy~rgiPvhUKbS4,mNY85V0x7dYcv7SnEYo1PCC6y2wNWMDNt-y9UWQx9fI,AAMC--8/frühstück.jpg"
221 private const val textKey = "KSK@gpl.html"
222 private const val sizeOkay = 2097152L
223 private const val sizeNotOkay = sizeOkay + 1