| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package rewrite // import "miniflux.app/v2/internal/reader/rewrite"
- import (
- "testing"
- "miniflux.app/v2/internal/model"
- )
- func TestRewriteEntryURL(t *testing.T) {
- scenarios := []struct {
- name string
- feed *model.Feed
- entry *model.Entry
- expectedURL string
- description string
- }{
- {
- name: "NoRewriteRules",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: "",
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when no rewrite rules are specified",
- },
- {
- name: "EmptyRewriteRules",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: " ",
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when rewrite rules are empty/whitespace",
- },
- {
- name: "ValidRewriteRule",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/article/(.+)"|"https://example.com/full-article/$1")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/full-article/123",
- description: "Should rewrite URL according to the regex pattern",
- },
- {
- name: "ComplexRegexRewrite",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://news.ycombinator.com/rss",
- UrlRewriteRules: `rewrite("^https://news\.ycombinator\.com/item\?id=(.+)"|"https://hn.algolia.com/api/v1/items/$1")`,
- },
- entry: &model.Entry{
- URL: "https://news.ycombinator.com/item?id=12345",
- },
- expectedURL: "https://hn.algolia.com/api/v1/items/12345",
- description: "Should handle complex regex patterns with escaped characters",
- },
- {
- name: "NoMatchingPattern",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://different.com/(.+)"|"https://rewritten.com/$1")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when regex pattern doesn't match",
- },
- {
- name: "InvalidRegexPattern",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/[invalid"|"https://rewritten.com/$1")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when regex pattern is invalid",
- },
- {
- name: "MalformedRewriteRule",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("invalid format")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when rewrite rule format is malformed",
- },
- {
- name: "MultipleGroups",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/([^/]+)/article/(.+)"|"https://example.com/full/$1/story/$2")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/tech/article/ai-news",
- },
- expectedURL: "https://example.com/full/tech/story/ai-news",
- description: "Should handle multiple capture groups in regex",
- },
- {
- name: "URLWithSpecialCharacters",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/(.+)"|"https://proxy.example.com/$1")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/test?param=value&other=123#section",
- },
- expectedURL: "https://proxy.example.com/article/test?param=value&other=123#section",
- description: "Should handle URLs with query parameters and fragments",
- },
- {
- name: "ReplaceWithStaticURL",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/(.+)"|"https://static.example.com/reader")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://static.example.com/reader",
- description: "Should replace with static URL when no capture groups are used in replacement",
- },
- {
- name: "EmptyReplacementString",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/(.+)"|"x")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "x",
- description: "Should replace with specified string",
- },
- {
- name: "EmptyReplacementNotSupported",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/(.+)"|"")`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when replacement is empty string (not supported by regex pattern)",
- },
- {
- name: "InvalidRewriteRuleFormat",
- feed: &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `not-a-rewrite-rule`,
- },
- entry: &model.Entry{
- URL: "https://example.com/article/123",
- },
- expectedURL: "https://example.com/article/123",
- description: "Should return original URL when rewrite rule doesn't match expected format",
- },
- }
- for _, scenario := range scenarios {
- t.Run(scenario.name, func(t *testing.T) {
- result := RewriteEntryURL(scenario.feed, scenario.entry)
- if result != scenario.expectedURL {
- t.Errorf("Expected URL %q, got %q. Description: %s", scenario.expectedURL, result, scenario.description)
- }
- })
- }
- }
- func TestRewriteEntryURLWithNilValues(t *testing.T) {
- t.Run("NilFeed", func(t *testing.T) {
- entry := &model.Entry{URL: "https://example.com/article/123"}
- // This should panic or handle gracefully - let's see what happens
- defer func() {
- if r := recover(); r == nil {
- t.Error("Expected panic when feed is nil, but function completed normally")
- }
- }()
- RewriteEntryURL(nil, entry)
- })
- t.Run("NilEntry", func(t *testing.T) {
- feed := &model.Feed{
- ID: 1,
- FeedURL: "https://example.com/feed.xml",
- UrlRewriteRules: `rewrite("^https://example.com/(.+)"|"https://rewritten.com/$1")`,
- }
- // This should panic or handle gracefully - let's see what happens
- defer func() {
- if r := recover(); r == nil {
- t.Error("Expected panic when entry is nil, but function completed normally")
- }
- }()
- RewriteEntryURL(feed, nil)
- })
- }
- func TestCustomReplaceRuleRegex(t *testing.T) {
- scenarios := []struct {
- name string
- input string
- expected []string
- matches bool
- }{
- {
- name: "ValidRule",
- input: `rewrite("^https://example.com/(.+)"|"https://rewritten.com/$1")`,
- expected: []string{`rewrite("^https://example.com/(.+)"|"https://rewritten.com/$1")`, `^https://example.com/(.+)`, `https://rewritten.com/$1`},
- matches: true,
- },
- {
- name: "ValidRuleWithEscapedCharacters",
- input: `rewrite("^https://news\\.ycombinator\\.com/item\\?id=(.+)"|"https://hn.algolia.com/api/v1/items/$1")`,
- expected: []string{`rewrite("^https://news\\.ycombinator\\.com/item\\?id=(.+)"|"https://hn.algolia.com/api/v1/items/$1")`, `^https://news\\.ycombinator\\.com/item\\?id=(.+)`, `https://hn.algolia.com/api/v1/items/$1`},
- matches: true,
- },
- {
- name: "InvalidFormat",
- input: `rewrite("invalid")`,
- expected: nil,
- matches: false,
- },
- {
- name: "EmptyString",
- input: ``,
- expected: nil,
- matches: false,
- },
- {
- name: "RandomText",
- input: `some random text`,
- expected: nil,
- matches: false,
- },
- }
- for _, scenario := range scenarios {
- t.Run(scenario.name, func(t *testing.T) {
- parts := customReplaceRuleRegex.FindStringSubmatch(scenario.input)
- if scenario.matches {
- if len(parts) < 3 {
- t.Errorf("Expected regex to match and return at least 3 parts, got %d parts: %v", len(parts), parts)
- return
- }
- // Check the full match and captured groups
- if parts[0] != scenario.expected[0] {
- t.Errorf("Expected full match %q, got %q", scenario.expected[0], parts[0])
- }
- if parts[1] != scenario.expected[1] {
- t.Errorf("Expected first capture group %q, got %q", scenario.expected[1], parts[1])
- }
- if parts[2] != scenario.expected[2] {
- t.Errorf("Expected second capture group %q, got %q", scenario.expected[2], parts[2])
- }
- } else if len(parts) >= 3 {
- t.Errorf("Expected regex not to match, but got %d parts: %v", len(parts), parts)
- }
- })
- }
- }
|