// 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 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) } if feed.Entries[0].Author != "by Foo Bar" { t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author) } } func TestParseEntryWithAtomAuthor(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) } if feed.Entries[0].Author != "Foo Bar" { t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author) } } 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) } if feed.Entries[0].Author != "Me (me@example.com)" { t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author) } } 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) } if feed.Entries[0].Author != "Someone" { t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author) } } 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) } if feed.Entries[0].Author != "Someone" { t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author) } } 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 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) } } }