diff --git a/MacMagazine/Features/FeedLibrary/Sources/FeedLibrary/Services/Parser/APIXMLParser.swift b/MacMagazine/Features/FeedLibrary/Sources/FeedLibrary/Services/Parser/APIXMLParser.swift
index cd427e9e..1b991a1b 100644
--- a/MacMagazine/Features/FeedLibrary/Sources/FeedLibrary/Services/Parser/APIXMLParser.swift
+++ b/MacMagazine/Features/FeedLibrary/Sources/FeedLibrary/Services/Parser/APIXMLParser.swift
@@ -84,7 +84,8 @@ class APIXMLParser: NSObject, XMLParserDelegate {
}
currentPost.artworkURL = url
case "enclosure":
- guard let url = attributes?["url"] else {
+ guard let url = attributes?["url"],
+ attributes?["type"]?.hasPrefix("audio/") == true else {
return
}
currentPost.podcastURL = url
diff --git a/MacMagazine/Features/FeedLibrary/Tests/FeedLibraryTests/XMLParserTests.swift b/MacMagazine/Features/FeedLibrary/Tests/FeedLibraryTests/XMLParserTests.swift
index f1910ea4..1f66eab6 100644
--- a/MacMagazine/Features/FeedLibrary/Tests/FeedLibraryTests/XMLParserTests.swift
+++ b/MacMagazine/Features/FeedLibrary/Tests/FeedLibraryTests/XMLParserTests.swift
@@ -174,6 +174,70 @@ struct XMLParserTests {
}
}
+ @Test("Image enclosure from search feed is not treated as a podcast URL")
+ func parseSearchXMLImageEnclosureIsNotPodcastURL() async throws {
+ // Given
+ let xml = """
+
+ -
+ 123
+ News item
+ https://macmagazine.com.br/post
+
+
+ """
+ let data = Data(xml.utf8)
+
+ // When
+ let posts = try await withCheckedThrowingContinuation { continuation in
+ let parser = XMLParser(data: data)
+ let apiParser = APIXMLParser(
+ numberOfPosts: -1,
+ category: "",
+ parseFullContent: false,
+ continuation: continuation
+ )
+ parser.delegate = apiParser
+ parser.parse()
+ } as [XMLPost]
+
+ // Then
+ let post = try #require(posts.first)
+ #expect(post.podcastURL.isEmpty, "Image enclosure must not populate podcastURL")
+ }
+
+ @Test("Audio enclosure from search feed is treated as a podcast URL")
+ func parseSearchXMLAudioEnclosureIsPodcastURL() async throws {
+ // Given
+ let xml = """
+
+ -
+ 456
+ Podcast item
+ https://macmagazine.com.br/podcast
+
+
+ """
+ let data = Data(xml.utf8)
+
+ // When
+ let posts = try await withCheckedThrowingContinuation { continuation in
+ let parser = XMLParser(data: data)
+ let apiParser = APIXMLParser(
+ numberOfPosts: -1,
+ category: "",
+ parseFullContent: false,
+ continuation: continuation
+ )
+ parser.delegate = apiParser
+ parser.parse()
+ } as [XMLPost]
+
+ // Then
+ let post = try #require(posts.first)
+ #expect(post.podcastURL == "https://feeds.soundcloud.com/stream/episode.mp3")
+ }
+
// MARK: - Error Handling Tests
@Test("Parse invalid XML throws error")
diff --git a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Extensions/ArrayExtensions.swift b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Extensions/ArrayExtensions.swift
index a3bb7073..c916ccaa 100644
--- a/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Extensions/ArrayExtensions.swift
+++ b/MacMagazine/Features/NewsLibrary/Sources/NewsLibrary/Extensions/ArrayExtensions.swift
@@ -5,12 +5,14 @@ private let filterKeyToCategory: [String: NewsCategory] = {
var map = [String: NewsCategory]()
for category in NewsCategory.allCases {
map[category.filterKey] = category
+ map[category.rawValue] = category
}
return map
}()
public extension Array where Element == String {
var toNewsCategory: [NewsCategory] {
- compactMap { filterKeyToCategory[$0] }
+ var seen = Set()
+ return compactMap { filterKeyToCategory[$0] }.filter { seen.insert($0).inserted }
}
}