- 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
246 lines
8.3 KiB
Kotlin
246 lines
8.3 KiB
Kotlin
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)
|
|
}
|
|
}
|