// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. // SPDX-License-Identifier: Apache-2.0 package subscription import ( "testing" ) func TestFindYoutubeFeed(t *testing.T) { type testResult struct { websiteURL string feedURLs []string discoveryError bool } scenarios := []testResult{ // Video URL { websiteURL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", feedURLs: []string{}, }, // Video URL with position argument { websiteURL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=1", feedURLs: []string{}, }, // Video URL with position argument { websiteURL: "https://www.youtube.com/watch?t=1&v=dQw4w9WgXcQ", feedURLs: []string{}, }, // Channel URL { websiteURL: "https://www.youtube.com/channel/UC-Qj80avWItNRjkZ41rzHyw", feedURLs: []string{ "https://www.youtube.com/feeds/videos.xml?channel_id=UC-Qj80avWItNRjkZ41rzHyw", "https://www.youtube.com/feeds/videos.xml?playlist_id=UULF-Qj80avWItNRjkZ41rzHyw", "https://www.youtube.com/feeds/videos.xml?playlist_id=UUSH-Qj80avWItNRjkZ41rzHyw", "https://www.youtube.com/feeds/videos.xml?playlist_id=UULV-Qj80avWItNRjkZ41rzHyw", }, }, // Channel URL with name { websiteURL: "https://www.youtube.com/@ABCDEFG", feedURLs: []string{}, }, // Playlist URL { websiteURL: "https://www.youtube.com/playlist?list=PLOOwEPgFWm_NHcQd9aCi5JXWASHO_n5uR", feedURLs: []string{"https://www.youtube.com/feeds/videos.xml?playlist_id=PLOOwEPgFWm_NHcQd9aCi5JXWASHO_n5uR"}, }, // Playlist URL with video ID { websiteURL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=PLOOwEPgFWm_N42HlCLhqyJ0ZBWr5K1QDM", feedURLs: []string{"https://www.youtube.com/feeds/videos.xml?playlist_id=PLOOwEPgFWm_N42HlCLhqyJ0ZBWr5K1QDM"}, }, // Playlist URL with video ID and index argument { websiteURL: "https://www.youtube.com/watch?v=6IutBmRJNLk&list=PLOOwEPgFWm_NHcQd9aCi5JXWASHO_n5uR&index=4", feedURLs: []string{"https://www.youtube.com/feeds/videos.xml?playlist_id=PLOOwEPgFWm_NHcQd9aCi5JXWASHO_n5uR"}, }, // Empty playlist ID parameter { websiteURL: "https://www.youtube.com/playlist?list=", feedURLs: []string{}, }, // Non-Youtube URL { websiteURL: "https://www.example.com/channel/UC-Qj80avWItNRjkZ41rzHyw", feedURLs: []string{}, }, // Invalid URL { websiteURL: "https://example|org/", feedURLs: []string{}, discoveryError: true, }, } for _, scenario := range scenarios { subscriptions, localizedError := NewSubscriptionFinder(nil).findSubscriptionsFromYouTube(scenario.websiteURL) if scenario.discoveryError { if localizedError == nil { t.Fatalf(`Parsing an invalid URL should return an error`) } } if len(scenario.feedURLs) == 0 { if len(subscriptions) > 0 { t.Fatalf(`Parsing an invalid URL should not return any subscription: %q -> %v`, scenario.websiteURL, subscriptions) } } else { if localizedError != nil { t.Fatalf(`Parsing a correctly formatted YouTube playlist or channel page should not return any error: %v`, localizedError) } if len(subscriptions) != len(scenario.feedURLs) { t.Fatalf(`Incorrect number of subscriptions returned, expected %d, got %d`, len(scenario.feedURLs), len(subscriptions)) } for i := range scenario.feedURLs { if subscriptions[i].URL != scenario.feedURLs[i] { t.Errorf(`Unexpected feed, got %s, instead of %s`, subscriptions[i].URL, scenario.feedURLs[i]) } } } } } func TestParseWebPageWithRssFeed(t *testing.T) { htmlPage := `
` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "Some Title" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/rss" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "rss" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithAtomFeed(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "Some Title" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/atom.xml" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "atom" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithJSONFeed(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "Some Title" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/feed.json" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "json" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithOldJSONFeedMimeType(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "Some Title" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/feed.json" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "json" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithJSONFeedWpJsonIgnored(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 0 { t.Fatal(`Incorrect number of subscriptions returned`) } } func TestParseWebPageWithRelativeFeedURL(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "Some Title" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/feed.json" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "json" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithEmptyTitle(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "http://example.org/feed.json" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/feed.json" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "json" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithMultipleFeeds(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 2 { t.Fatal(`Incorrect number of subscriptions returned`) } } func TestParseWebPageWithDuplicatedFeeds(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 1 { t.Fatal(`Incorrect number of subscriptions returned`) } if subscriptions[0].Title != "Feed A" { t.Errorf(`Incorrect subscription title: %q`, subscriptions[0].Title) } if subscriptions[0].URL != "http://example.org/feed.xml" { t.Errorf(`Incorrect subscription URL: %q`, subscriptions[0].URL) } if subscriptions[0].Type != "rss" { t.Errorf(`Incorrect subscription type: %q`, subscriptions[0].Type) } } func TestParseWebPageWithEmptyFeedURL(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 0 { t.Fatal(`Incorrect number of subscriptions returned`) } } func TestParseWebPageWithNoHref(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", doc) if err != nil { t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err) } if len(subscriptions) != 0 { t.Fatal(`Incorrect number of subscriptions returned`) } } func TestFindCanonicalURL(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } canonicalURL := NewSubscriptionFinder(nil).findCanonicalURL("https://example.org/page", "http://example.org", doc) if canonicalURL != "https://example.org/canonical-page" { t.Errorf(`Unexpected canonical URL, got %q, expected %q`, canonicalURL, "https://example.org/canonical-page") } } func TestFindCanonicalURLNotFound(t *testing.T) { htmlPage := ` ` doc, shouldNeverHappenErr := parseHTMLDocument("text/html", []byte(htmlPage)) if shouldNeverHappenErr != nil { t.Fatalf(`Unable to parse the HTML: %v`, shouldNeverHappenErr) } canonicalURL := NewSubscriptionFinder(nil).findCanonicalURL("https://example.org/page", "https://example.org", doc) if canonicalURL != "https://example.org/page" { t.Errorf(`Expected effective URL when canonical not found, got %q`, canonicalURL) } }