restructure
Some checks failed
CI - Multi-Platform Native / Build iOS (RSSuper) (push) Has been cancelled
CI - Multi-Platform Native / Build macOS (push) Has been cancelled
CI - Multi-Platform Native / Build Android (push) Has been cancelled
CI - Multi-Platform Native / Build Linux (push) Has been cancelled
CI - Multi-Platform Native / Build Summary (push) Has been cancelled
Some checks failed
CI - Multi-Platform Native / Build iOS (RSSuper) (push) Has been cancelled
CI - Multi-Platform Native / Build macOS (push) Has been cancelled
CI - Multi-Platform Native / Build Android (push) Has been cancelled
CI - Multi-Platform Native / Build Linux (push) Has been cancelled
CI - Multi-Platform Native / Build Summary (push) Has been cancelled
This commit is contained in:
211
iOS/RSSuperTests/FeedParserTests.swift
Normal file
211
iOS/RSSuperTests/FeedParserTests.swift
Normal file
@@ -0,0 +1,211 @@
|
||||
import XCTest
|
||||
@testable import RSSuper
|
||||
|
||||
final class FeedParserTests: XCTestCase {
|
||||
func testParsesRSS20Feed() throws {
|
||||
let parser = FeedParser()
|
||||
let result = try parser.parse(
|
||||
data: Data(rssSample.utf8),
|
||||
sourceURL: "https://example.com/rss.xml"
|
||||
)
|
||||
|
||||
XCTAssertEqual(result.feedType, .rss)
|
||||
XCTAssertEqual(result.feed.title, "Example Podcast")
|
||||
XCTAssertEqual(result.feed.subtitle, "Weekly iOS and Swift updates")
|
||||
XCTAssertEqual(result.feed.items.count, 2)
|
||||
XCTAssertEqual(result.feed.ttl, 60)
|
||||
|
||||
let firstItem = try XCTUnwrap(result.feed.items.first)
|
||||
XCTAssertEqual(firstItem.title, "Episode 1")
|
||||
XCTAssertEqual(firstItem.author, "Host Name")
|
||||
XCTAssertEqual(firstItem.guid, "episode-1")
|
||||
XCTAssertEqual(firstItem.categories, ["Swift"])
|
||||
XCTAssertEqual(firstItem.enclosure?.url, "https://example.com/audio/ep1.mp3")
|
||||
XCTAssertEqual(firstItem.enclosure?.type, "audio/mpeg")
|
||||
XCTAssertEqual(firstItem.enclosure?.length, 12345)
|
||||
XCTAssertEqual(firstItem.content, "<p>Full content for episode 1.</p>")
|
||||
}
|
||||
|
||||
func testParsesAtom10Feed() throws {
|
||||
let parser = FeedParser()
|
||||
let result = try parser.parse(
|
||||
data: Data(atomSample.utf8),
|
||||
sourceURL: "https://example.com/atom.xml"
|
||||
)
|
||||
|
||||
XCTAssertEqual(result.feedType, .atom)
|
||||
XCTAssertEqual(result.feed.title, "Example Atom Feed")
|
||||
XCTAssertEqual(result.feed.subtitle, "Recent engineering posts")
|
||||
XCTAssertEqual(result.feed.link, "https://example.com")
|
||||
XCTAssertEqual(result.feed.items.count, 2)
|
||||
|
||||
let firstItem = try XCTUnwrap(result.feed.items.first)
|
||||
XCTAssertEqual(firstItem.guid, "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a")
|
||||
XCTAssertEqual(firstItem.link, "https://example.com/posts/1")
|
||||
XCTAssertEqual(firstItem.author, "Jane Author")
|
||||
XCTAssertEqual(firstItem.enclosure?.url, "https://example.com/audio/post1.mp3")
|
||||
XCTAssertEqual(firstItem.enclosure?.type, "audio/mpeg")
|
||||
XCTAssertEqual(firstItem.enclosure?.length, 2048)
|
||||
}
|
||||
|
||||
func testHandlesITunesNamespace() throws {
|
||||
let parser = FeedParser()
|
||||
let result = try parser.parse(
|
||||
data: Data(rssWithITunesSample.utf8),
|
||||
sourceURL: "https://example.com/itunes.xml"
|
||||
)
|
||||
|
||||
XCTAssertEqual(result.feed.subtitle, "Podcast subtitle")
|
||||
XCTAssertEqual(result.feed.description, "Feed-level summary")
|
||||
|
||||
let item = try XCTUnwrap(result.feed.items.first)
|
||||
XCTAssertEqual(item.author, "Podcast Author")
|
||||
XCTAssertEqual(item.description, "Item-level summary")
|
||||
}
|
||||
|
||||
func testThrowsForMalformedXML() {
|
||||
let parser = FeedParser()
|
||||
XCTAssertThrowsError(
|
||||
try parser.parse(
|
||||
data: Data("<rss><channel><title>Broken".utf8),
|
||||
sourceURL: "https://example.com/broken.xml"
|
||||
)
|
||||
) { error in
|
||||
XCTAssertEqual(error as? FeedParsingError, .malformedXML)
|
||||
}
|
||||
}
|
||||
|
||||
func testParsesRealWorldStyleFeeds() throws {
|
||||
let parser = FeedParser()
|
||||
let rssResult = try parser.parse(
|
||||
data: Data(realWorldRSSSample.utf8),
|
||||
sourceURL: "https://feeds.example.com/news.xml"
|
||||
)
|
||||
XCTAssertEqual(rssResult.feedType, .rss)
|
||||
XCTAssertGreaterThan(rssResult.feed.items.count, 0)
|
||||
|
||||
let atomResult = try parser.parse(
|
||||
data: Data(realWorldAtomSample.utf8),
|
||||
sourceURL: "https://feeds.example.com/engineering.xml"
|
||||
)
|
||||
XCTAssertEqual(atomResult.feedType, .atom)
|
||||
XCTAssertGreaterThan(atomResult.feed.items.count, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private let rssSample = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<channel>
|
||||
<title>Example Podcast</title>
|
||||
<link>https://example.com</link>
|
||||
<description>A sample RSS feed</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>Mon, 30 Mar 2026 10:00:00 +0000</lastBuildDate>
|
||||
<generator>RSSuper Test Suite</generator>
|
||||
<ttl>60</ttl>
|
||||
<itunes:subtitle>Weekly iOS and Swift updates</itunes:subtitle>
|
||||
<item>
|
||||
<title>Episode 1</title>
|
||||
<link>https://example.com/episodes/1</link>
|
||||
<guid>episode-1</guid>
|
||||
<pubDate>Mon, 30 Mar 2026 09:00:00 +0000</pubDate>
|
||||
<category>Swift</category>
|
||||
<description>Episode 1 summary</description>
|
||||
<content:encoded><![CDATA[<p>Full content for episode 1.</p>]]></content:encoded>
|
||||
<itunes:author>Host Name</itunes:author>
|
||||
<enclosure url="https://example.com/audio/ep1.mp3" type="audio/mpeg" length="12345" />
|
||||
</item>
|
||||
<item>
|
||||
<title>Episode 2</title>
|
||||
<link>https://example.com/episodes/2</link>
|
||||
<guid>episode-2</guid>
|
||||
<pubDate>Mon, 30 Mar 2026 08:00:00 +0000</pubDate>
|
||||
<description>Episode 2 summary</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
private let atomSample = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Example Atom Feed</title>
|
||||
<subtitle>Recent engineering posts</subtitle>
|
||||
<link href="https://example.com" rel="alternate" />
|
||||
<updated>2026-03-30T10:00:00Z</updated>
|
||||
<generator>Atom Test Generator</generator>
|
||||
<entry>
|
||||
<title>Post One</title>
|
||||
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
|
||||
<updated>2026-03-30T09:00:00Z</updated>
|
||||
<published>2026-03-30T08:59:00Z</published>
|
||||
<summary>First post summary</summary>
|
||||
<content type="html"><p>First post full content</p></content>
|
||||
<author><name>Jane Author</name></author>
|
||||
<link href="https://example.com/posts/1" rel="alternate" />
|
||||
<link href="https://example.com/audio/post1.mp3" rel="enclosure" type="audio/mpeg" length="2048" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Post Two</title>
|
||||
<id>urn:uuid:7a9b2f0d-65b2-44a7-a2ad-d3c3ff7dd003</id>
|
||||
<updated>2026-03-30T08:00:00Z</updated>
|
||||
<summary>Second post summary</summary>
|
||||
<link href="https://example.com/posts/2" rel="alternate" />
|
||||
</entry>
|
||||
</feed>
|
||||
"""
|
||||
|
||||
private let rssWithITunesSample = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||
<channel>
|
||||
<title>iTunes Feed</title>
|
||||
<itunes:subtitle>Podcast subtitle</itunes:subtitle>
|
||||
<itunes:summary>Feed-level summary</itunes:summary>
|
||||
<item>
|
||||
<title>Episode</title>
|
||||
<itunes:author>Podcast Author</itunes:author>
|
||||
<itunes:summary>Item-level summary</itunes:summary>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
private let realWorldRSSSample = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Daily Tech News</title>
|
||||
<link>https://news.example.com</link>
|
||||
<description>Latest updates from the tech world</description>
|
||||
<lastBuildDate>Mon, 30 Mar 2026 12:00:00 +0000</lastBuildDate>
|
||||
<item>
|
||||
<title>Apple ships new SDK tools</title>
|
||||
<link>https://news.example.com/apple-sdk-tools</link>
|
||||
<guid>news-1</guid>
|
||||
<pubDate>Mon, 30 Mar 2026 11:00:00 +0000</pubDate>
|
||||
<description>Tooling improvements for mobile developers.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
private let realWorldAtomSample = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Engineering Blog</title>
|
||||
<updated>2026-03-30T12:00:00Z</updated>
|
||||
<link rel="alternate" href="https://engineering.example.com" />
|
||||
<entry>
|
||||
<title>Improving app startup performance</title>
|
||||
<id>tag:engineering.example.com,2026:post-1</id>
|
||||
<updated>2026-03-29T16:00:00Z</updated>
|
||||
<published>2026-03-29T15:00:00Z</published>
|
||||
<summary>How we reduced cold-start by 25%.</summary>
|
||||
<content>Detailed analysis of startup bottlenecks and fixes.</content>
|
||||
<author><name>Engineering Team</name></author>
|
||||
<link rel="alternate" href="https://engineering.example.com/posts/startup-performance" />
|
||||
</entry>
|
||||
</feed>
|
||||
"""
|
||||
Reference in New Issue
Block a user