// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. // SPDX-License-Identifier: Apache-2.0 package rss // import "miniflux.app/v2/internal/reader/rss" import ( "bytes" "testing" "time" ) func TestParseRss2Sample(t *testing.T) { data := ` Liftoff News http://liftoff.msfc.nasa.gov/ Liftoff to Space Exploration. http://liftoff.msfc.nasa.gov/HomePageXtra/MeatBall.gif NASA http://liftoff.msfc.nasa.gov/ en-us Tue, 10 Jun 2003 04:00:00 GMT Tue, 10 Jun 2003 09:41:01 GMT http://blogs.law.harvard.edu/tech/rss Weblog Editor 2.0 editor@example.com webmaster@example.com Star City http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>. Tue, 03 Jun 2003 09:39:21 GMT http://liftoff.msfc.nasa.gov/2003/06/03.html#item573 Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm">partial eclipse of the Sun</a> on Saturday, May 31st. Fri, 30 May 2003 11:06:42 GMT http://liftoff.msfc.nasa.gov/2003/05/30.html#item572 The Engine That Does More http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that. Tue, 27 May 2003 08:37:32 GMT http://liftoff.msfc.nasa.gov/2003/05/27.html#item571 Astronauts' Dirty Laundry http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options. Tue, 20 May 2003 08:56:02 GMT http://liftoff.msfc.nasa.gov/2003/05/20.html#item570 ` feed, err := Parse("http://liftoff.msfc.nasa.gov/rss.xml", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Title != "Liftoff News" { t.Errorf("Incorrect title, got: %s", feed.Title) } if feed.FeedURL != "http://liftoff.msfc.nasa.gov/rss.xml" { t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) } if feed.SiteURL != "http://liftoff.msfc.nasa.gov/" { t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) } if feed.IconURL != "http://liftoff.msfc.nasa.gov/HomePageXtra/MeatBall.gif" { t.Errorf("Incorrect image URL, got: %s", feed.IconURL) } if len(feed.Entries) != 4 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } expectedDate := time.Date(2003, time.June, 3, 9, 39, 21, 0, time.UTC) if !feed.Entries[0].Date.Equal(expectedDate) { t.Errorf("Incorrect entry date, got: %v, want: %v", feed.Entries[0].Date, expectedDate) } if feed.Entries[0].Hash != "5b2b4ac2fe1786ddf0fd2da2f1b07f64e691264f41f2db3ea360f31bb6d9152b" { t.Errorf("Incorrect entry hash, got: %s", feed.Entries[0].Hash) } if feed.Entries[0].URL != "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp" { t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) } if feed.Entries[0].Title != "Star City" { t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title) } if feed.Entries[0].Content != `How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's Star City.` { t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].Content) } if feed.Entries[1].URL != "http://liftoff.msfc.nasa.gov/2003/05/30.html#item572" { t.Errorf("Incorrect entry URL, got: %s", feed.Entries[1].URL) } } func TestParseFeedWithFeedURLWithTrailingSpace(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item ` feed, err := Parse("https://example.org/ ", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.FeedURL != "https://example.org/rss" { t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) } } func TestParseFeedWithRelativeFeedURL(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.FeedURL != "https://example.org/rss" { t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) } } func TestParseFeedSiteURLWithTrailingSpace(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.SiteURL != "https://example.org/" { t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) } } func TestParseFeedWithRelativeSiteURL(t *testing.T) { data := ` Example /example Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.SiteURL != "https://example.org/example" { t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) } } func TestParseFeedWithoutTitle(t *testing.T) { data := ` https://example.org/ ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Title != "https://example.org/" { t.Errorf("Incorrect feed title, got: %s", feed.Title) } } func TestParseEntryWithoutTitleAndDescription(t *testing.T) { data := ` https://example.org/ https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "https://example.org/item" { t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title) } } func TestParseEntryWithoutTitleButWithDescription(t *testing.T) { data := ` https://example.org/ https://example.org/item This is the description ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "This is the description" { t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title) } } func TestParseEntryWithMediaTitle(t *testing.T) { data := ` https://example.org/ Entry Title https://example.org/item Media Title ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "Entry Title" { t.Errorf("Incorrect entry title, got: %q", feed.Entries[0].Title) } } func TestParseEntryWithDCTitleOnly(t *testing.T) { data := ` https://example.org/ Entry Title https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "Entry Title" { t.Errorf("Incorrect entry title, got: %q", feed.Entries[0].Title) } } func TestParseEntryWithoutLink(t *testing.T) { data := ` https://example.org/ 1234 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].URL != "https://example.org/" { t.Errorf("Incorrect entry link, got: %s", feed.Entries[0].URL) } if feed.Entries[0].Hash != "03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4" { t.Errorf("Incorrect entry hash, got: %s", feed.Entries[0].Hash) } } func TestParseEntryWithOnlyGuidPermalink(t *testing.T) { data := ` https://example.org/ https://example.org/some-article.html https://example.org/another-article.html ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].URL != "https://example.org/some-article.html" { t.Errorf("Incorrect entry link, got: %s", feed.Entries[0].URL) } if feed.Entries[1].URL != "https://example.org/another-article.html" { t.Errorf("Incorrect entry link, got: %s", feed.Entries[1].URL) } } func TestParseEntryWithAtomLink(t *testing.T) { data := ` https://example.org/ Test ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].URL != "https://example.org/item" { t.Errorf("Incorrect entry link, got: %s", feed.Entries[0].URL) } } func TestParseEntryWithMultipleAtomLinks(t *testing.T) { data := ` https://example.org/ Test ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].URL != "https://example.org/b" { t.Errorf("Incorrect entry link, got: %s", feed.Entries[0].URL) } } func TestParseFeedURLWithAtomLink(t *testing.T) { data := ` Example https://example.org/ ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.FeedURL != "https://example.org/rss" { t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) } if feed.SiteURL != "https://example.org/" { t.Errorf("Incorrect site URL, got: %s", feed.SiteURL) } } func TestParseFeedWithWebmaster(t *testing.T) { data := ` Example https://example.org/ webmaster@example.com Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "webmaster@example.com" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseFeedWithManagingEditor(t *testing.T) { data := ` Example https://example.org/ webmaster@example.com editor@example.com Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "editor@example.com" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithAuthorAndInnerHTML(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item by ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "by Foo Bar" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithAuthorAndCDATA(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "by Foo Bar" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithAtomAuthorEmail(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item author@example.org ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "author@example.org" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithAtomAuthorName(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item Foo Bar ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "Foo Bar" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got: %q instead of %q", result, expected) } } func TestParseEntryWithDublinCoreAuthor(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item Me (me@example.com) ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "Me (me@example.com)" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithItunesAuthor(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item Someone ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "Someone" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseFeedWithItunesAuthor(t *testing.T) { data := ` Example https://example.org/ Someone Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "Someone" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseFeedWithItunesOwner(t *testing.T) { data := ` Example https://example.org/ John Doe john.doe@example.com Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "John Doe" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseFeedWithItunesOwnerEmail(t *testing.T) { data := ` Example https://example.org/ john.doe@example.com Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "john.doe@example.com" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithGooglePlayAuthor(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item Someone ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "Someone" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseFeedWithGooglePlayAuthor(t *testing.T) { data := ` Example https://example.org/ Someone Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := "Someone" result := feed.Entries[0].Author if result != expected { t.Errorf("Incorrect entry author, got %q instead of %q", result, expected) } } func TestParseEntryWithDublinCoreDate(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 Description. UUID 2002-09-29T23:40:06-05:00 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } location, _ := time.LoadLocation("EST") expectedDate := time.Date(2002, time.September, 29, 23, 40, 06, 0, location) if !feed.Entries[0].Date.Equal(expectedDate) { t.Errorf("Incorrect entry date, got: %v, want: %v", feed.Entries[0].Date, expectedDate) } } func TestParseEntryWithContentEncoded(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 Description. UUID Example.

]]>
` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Content != `

