Implement Android RSS/Atom feed parser
- Add FeedParser.kt with automatic feed type detection - Add RSSParser.kt for RSS 2.0 feeds - Add AtomParser.kt for Atom 1.0 feeds - Add comprehensive unit tests for both parsers - Support iTunes namespace and enclosures - Fix pre-existing compilation issues in the codebase - Update build.gradle.kts with proper dependencies and AGP 8.5.0
This commit is contained in:
@@ -46,9 +46,9 @@ class RssDatabaseTest {
|
||||
@Test
|
||||
fun ftsVirtualTableExists() {
|
||||
val cursor = database.run {
|
||||
openHelper.writableDatabase.rawQuery(
|
||||
openHelper.writableDatabase.query(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='feed_items_fts'",
|
||||
null
|
||||
emptyArray()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ class SearchResultTest {
|
||||
assertEquals("article-1", modified.id)
|
||||
assertEquals(SearchResultType.ARTICLE, modified.type)
|
||||
assertEquals("Modified Title", modified.title)
|
||||
assertEquals(0.99, modified.score, 0.001)
|
||||
assertEquals(0.99, modified.score!!, 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.rssuper.parsing
|
||||
|
||||
import com.rssuper.models.Enclosure
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [24])
|
||||
class AtomParserTest {
|
||||
|
||||
@Test
|
||||
fun testParseBasicAtom() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Atom Feed</title>
|
||||
<subtitle>Feed subtitle</subtitle>
|
||||
<link href="https://example.com" rel="alternate"/>
|
||||
<id>urn:uuid:feed-id-123</id>
|
||||
<updated>2024-01-01T12:00:00Z</updated>
|
||||
<generator>Atom Generator</generator>
|
||||
<entry>
|
||||
<title>Entry 1</title>
|
||||
<link href="https://example.com/entry1" rel="alternate"/>
|
||||
<id>urn:uuid:entry-1</id>
|
||||
<updated>2024-01-01T10:00:00Z</updated>
|
||||
<summary>Summary of entry 1</summary>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Entry 2</title>
|
||||
<link href="https://example.com/entry2" rel="alternate"/>
|
||||
<id>urn:uuid:entry-2</id>
|
||||
<updated>2023-12-31T10:00:00Z</updated>
|
||||
<content>Full content of entry 2</content>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Atom Feed", feed.title)
|
||||
assertEquals("https://example.com", feed.link)
|
||||
assertEquals("Feed subtitle", feed.subtitle)
|
||||
assertEquals(2, feed.items.size)
|
||||
|
||||
val entry1 = feed.items[0]
|
||||
assertEquals("Entry 1", entry1.title)
|
||||
assertEquals("https://example.com/entry1", entry1.link)
|
||||
assertEquals("Summary of entry 1", entry1.description)
|
||||
assertNotNull(entry1.published)
|
||||
|
||||
val entry2 = feed.items[1]
|
||||
assertEquals("Entry 2", entry2.title)
|
||||
assertEquals("Full content of entry 2", entry2.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithAuthor() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Author Feed</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
<entry>
|
||||
<title>Entry with Author</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
<email>john@example.com</email>
|
||||
</author>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
val entry = feed.items[0]
|
||||
assertEquals("John Doe", entry.author)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithCategories() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Categorized Feed</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
<entry>
|
||||
<title>Categorized Entry</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
<category term="technology"/>
|
||||
<category term="programming"/>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
val entry = feed.items[0]
|
||||
assertEquals(2, entry.categories?.size)
|
||||
assertEquals("technology", entry.categories?.get(0))
|
||||
assertEquals("programming", entry.categories?.get(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithEnclosure() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Enclosure Feed</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
<entry>
|
||||
<title>Episode</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
<link href="https://example.com/ep.mp3" rel="enclosure" type="audio/mpeg" length="12345678"/>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
val entry = feed.items[0]
|
||||
assertNotNull(entry.enclosure)
|
||||
assertEquals("https://example.com/ep.mp3", entry.enclosure?.url)
|
||||
assertEquals("audio/mpeg", entry.enclosure?.type)
|
||||
assertEquals(12345678L, entry.enclosure?.length)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithContent() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Content Feed</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
<entry>
|
||||
<title>Entry</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
<summary>Short summary</summary>
|
||||
<content>Full HTML content</content>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
val entry = feed.items[0]
|
||||
assertEquals("Full HTML content", entry.content)
|
||||
assertEquals("Short summary", entry.description)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithiTunesExtension() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<title>Podcast</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
<entry>
|
||||
<title>Episode</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
<itunes:duration>3600</itunes:duration>
|
||||
<itunes:summary>Episode summary</itunes:summary>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
val entry = feed.items[0]
|
||||
assertEquals("Episode summary", entry.description)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithPublished() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Date Feed</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
<updated>2024-06-15T12:00:00Z</updated>
|
||||
<entry>
|
||||
<title>Entry</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
<published>2024-01-01T08:00:00Z</published>
|
||||
<updated>2024-01-02T10:00:00Z</updated>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
val entry = feed.items[0]
|
||||
assertNotNull(entry.published)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithEmptyFeed() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Empty Feed</title>
|
||||
<id>urn:uuid:feed-id</id>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Empty Feed", feed.title)
|
||||
assertEquals(0, feed.items.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomWithMissingFields() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<title>Minimal Entry</title>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = AtomParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Untitled Feed", feed.title)
|
||||
assertEquals(1, feed.items.size)
|
||||
assertEquals("Minimal Entry", feed.items[0].title)
|
||||
assertNull(feed.items[0].link)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.rssuper.parsing
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [24])
|
||||
class FeedParserTest {
|
||||
|
||||
@Test
|
||||
fun testParseRSSFeed() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>RSS Feed</title>
|
||||
<link>https://example.com</link>
|
||||
<item>
|
||||
<title>Item</title>
|
||||
<link>https://example.com/item</link>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val result = FeedParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals(FeedType.RSS, result.feedType)
|
||||
assertEquals("RSS Feed", result.feed.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseAtomFeed() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Atom Feed</title>
|
||||
<id>urn:uuid:feed</id>
|
||||
<entry>
|
||||
<title>Entry</title>
|
||||
<id>urn:uuid:entry</id>
|
||||
</entry>
|
||||
</feed>
|
||||
""".trimIndent()
|
||||
|
||||
val result = FeedParser.parse(xml, "https://example.com/feed.atom")
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals(FeedType.Atom, result.feedType)
|
||||
assertEquals("Atom Feed", result.feed.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithNamespaces() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<channel>
|
||||
<title>Namespaced Feed</title>
|
||||
<atom:link href="https://example.com/feed.xml" rel="self"/>
|
||||
<itunes:author>Author</itunes:author>
|
||||
<item>
|
||||
<title>Item</title>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val result = FeedParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals(FeedType.RSS, result.feedType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseMalformedXml() {
|
||||
val malformedXml = """
|
||||
<?xml version="1.0"?>
|
||||
<rss>
|
||||
<channel>
|
||||
<title>Broken
|
||||
""".trimIndent()
|
||||
|
||||
try {
|
||||
val result = FeedParser.parse(malformedXml, "https://example.com/feed.xml")
|
||||
assertNotNull(result)
|
||||
} catch (e: Exception) {
|
||||
assertNotNull(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseInvalidFeedType() {
|
||||
val invalidXml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<invalid>
|
||||
<data>Some data</data>
|
||||
</invalid>
|
||||
""".trimIndent()
|
||||
|
||||
try {
|
||||
FeedParser.parse(invalidXml, "https://example.com/feed.xml")
|
||||
fail("Expected exception for invalid feed type")
|
||||
} catch (e: FeedParsingError) {
|
||||
assertEquals(FeedParsingError.UnsupportedFeedType, e)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseEmptyFeed() {
|
||||
val emptyXml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title></title>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val result = FeedParser.parse(emptyXml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("Untitled Feed", result.feed.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsyncCallback() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Async Feed</title>
|
||||
<item>
|
||||
<title>Item</title>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
FeedParser.parseAsync(xml, "https://example.com/feed.xml") { result ->
|
||||
assert(result.isSuccess)
|
||||
val feed = result.getOrNull()
|
||||
assertNotNull(feed)
|
||||
assertEquals("Async Feed", feed?.feed?.title)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsyncCallbackError() {
|
||||
val invalidXml = "not xml"
|
||||
|
||||
FeedParser.parseAsync(invalidXml, "https://example.com/feed.xml") { result ->
|
||||
assert(result.isFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.rssuper.parsing
|
||||
|
||||
import com.rssuper.models.Enclosure
|
||||
import com.rssuper.models.Feed
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [24])
|
||||
class RSSParserTest {
|
||||
|
||||
@Test
|
||||
fun testParseBasicRSS() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Test Feed</title>
|
||||
<link>https://example.com</link>
|
||||
<description>A test feed</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>Mon, 01 Jan 2024 12:00:00 GMT</lastBuildDate>
|
||||
<generator>RSS Generator</generator>
|
||||
<ttl>60</ttl>
|
||||
<item>
|
||||
<title>Item 1</title>
|
||||
<link>https://example.com/item1</link>
|
||||
<description>Description of item 1</description>
|
||||
<guid isPermaLink="true">https://example.com/item1</guid>
|
||||
<pubDate>Mon, 01 Jan 2024 10:00:00 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title>Item 2</title>
|
||||
<link>https://example.com/item2</link>
|
||||
<description>Description of item 2</description>
|
||||
<guid>item-2-guid</guid>
|
||||
<pubDate>Sun, 31 Dec 2023 10:00:00 GMT</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Test Feed", feed.title)
|
||||
assertEquals("https://example.com", feed.link)
|
||||
assertEquals("A test feed", feed.description)
|
||||
assertEquals("en-us", feed.language)
|
||||
assertEquals(60, feed.ttl)
|
||||
assertEquals(2, feed.items.size)
|
||||
|
||||
val item1 = feed.items[0]
|
||||
assertEquals("Item 1", item1.title)
|
||||
assertEquals("https://example.com/item1", item1.link)
|
||||
assertEquals("Description of item 1", item1.description)
|
||||
assertNotNull(item1.published)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithiTunesNamespace() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<channel>
|
||||
<title>Podcast Feed</title>
|
||||
<link>https://example.com/podcast</link>
|
||||
<description>My podcast</description>
|
||||
<itunes:subtitle>Podcast subtitle</itunes:subtitle>
|
||||
<itunes:author>Author Name</itunes:author>
|
||||
<item>
|
||||
<title>Episode 1</title>
|
||||
<link>https://example.com/episode1</link>
|
||||
<description>Episode description</description>
|
||||
<itunes:duration>01:30:00</itunes:duration>
|
||||
<enclosure url="https://example.com/ep1.mp3" type="audio/mpeg" length="12345678"/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Podcast Feed", feed.title)
|
||||
|
||||
val item = feed.items[0]
|
||||
assertEquals("Episode 1", item.title)
|
||||
assertNotNull(item.enclosure)
|
||||
assertEquals("https://example.com/ep1.mp3", item.enclosure?.url)
|
||||
assertEquals("audio/mpeg", item.enclosure?.type)
|
||||
assertEquals(12345678L, item.enclosure?.length)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithContentNamespace() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
||||
<channel>
|
||||
<title>Feed with Content</title>
|
||||
<item>
|
||||
<title>Item with Content</title>
|
||||
<description>Short description</description>
|
||||
<content:encoded><![CDATA[<p>Full content here</p>]]></content:encoded>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals(1, feed.items.size)
|
||||
assertEquals("Item with Content", feed.items[0].title)
|
||||
assertEquals("<p>Full content here</p>", feed.items[0].content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithCategories() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Categorized Feed</title>
|
||||
<item>
|
||||
<title>Tech Article</title>
|
||||
<category>Technology</category>
|
||||
<category>Programming</category>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
val item = feed.items[0]
|
||||
assertEquals(2, item.categories?.size)
|
||||
assertEquals("Technology", item.categories?.get(0))
|
||||
assertEquals("Programming", item.categories?.get(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithAuthor() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Author Feed</title>
|
||||
<item>
|
||||
<title>Article by Author</title>
|
||||
<author>author@example.com (John Doe)</author>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
val item = feed.items[0]
|
||||
assertEquals("author@example.com (John Doe)", item.author)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithGuid() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Guid Feed</title>
|
||||
<item>
|
||||
<title>Item</title>
|
||||
<guid>custom-guid-12345</guid>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("custom-guid-12345", feed.items[0].guid)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithEmptyChannel() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Minimal Feed</title>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Minimal Feed", feed.title)
|
||||
assertEquals(0, feed.items.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithMissingFields() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<item>
|
||||
<title>Only Title</title>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("Untitled Feed", feed.title)
|
||||
assertEquals(1, feed.items.size)
|
||||
assertEquals("Only Title", feed.items[0].title)
|
||||
assertNull(feed.items[0].link)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRSSWithCDATA() {
|
||||
val xml = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title><![CDATA[CDATA Title]]></title>
|
||||
<description><![CDATA[<p>HTML <strong>content</strong></p>]]></description>
|
||||
<item>
|
||||
<title>CDATA Item</title>
|
||||
<description><![CDATA[Item content]]></description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
""".trimIndent()
|
||||
|
||||
val feed = RSSParser.parse(xml, "https://example.com/feed.xml")
|
||||
|
||||
assertNotNull(feed)
|
||||
assertEquals("CDATA Title", feed.title)
|
||||
assertEquals("<p>HTML <strong>content</strong></p>", feed.description)
|
||||
assertEquals("Item content", feed.items[0].description)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user