summaryrefslogtreecommitdiff
path: root/app/src/test/java/org/terst/feedreader/readability/ExtractionFailureTest.kt
blob: 6f691fdb283f1c03db339a8646abe75a7b539568 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package org.terst.feedreader.readability

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * Extraction failures used to be discarded entirely — ReaderScreen did
 * `extracted?.content?.let { }`, so any failure left the reader silently showing
 * the raw feed content. The bug report that prompted this could say only
 * "extraction failed", because the app itself never knew why.
 *
 * These pin the two things a failure has to produce: something short enough to
 * put in front of the user, and something specific enough to debug from.
 */
class ExtractionFailureTest {

    @Test
    fun `load error names the underlying description`() {
        val f = ExtractionFailure.LoadError(-2, "net::ERR_NAME_NOT_RESOLVED")
        assertEquals("Couldn't load the page (net::ERR_NAME_NOT_RESOLVED)", f.userMessage())
        assertTrue(f.diagnostic().contains("code=-2"))
        assertTrue(f.diagnostic().contains("ERR_NAME_NOT_RESOLVED"))
    }

    @Test
    fun `load error without a description still reads properly`() {
        assertEquals("Couldn't load the page", ExtractionFailure.LoadError(-1, null).userMessage())
        assertEquals("Couldn't load the page", ExtractionFailure.LoadError(-1, "  ").userMessage())
    }

    @Test
    fun `http error names the status`() {
        val f = ExtractionFailure.HttpError(403, "Forbidden")
        assertEquals("Server returned HTTP 403", f.userMessage())
        assertTrue(f.diagnostic().contains("status=403"))
    }

    @Test
    fun `timeout is reported in seconds`() {
        assertEquals("Page took longer than 25s to load", ExtractionFailure.TimedOut(25_000).userMessage())
        assertTrue(ExtractionFailure.TimedOut(25_000).diagnostic().contains("25000ms"))
    }

    @Test
    fun `readability error carries the thrown message`() {
        val f = ExtractionFailure.ReadabilityError("TypeError: x is null")
        assertTrue(f.userMessage().contains("TypeError"))
        assertTrue(f.diagnostic().contains("TypeError"))
    }

    @Test
    fun `no content is distinct from an error`() {
        assertEquals("No article text found on the page", ExtractionFailure.NoContent.userMessage())
    }

    // The raw JS result can be enormous; a diagnostic that pastes all of it is
    // unusable in a bug report.
    @Test
    fun `bad result truncates the raw payload`() {
        val f = ExtractionFailure.BadResult("x".repeat(5_000))
        assertTrue("diagnostic should be bounded", f.diagnostic().length < 400)
    }

    // Every case must produce a non-empty message; a blank one would put the
    // user back where they started.
    @Test
    fun `every failure produces both messages`() {
        val all = listOf(
            ExtractionFailure.LoadError(-6, "ERR_CONNECTION_REFUSED"),
            ExtractionFailure.HttpError(500, null),
            ExtractionFailure.TimedOut(25_000),
            ExtractionFailure.ReadabilityError("boom"),
            ExtractionFailure.NoContent,
            ExtractionFailure.BadResult("{}"),
        )
        for (f in all) {
            assertTrue("$f has a blank user message", f.userMessage().isNotBlank())
            assertTrue("$f has a blank diagnostic", f.diagnostic().isNotBlank())
        }
    }
}