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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
package org.terst.feedreader.readability
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
/**
* The decision ReaderExtractor makes about each `evaluateJavascript` result:
* accept it, inject again because the page probably was not ready, or give up
* with a reason. It used to live inline in a WebView callback, which meant the
* only way to exercise it was on a device against a live page.
*
* The case that mattered and was never covered: a result that is perfectly
* well-formed and non-blank, and is the page's cookie-consent banner rather than
* the article. That counted as a success, got cached, and stayed cached.
*/
class EvalOutcomeTest {
private val maxAttempts = 6
private fun decide(article: ExtractedArticle?, attempt: Int, raw: String = "\"{}\"") =
decideEvalOutcome(article, raw, attempt, maxAttempts)
private fun article(content: String?, error: String? = null) =
ExtractedArticle(title = "t", content = content, textContent = content, error = error)
@Test
fun `real content is accepted on the first attempt`() {
val a = article("<p>" + "The actual article body. ".repeat(60) + "</p>")
val decision = decide(a, attempt = 1)
assertEquals(EvalDecision.Done(ExtractionResult.Success(a)), decision)
}
// Retrying cannot change Readability's opinion of the markup, so a thrown
// error is final however early it arrives.
@Test
fun `a readability error is final immediately`() {
val decision = decide(article(null, error = "TypeError: x is null"), attempt = 1)
assertEquals(
EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.ReadabilityError("TypeError: x is null"))),
decision
)
}
@Test
fun `an undecodable result retries and then reports a bad result`() {
assertEquals(EvalDecision.Retry, decide(null, attempt = 1, raw = "null"))
val last = decide(null, attempt = maxAttempts, raw = "null")
assertEquals(EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.BadResult("null"))), last)
}
@Test
fun `blank content retries and then reports no content`() {
assertEquals(EvalDecision.Retry, decide(article(""), attempt = 1))
assertEquals(
EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.NoContent)),
decide(article(""), attempt = maxAttempts)
)
}
// A cookie banner is exactly what a half-loaded page yields -- the banner is
// injected early, the article body may not be in the DOM yet -- so the right
// first move is the retry the extractor already does for an empty result.
@Test
fun `a consent notice retries rather than being accepted`() {
val decision = decide(article(ConsentNoticeTest.EXTRACTED_COOKIE_NOTICE), attempt = 1)
assertEquals(EvalDecision.Retry, decision)
}
@Test
fun `a consent notice that survives every attempt is reported as a failure`() {
val decision = decide(article(ConsentNoticeTest.EXTRACTED_COOKIE_NOTICE), attempt = maxAttempts)
assertEquals(
EvalDecision.Done(ExtractionResult.Failure(ExtractionFailure.ConsentInterstitial)),
decision
)
}
// The failure has to be distinguishable from "no content": one means the page
// has no article, the other means the page would not show it without consent.
@Test
fun `the consent failure says what happened`() {
val f: ExtractionFailure = ExtractionFailure.ConsentInterstitial
assertTrue(f.userMessage().isNotBlank())
assertTrue(f.diagnostic().isNotBlank())
assertTrue(f.userMessage().lowercase().contains("cookie") || f.userMessage().lowercase().contains("consent"))
assertTrue(f.userMessage() != ExtractionFailure.NoContent.userMessage())
}
/**
* A fresh extraction is cleaned before it is accepted, so the widgets never reach the
* screen and never reach `articles.extractedContent` either.
*/
@Test
fun `a successful extraction has its recirculation widgets stripped`() {
val body = "<p>" + "Real article sentence. ".repeat(40) + "</p>"
val widget = "<div data-mrf-recirculation=\"IL_x\"><p><h3><span>Trending</span></h3></p></div>"
val article = ExtractedArticle(title = "T", content = body + widget + body)
val decision = decideEvalOutcome(article, rawResult = "{}", attempt = 1, maxAttempts = 6)
val content = ((decision as EvalDecision.Done).result as ExtractionResult.Success).article.content!!
assertFalse("the stray header reached the caller: $content", content.contains("Trending"))
assertTrue(content.contains("Real article sentence."))
}
}
|