Example.

` { t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].Content) } } // https://www.rssboard.org/rss-encoding-examples func TestParseEntryDescriptionWithEncodedHTMLTags(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 this is <b>bold</b> ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Content != `this is bold` { t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content) } } // https://www.rssboard.org/rss-encoding-examples func TestParseEntryWithDescriptionWithHTMLCDATA(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 bold]]> ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Content != `this is bold` { t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content) } } // https://www.rssboard.org/rss-encoding-examples func TestParseEntryDescriptionWithEncodingAngleBracketsInText(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 5 &lt; 8, ticker symbol &lt;BIGCO&gt; ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Content != `5 < 8, ticker symbol <BIGCO>` { t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content) } } // https://www.rssboard.org/rss-encoding-examples func TestParseEntryDescriptionWithEncodingAngleBracketsWithinCDATASection(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Content != `5 < 8, ticker symbol <BIGCO>` { t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content) } } func TestParseEntryWithFeedBurnerLink(t *testing.T) { data := ` Example http://example.org/ Item 1 http://example.org/item1 http://example.org/original ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].URL != "http://example.org/original" { t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].URL) } } func TestParseEntryTitleWithWhitespaces(t *testing.T) { data := ` Example http://example.org Some Title http://www.example.org/entries/1 Fri, 15 Jul 2005 00:00:00 -0500 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "Some Title" { t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title) } } func TestParseEntryWithEnclosures(t *testing.T) { data := ` My Podcast Feed http://example.org some.email@example.org Podcasting with RSS http://www.example.org/entries/1 An overview of RSS podcasting Fri, 15 Jul 2005 00:00:00 -0500 http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) } if feed.Entries[0].Enclosures[0].MimeType != "audio/mpeg" { t.Errorf("Incorrect enclosure type, got: %s", feed.Entries[0].Enclosures[0].MimeType) } if feed.Entries[0].Enclosures[0].Size != 12345 { t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[0].Size) } } func TestParseEntryWithIncorrectEnclosureLength(t *testing.T) { data := ` My Podcast Feed http://example.org some.email@example.org Podcasting with RSS http://www.example.org/entries/1 An overview of RSS podcasting Fri, 15 Jul 2005 00:00:00 -0500 http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 2 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) } if feed.Entries[0].Enclosures[0].MimeType != "audio/mpeg" { t.Errorf("Incorrect enclosure type, got: %s", feed.Entries[0].Enclosures[0].MimeType) } if feed.Entries[0].Enclosures[0].Size != 0 { t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[0].Size) } if feed.Entries[0].Enclosures[1].Size != 0 { t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[0].Size) } } func TestParseEntryWithDuplicatedEnclosureURL(t *testing.T) { data := ` My Podcast Feed http://example.org Podcasting with RSS http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) } } func TestParseEntryWithEmptyEnclosureURL(t *testing.T) { data := ` My Podcast Feed http://example.org some.email@example.org Podcasting with RSS http://www.example.org/entries/1 An overview of RSS podcasting Fri, 15 Jul 2005 00:00:00 -0500 http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 0 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } } func TestParseEntryWithRelativeEnclosureURL(t *testing.T) { data := ` My Podcast Feed http://example.org some.email@example.org Podcasting with RSS http://www.example.org/entries/1 An overview of RSS podcasting Fri, 15 Jul 2005 00:00:00 -0500 http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://example.org/files/file.mp3" { t.Errorf("Incorrect enclosure URL, got: %q", feed.Entries[0].Enclosures[0].URL) } } func TestParseEntryWithFeedBurnerEnclosures(t *testing.T) { data := ` My Example Feed http://example.org some.email@example.org Example Item http://www.example.org/entries/1 http://example.org/67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://example.org/67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3" { t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) } if feed.Entries[0].Enclosures[0].MimeType != "audio/mpeg" { t.Errorf("Incorrect enclosure type, got: %s", feed.Entries[0].Enclosures[0].MimeType) } if feed.Entries[0].Enclosures[0].Size != 76192460 { t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[0].Size) } } func TestParseEntryWithFeedBurnerEnclosuresAndRelativeURL(t *testing.T) { data := ` My Example Feed http://example.org Example Item http://www.example.org/entries/1 /67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://example.org/67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3" { t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) } } func TestParseEntryWithRelativeURL(t *testing.T) { data := ` https://example.org/ item.html ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "https://example.org/item.html" { t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title) } } func TestParseEntryWithCommentsURL(t *testing.T) { data := ` https://example.org/ Item 1 https://example.org/item1 https://example.org/comments 42 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].CommentsURL != "https://example.org/comments" { t.Errorf("Incorrect entry comments URL, got: %q", feed.Entries[0].CommentsURL) } } func TestParseEntryWithInvalidCommentsURL(t *testing.T) { data := ` https://example.org/ Item 1 https://example.org/item1 Some text ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].CommentsURL != "" { t.Errorf("Incorrect entry comments URL, got: %q", feed.Entries[0].CommentsURL) } } func TestParseInvalidXml(t *testing.T) { data := `garbage` _, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err == nil { t.Error("Parse should returns an error") } } func TestParseFeedTitleWithHTMLEntity(t *testing.T) { data := ` https://example.org/ Example   Feed ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Title != "Example \u00a0 Feed" { t.Errorf(`Incorrect title, got: %q`, feed.Title) } } func TestParseFeedTitleWithUnicodeEntityAndCdata(t *testing.T) { data := ` https://example.org/ <![CDATA[Jenny’s Newsletter]]> ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Title != `Jenny’s Newsletter` { t.Errorf(`Incorrect title, got: %q`, feed.Title) } } func TestParseItemTitleWithHTMLEntity(t *testing.T) { data := ` https://example.org/ Example </example> http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "" { t.Errorf(`Incorrect title, got: %q`, feed.Entries[0].Title) } } func TestParseItemTitleWithNumericCharacterReference(t *testing.T) { data := ` https://example.org/ Example Σ ß http://www.example.org/article.html ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "Σ ß" { t.Errorf(`Incorrect title, got: %q`, feed.Entries[0].Title) } } func TestParseItemTitleWithDoubleEncodedEntities(t *testing.T) { data := ` https://example.org/ Example &#39;Text&#39; http://www.example.org/article.html ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.Entries[0].Title != "'Text'" { t.Errorf(`Incorrect title, got: %q`, feed.Entries[0].Title) } } func TestParseFeedLinkWithInvalidCharacterEntity(t *testing.T) { data := ` https://example.org/a&b Example Feed ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.SiteURL != "https://example.org/a&b" { t.Errorf(`Incorrect url, got: %q`, feed.SiteURL) } } func TestParseEntryWithMediaGroup(t *testing.T) { data := ` My Example Feed https://example.org Example Item http://www.example.org/entries/1 nonadult ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 6 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } expectedResults := []struct { url string mimeType string size int64 }{ {"https://example.org/image.jpg", "image/*", 0}, {"https://example.org/file3.torrent", "application/x-bittorrent", 670053113}, {"https://example.org/file1.torrent", "application/x-bittorrent", 0}, {"https://example.org/file2.torrent", "application/x-bittorrent", 0}, {"https://example.org/file4.torrent", "application/x-bittorrent", 0}, {"https://example.org/file5.torrent", "application/x-bittorrent", 42}, } for index, enclosure := range feed.Entries[0].Enclosures { if expectedResults[index].url != enclosure.URL { t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url) } if expectedResults[index].mimeType != enclosure.MimeType { t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType) } if expectedResults[index].size != enclosure.Size { t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size) } } } func TestParseEntryWithMediaContent(t *testing.T) { data := ` My Example Feed https://example.org Example Item http://www.example.org/entries/1 Some Title for Media 1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 4 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } expectedResults := []struct { url string mimeType string size int64 }{ {"https://example.org/thumbnail.jpg", "image/*", 0}, {"https://example.org/thumbnail.jpg", "image/*", 0}, {"https://example.org/media1.jpg", "image/*", 0}, {"https://example.org/media2.jpg", "image/*", 0}, } for index, enclosure := range feed.Entries[0].Enclosures { if expectedResults[index].url != enclosure.URL { t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url) } if expectedResults[index].mimeType != enclosure.MimeType { t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType) } if expectedResults[index].size != enclosure.Size { t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size) } } } func TestParseEntryWithMediaPeerLink(t *testing.T) { data := ` My Example Feed https://website.example.org Example Item http://www.example.org/entries/1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 2 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } expectedResults := []struct { url string mimeType string size int64 }{ {"https://www.example.org/file.torrent", "application/x-bittorrent", 0}, {"https://website.example.org/file2.torrent", "application/x-bittorrent", 0}, } for index, enclosure := range feed.Entries[0].Enclosures { if expectedResults[index].url != enclosure.URL { t.Errorf(`Unexpected enclosure URL, got %q instead of %q`, enclosure.URL, expectedResults[index].url) } if expectedResults[index].mimeType != enclosure.MimeType { t.Errorf(`Unexpected enclosure type, got %q instead of %q`, enclosure.MimeType, expectedResults[index].mimeType) } if expectedResults[index].size != enclosure.Size { t.Errorf(`Unexpected enclosure size, got %d instead of %d`, enclosure.Size, expectedResults[index].size) } } } func TestParseItunesDuration(t *testing.T) { data := ` Podcast Example http://www.example.com/index.html Podcast Episode http://example.com/episode.m4a Tue, 08 Mar 2016 12:00:00 GMT 1:23:45 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := 83 result := feed.Entries[0].ReadingTime if expected != result { t.Errorf(`Unexpected podcast duration, got %d instead of %d`, result, expected) } } func TestParseIncorrectItunesDuration(t *testing.T) { data := ` Podcast Example http://www.example.com/index.html Podcast Episode http://example.com/episode.m4a Tue, 08 Mar 2016 12:00:00 GMT invalid ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } expected := 0 result := feed.Entries[0].ReadingTime if expected != result { t.Errorf(`Unexpected podcast duration, got %d instead of %d`, result, expected) } } func TestEntryDescriptionFromItunesSummary(t *testing.T) { data := ` Podcast Example http://www.example.com/index.html Podcast Episode http://example.com/episode.m4a Tue, 08 Mar 2016 12:00:00 GMT Episode Subtitle Episode Summary ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } expected := "Episode Summary" result := feed.Entries[0].Content if expected != result { t.Errorf(`Unexpected podcast content, got %q instead of %q`, result, expected) } } func TestEntryDescriptionFromItunesSubtitle(t *testing.T) { data := ` Podcast Example http://www.example.com/index.html Podcast Episode http://example.com/episode.m4a Tue, 08 Mar 2016 12:00:00 GMT Episode Subtitle ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } expected := "Episode Subtitle" result := feed.Entries[0].Content if expected != result { t.Errorf(`Unexpected podcast content, got %q instead of %q`, result, expected) } } func TestEntryDescriptionFromGooglePlayDescription(t *testing.T) { data := ` Podcast Example http://www.example.com/index.html Podcast Episode http://example.com/episode.m4a Tue, 08 Mar 2016 12:00:00 GMT Episode Subtitle Episode Description ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } expected := "Episode Description" result := feed.Entries[0].Content if expected != result { t.Errorf(`Unexpected podcast content, got %q instead of %q`, result, expected) } } func TestParseEntryWithRSSDescriptionAndMediaDescription(t *testing.T) { data := ` Podcast Example http://www.example.com/index.html Entry Title http://www.example.com/entries/1 Entry Description Media Description ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } expected := "Entry Description" result := feed.Entries[0].Content if expected != result { t.Errorf(`Unexpected description, got %q instead of %q`, result, expected) } } func TestParseFeedWithCategories(t *testing.T) { data := ` Example https://example.org/ Category 1 Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries[0].Tags) != 2 { t.Errorf("Incorrect number of tags, got: %d", len(feed.Entries[0].Tags)) } expected := []string{"Category 1", "Category 2"} result := feed.Entries[0].Tags for i, tag := range result { if tag != expected[i] { t.Errorf("Incorrect tag, got: %q", tag) } } } func TestParseEntryWithCategories(t *testing.T) { data := ` Example https://example.org/ Category 3 Test https://example.org/item Category 1 ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries[0].Tags) != 2 { t.Fatalf("Incorrect number of tags, got: %d", len(feed.Entries[0].Tags)) } expected := []string{"Category 1", "Category 2"} result := feed.Entries[0].Tags for i, tag := range result { if tag != expected[i] { t.Errorf("Incorrect tag, got: %q", tag) } } } func TestParseFeedWithItunesCategories(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries[0].Tags) != 4 { t.Errorf("Incorrect number of tags, got: %d", len(feed.Entries[0].Tags)) } expected := []string{"Society & Culture", "Documentary", "Health", "Mental Health"} result := feed.Entries[0].Tags for i, tag := range result { if tag != expected[i] { t.Errorf("Incorrect tag, got: %q", tag) } } } func TestParseFeedWithGooglePlayCategory(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries[0].Tags) != 1 { t.Errorf("Incorrect number of tags, got: %d", len(feed.Entries[0].Tags)) } expected := []string{"Art"} result := feed.Entries[0].Tags for i, tag := range result { if tag != expected[i] { t.Errorf("Incorrect tag, got: %q", tag) } } } func TestParseEntryWithMediaCategories(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item visual_art music/artist/album/song ycantpark mobile Arts/Movies/Titles/A/Ace_Ventura_Series/Ace_Ventura_ -_Pet_Detective ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if len(feed.Entries[0].Tags) != 2 { t.Errorf("Incorrect number of tags, got: %d", len(feed.Entries[0].Tags)) } expected := []string{"Visual Art", "Ace Ventura - Pet Detective"} result := feed.Entries[0].Tags for i, tag := range result { if tag != expected[i] { t.Errorf("Incorrect tag, got: %q", tag) } } } func TestParseFeedWithTTLField(t *testing.T) { data := ` Example https://example.org/ 60 Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.TTL != 60 { t.Errorf("Incorrect TTL, got: %d", feed.TTL) } } func TestParseFeedWithIncorrectTTLValue(t *testing.T) { data := ` Example https://example.org/ invalid Test https://example.org/item ` feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) if err != nil { t.Fatal(err) } if feed.TTL != 0 { t.Errorf("Incorrect TTL, got: %d", feed.TTL) } }