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("
" + "The actual article body. ".repeat(60) + "
") 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 = "" + "Real article sentence. ".repeat(40) + "
" val widget = "