// Copyright 2017 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. package rss // import "miniflux.app/reader/rss" import ( "bytes" "testing" "time" ) func TestParseRss2Sample(t *testing.T) { data := ` Liftoff News http://liftoff.msfc.nasa.gov/ Liftoff to Space Exploration. 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(bytes.NewBufferString(data)) if err != nil { t.Fatal(err) } if feed.Title != "Liftoff News" { t.Errorf("Incorrect title, got: %s", feed.Title) } if feed.FeedURL != "" { 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 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) } } func TestParseFeedWithoutTitle(t *testing.T) { data := ` https://example.org/ ` feed, err := Parse(bytes.NewBufferString(data)) if err != nil { t.Fatal(err) } if feed.Title != "https://example.org/" { t.Errorf("Incorrect feed title, got: %s", feed.Title) } } func TestParseEntryWithoutTitle(t *testing.T) { data := ` https://example.org/ https://example.org/item ` feed, err := Parse(bytes.NewBufferString(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 TestParseEntryWithoutLink(t *testing.T) { data := ` https://example.org/ 1234 ` feed, err := Parse(bytes.NewBufferString(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 TestParseEntryWithAtomLink(t *testing.T) { data := ` https://example.org/ Test ` feed, err := Parse(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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 TestParseEntryWithNonStandardAtomAuthor(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item Foo Bar Vice President FooBar Inc. ` feed, err := Parse(bytes.NewBufferString(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 TestParseEntryWithAtomAuthorEmail(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item author@example.org ` feed, err := Parse(bytes.NewBufferString(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 TestParseEntryWithAtomAuthor(t *testing.T) { data := ` Example https://example.org/ Test https://example.org/item Foo Bar ` feed, err := Parse(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(data)) if err != nil { t.Fatal(err) } if feed.Entries[0].Content != `

Example.

` { t.Errorf("Incorrect entry content, got: %s", 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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(data)) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } if feed.Entries[0].URL != "http://www.example.org/entries/1" { t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) } if len(feed.Entries[0].Enclosures) != 1 { t.Errorf("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 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(bytes.NewBufferString(data)) if err != nil { t.Fatal(err) } if len(feed.Entries) != 1 { t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) } if feed.Entries[0].URL != "http://www.example.org/entries/1" { t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) } if len(feed.Entries[0].Enclosures) != 1 { t.Errorf("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 TestParseEntryWithRelativeURL(t *testing.T) { data := ` https://example.org/ item.html ` feed, err := Parse(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(data)) if err == nil { t.Error("Parse should returns an error") } } func TestParseWithHTMLEntity(t *testing.T) { data := ` https://example.org/ Example   Feed ` feed, err := Parse(bytes.NewBufferString(data)) if err != nil { t.Fatal(err) } if feed.Title != "Example \u00a0 Feed" { t.Errorf(`Incorrect title, got: %q`, feed.Title) } } func TestParseWithInvalidCharacterEntity(t *testing.T) { data := ` https://example.org/a&b Example Feed ` feed, err := Parse(bytes.NewBufferString(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 http://example.org Example Item http://www.example.org/entries/1 nonadult ` feed, err := Parse(bytes.NewBufferString(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 http://example.org Example Item http://www.example.org/entries/1 Some Title for Media 1 ` feed, err := Parse(bytes.NewBufferString(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) != 3 { 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/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 http://example.org Example Item http://www.example.org/entries/1 ` feed, err := Parse(bytes.NewBufferString(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)) } expectedResults := []struct { url string mimeType string size int64 }{ {"http://www.example.org/file.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 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(bytes.NewBufferString(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(bytes.NewBufferString(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(bytes.NewBufferString(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) } }