summaryrefslogtreecommitdiff
path: root/app/src/test/java/org/terst/feedreader/data
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/test/java/org/terst/feedreader/data')
-rw-r--r--app/src/test/java/org/terst/feedreader/data/CachedExtractionTest.kt19
-rw-r--r--app/src/test/java/org/terst/feedreader/data/FeedDiscoveryTest.kt93
2 files changed, 112 insertions, 0 deletions
diff --git a/app/src/test/java/org/terst/feedreader/data/CachedExtractionTest.kt b/app/src/test/java/org/terst/feedreader/data/CachedExtractionTest.kt
index fc9c32e..442663f 100644
--- a/app/src/test/java/org/terst/feedreader/data/CachedExtractionTest.kt
+++ b/app/src/test/java/org/terst/feedreader/data/CachedExtractionTest.kt
@@ -96,4 +96,23 @@ class CachedExtractionTest {
assertFalse(result.discardedConsentNotice)
assertFalse(cleared)
}
+
+ /**
+ * A cached article carrying the publisher's "Trending" promo widgets. Unlike a cookie
+ * notice, the article itself is fine -- only the widgets have to go, and re-extracting
+ * would be a pointless round trip that produced the same body again.
+ */
+ @Test
+ fun `a cached article has its recirculation widgets stripped without being re-extracted`() = runTest {
+ var cleared = false
+ val body = "<p>" + "The actual article body. ".repeat(60) + "</p>"
+ val widget = "<div data-mrf-recirculation=\"IL_x\"><p><h3><span>Trending</span></h3></p></div>"
+
+ val result = loadCachedExtraction(body + widget + body) { cleared = true }
+
+ assertFalse("the stray header survived: ${result.content}", result.content!!.contains("Trending"))
+ assertTrue(result.content!!.contains("The actual article body."))
+ assertFalse("the article was fine; nothing should be thrown away", result.discarded)
+ assertFalse("re-extracting would fetch the same body again", cleared)
+ }
}
diff --git a/app/src/test/java/org/terst/feedreader/data/FeedDiscoveryTest.kt b/app/src/test/java/org/terst/feedreader/data/FeedDiscoveryTest.kt
new file mode 100644
index 0000000..eee238a
--- /dev/null
+++ b/app/src/test/java/org/terst/feedreader/data/FeedDiscoveryTest.kt
@@ -0,0 +1,93 @@
+package org.terst.feedreader.data
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+/**
+ * Finding the feed when what you were given is a site.
+ *
+ * Reported live: "Can't subscribe to https://heathercoxrichardson.substack.com/". Pasting a
+ * site's home page was the only thing to hand -- a site does not advertise its feed URL
+ * anywhere a reader can see, it advertises it in a <link rel="alternate"> that every other
+ * reader looks for and this one did not.
+ */
+class FeedDiscoveryTest {
+
+ /** Verbatim from the reported page, 2026-10-21. */
+ private val substackHead =
+ """<!doctype html><html><head><title>Letters from an American</title>""" +
+ """<link rel="alternate" type="application/rss+xml" href="/feed" """ +
+ """title="Letters from an American"/></head><body>...</body></html>"""
+
+ @Test
+ fun `finds the feed a Substack home page advertises, resolved against the site`() {
+ assertEquals(
+ "https://heathercoxrichardson.substack.com/feed",
+ FeedDiscovery.findFeedUrl(substackHead, "https://heathercoxrichardson.substack.com/")
+ )
+ }
+
+ @Test
+ fun `finds an Atom feed too`() {
+ val html = """<link rel="alternate" type="application/atom+xml" href="/atom.xml">"""
+
+ assertEquals("https://example.com/atom.xml", FeedDiscovery.findFeedUrl(html, "https://example.com/"))
+ }
+
+ @Test
+ fun `keeps an absolute href as it stands`() {
+ val html = """<link rel="alternate" type="application/rss+xml" href="https://feeds.example.net/x.xml">"""
+
+ assertEquals("https://feeds.example.net/x.xml", FeedDiscovery.findFeedUrl(html, "https://example.com/blog/"))
+ }
+
+ @Test
+ fun `resolves a relative href against the page, not the site root`() {
+ val html = """<link rel="alternate" type="application/rss+xml" href="feed.xml">"""
+
+ assertEquals("https://example.com/blog/feed.xml", FeedDiscovery.findFeedUrl(html, "https://example.com/blog/"))
+ }
+
+ @Test
+ fun `copes with the attributes in any order and single quotes`() {
+ val html = """<link type='application/rss+xml' href='/rss' rel='alternate'>"""
+
+ assertEquals("https://example.com/rss", FeedDiscovery.findFeedUrl(html, "https://example.com"))
+ }
+
+ @Test
+ fun `prefers the first feed when a page advertises several`() {
+ val html =
+ """<link rel="alternate" type="application/rss+xml" href="/feed" title="Posts">""" +
+ """<link rel="alternate" type="application/rss+xml" href="/comments/feed" title="Comments">"""
+
+ assertEquals("https://example.com/feed", FeedDiscovery.findFeedUrl(html, "https://example.com/"))
+ }
+
+ @Test
+ fun `ignores an alternate link that is not a feed`() {
+ val html =
+ """<link rel="alternate" hreflang="fr" href="/fr/">""" +
+ """<link rel="alternate" type="application/json" href="/feed.json">"""
+
+ assertNull(FeedDiscovery.findFeedUrl(html, "https://example.com/"))
+ }
+
+ @Test
+ fun `a page with no feed at all reports nothing rather than guessing a path`() {
+ assertNull(FeedDiscovery.findFeedUrl("<html><head></head><body>hi</body></html>", "https://example.com/"))
+ }
+
+ @Test
+ fun `an http feed link is upgraded, since the app cannot fetch cleartext`() {
+ val html = """<link rel="alternate" type="application/rss+xml" href="http://example.com/feed">"""
+
+ assertEquals("https://example.com/feed", FeedDiscovery.findFeedUrl(html, "https://example.com/"))
+ }
+
+ @Test
+ fun `xml declaration means this was already a feed, so there is nothing to discover`() {
+ assertNull(FeedDiscovery.findFeedUrl("<?xml version=\"1.0\"?><rss><channel></channel></rss>", "https://x/f"))
+ }
+}