diff options
Diffstat (limited to 'internal/redirects/matching_test.go')
-rw-r--r-- | internal/redirects/matching_test.go | 355 |
1 files changed, 344 insertions, 11 deletions
diff --git a/internal/redirects/matching_test.go b/internal/redirects/matching_test.go index 23815b97..3938b680 100644 --- a/internal/redirects/matching_test.go +++ b/internal/redirects/matching_test.go @@ -1,6 +1,7 @@ package redirects import ( + "net/url" "testing" "github.com/stretchr/testify/require" @@ -56,7 +57,7 @@ var testsWithoutPlaceholders = map[string]testCaseData{ }, } -func Test_matchesRule(t *testing.T) { +func testMatchesRule(t *testing.T) { t.Setenv(feature.RedirectsPlaceholders.EnvVariable, "true") tests := mergeTestSuites(testsWithoutPlaceholders, map[string]testCaseData{ @@ -68,11 +69,12 @@ func Test_matchesRule(t *testing.T) { expectMatch: true, expectedPath: "/bar/", }, + // Since we are treating path as an entire path, so // is considered domain name "multiple_leading_slashes": { rule: "/foo/ /bar/", path: "//foo", - expectMatch: true, - expectedPath: "/bar/", + expectMatch: false, + expectedPath: "", }, "multiple_slashes_in_middle": { rule: "/foo/bar /baz/", @@ -81,6 +83,13 @@ func Test_matchesRule(t *testing.T) { expectedPath: "/baz/", }, + "schema_host_from_url": { + rule: "http://pages.example.io/foo /baz/", + path: "http://pages.example.io/foo", + expectMatch: true, + expectedPath: "/baz/", + }, + "splat_match": { rule: "/foo/*/bar /foo/:splat/qux", path: "/foo/baz/bar", @@ -153,12 +162,6 @@ func Test_matchesRule(t *testing.T) { expectMatch: true, expectedPath: "/qux/foo/bar", }, - "multiple_splats": { - rule: "/foo/*/bar/*/baz /qux/:splat", - path: "/foo/hello/bar/world/baz", - expectMatch: true, - expectedPath: "/qux/hello", - }, "duplicate_splat_replacements": { rule: "/foo/* /qux/:splat/:splat", path: "/foo/hello", @@ -301,13 +304,25 @@ func Test_matchesRule(t *testing.T) { rules, err := netlifyRedirects.ParseString(tt.rule) require.NoError(t, err) - isMatch, path := matchesRule(&rules[0], tt.path) + parsedURL, err := url.Parse(tt.path) + require.NoError(t, err) + + isMatch, path := matchesRule(&rules[0], parsedURL) require.Equal(t, tt.expectMatch, isMatch) require.Equal(t, tt.expectedPath, path) }) } } +func Test_matchesRule(t *testing.T) { + testMatchesRule(t) +} + +func Test_matchesNonDomainRule_DomainRedirects_enabled(t *testing.T) { + t.Setenv(feature.DomainRedirects.EnvVariable, "true") + testMatchesRule(t) +} + // Tests matching behavior when the `FF_ENABLE_PLACEHOLDERS` // feature flag is not enabled. These tests can be removed when the // `FF_ENABLE_PLACEHOLDERS` flag is removed. @@ -343,7 +358,325 @@ func Test_matchesRule_NoPlaceholders(t *testing.T) { rules, err := netlifyRedirects.ParseString(tt.rule) require.NoError(t, err) - isMatch, path := matchesRule(&rules[0], tt.path) + parsedURL, err := url.Parse(tt.path) + require.NoError(t, err) + + isMatch, path := matchesRule(&rules[0], parsedURL) + require.Equal(t, tt.expectMatch, isMatch) + require.Equal(t, tt.expectedPath, path) + }) + } +} + +// Tests matching behavior when the `FF_ENABLE_DOMAIN_REDIRECT` +// feature flag is enabled. +func Test_matchesDomainRule(t *testing.T) { + t.Setenv(feature.RedirectsPlaceholders.EnvVariable, "true") + t.Setenv(feature.DomainRedirects.EnvVariable, "true") + + tests := map[string]testCaseData{ + "exact_path_match": { + rule: "/foo/ http://test.example.io/bar/", + path: "http://pages.example.io/foo/", + expectMatch: true, + expectedPath: "http://test.example.io/bar/", + }, + "exact_url_match": { + rule: "http://pages.example.io/foo/ http://test.example.io/bar/", + path: "http://pages.example.io/foo/", + expectMatch: true, + expectedPath: "http://test.example.io/bar/", + }, + "single_trailing_slash": { + rule: "http://pages.example.io/foo/ http://test.example.io/bar/", + path: "http://pages.example.io/foo", + expectMatch: true, + expectedPath: "http://test.example.io/bar/", + }, + "ignore_missing_slash": { + rule: "http://pages.example.io/foo http://test.example.io/bar/", + path: "http://pages.example.io/foo/", + expectMatch: true, + expectedPath: "http://test.example.io/bar/", + }, + "no_match": { + rule: "http://pages.example.io/foo http://test.example.io/bar/", + path: "http://pages.example.io/foo/bar", + expectMatch: false, + expectedPath: "", + }, + + // Note: the following 3 cases behave differently when + // placeholders are disabled. See the similar test cases below. + "multiple_trailing_slashes": { + rule: "http://pages.example.io/foo/ http://test.example.io/bar/", + path: "http://pages.example.io/foo//", + expectMatch: true, + expectedPath: "http://test.example.io/bar/", + }, + "multiple_slashes_in_middle": { + rule: "http://pages.example.io/foo/bar http://test.example.io/baz/", + path: "http://pages.example.io/foo//bar", + expectMatch: true, + expectedPath: "http://test.example.io/baz/", + }, + + "domain_redirect_different_protocol": { + rule: "http://pages.example.io/foo/bar https://test.example.io/baz/", + path: "http://pages.example.io/foo//bar", + expectMatch: true, + expectedPath: "https://test.example.io/baz/", + }, + + "splat_match": { + rule: "http://pages.example.io/foo/*/bar http://test.example.io/foo/:splat/qux", + path: "http://pages.example.io/foo/baz/bar", + expectMatch: true, + expectedPath: "http://test.example.io/foo/baz/qux", + }, + "splat_match_multiple_segments": { + rule: "http://pages.example.io/foo/*/bar http://test.example.io/foo/:splat/qux", + path: "http://pages.example.io/foo/hello/world/bar", + expectMatch: true, + expectedPath: "http://test.example.io/foo/hello/world/qux", + }, + "splat_match_ignore_trailing_slash": { + rule: "http://pages.example.io/foo/*/bar http://test.example.io/foo/:splat/qux", + path: "http://pages.example.io/foo/baz/bar/", + expectMatch: true, + expectedPath: "http://test.example.io/foo/baz/qux", + }, + "splat_match_end": { + rule: "http://pages.example.io/foo/* http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/baz/bar", + expectMatch: true, + expectedPath: "http://test.example.io/qux/baz/bar", + }, + "splat_match_end_with_slash": { + rule: "http://pages.example.io/foo/* http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/baz/bar/", + expectMatch: true, + expectedPath: "http://test.example.io/qux/baz/bar/", + }, + "splat_match_beginning": { + rule: "http://pages.example.io/*/baz/bar http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/baz/bar", + expectMatch: true, + expectedPath: "http://test.example.io/qux/foo", + }, + "splat_match_empty_suffix": { + rule: "http://pages.example.io/foo/* http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/", + expectMatch: true, + expectedPath: "http://test.example.io/qux/", + }, + "splat_consumes_trailing_slash": { + rule: "http://pages.example.io/foo/* http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo", + expectMatch: true, + expectedPath: "http://test.example.io/qux/", + }, + "splat_match_empty_prefix": { + rule: "http://pages.example.io/*/foo http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo", + expectMatch: true, + expectedPath: "http://test.example.io/qux/", + }, + "splat_mid_segment": { + rule: "http://pages.example.io/foo*bar http://test.example.io/qux/:splat", + path: "http://pages.example.io/foobazbar", + expectMatch: false, + expectedPath: "", + }, + "splat_mid_segment_no_content": { + rule: "http://pages.example.io/foo*bar http://test.example.io/qux/:splat", + path: "http://pages.example.io/foobar", + expectMatch: false, + expectedPath: "", + }, + "lone_splat": { + rule: "http://pages.example.io/* http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/bar", + expectMatch: true, + expectedPath: "http://test.example.io/qux/foo/bar", + }, + "duplicate_splat_replacements": { + rule: "http://pages.example.io/foo/* http://test.example.io/qux/:splat/:splat", + path: "http://pages.example.io/foo/hello", + expectMatch: true, + expectedPath: "http://test.example.io/qux/hello/hello", + }, + "splat_missing_path_segment_behavior": { + rule: "http://pages.example.io/foo/*/bar http://test.example.io/foo/:splat/qux", + path: "http://pages.example.io/foo/bar", + expectMatch: true, + expectedPath: "http://test.example.io/foo/qux", + }, + "missing_splat_placeholder": { + rule: "http://pages.example.io/foo/ http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/", + expectMatch: true, + expectedPath: "http://test.example.io/qux/", + }, + "placeholder_match": { + rule: "http://pages.example.io/foo/:year/:month/:day/bar http://test.example.io/qux/:year-:month-:day", + path: "http://pages.example.io/foo/2021/08/16/bar", + expectMatch: true, + expectedPath: "http://test.example.io/qux/2021-08-16", + }, + "placeholder_match_end": { + rule: "http://pages.example.io/foo/:placeholder http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foo/bar", + expectMatch: true, + expectedPath: "http://test.example.io/qux/bar", + }, + "placeholder_match_beginning": { + rule: "http://pages.example.io/:placeholder/foo http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/baz/foo", + expectMatch: true, + expectedPath: "http://test.example.io/qux/baz", + }, + "placeholder_no_multiple_segments": { + rule: "http://pages.example.io/foo/:placeholder/bar http://test.example.io/foo/:placeholder/qux", + path: "http://pages.example.io/foo/hello/world/bar", + expectMatch: false, + expectedPath: "", + }, + "placeholder_at_beginning_no_content": { + rule: "http://pages.example.io/:placeholder/foo http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foo", + expectMatch: false, + expectedPath: "", + }, + "placeholder_at_end_no_content": { + rule: "http://pages.example.io/foo/:placeholder http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foo/", + expectMatch: false, + expectedPath: "", + }, + "placeholder_mid_segment_in_from": { + rule: "http://pages.example.io/foo:placeholder http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foorbar", + expectMatch: false, + expectedPath: "", + }, + "placeholder_mid_segment_in_to": { + rule: "http://pages.example.io/foo/:placeholder http://test.example.io/qux/bar:placeholder", + path: "http://pages.example.io/foo/baz", + expectMatch: true, + expectedPath: "http://test.example.io/qux/barbaz", + }, + "placeholder_missing_replacement_with_substring": { + rule: "http://pages.example.io/:foo http://test.example.io/:foobar", + path: "http://pages.example.io/baz", + expectMatch: true, + expectedPath: "http://test.example.io/", + }, + "placeholder_mid_segment_no_content": { + rule: "http://pages.example.io/foo:placeholder http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo", + expectMatch: false, + expectedPath: "", + }, + "placeholder_name_substring": { + rule: "http://pages.example.io/foo/:foo/:foobar http://test.example.io/qux/:foo/:foobar", + path: "http://pages.example.io/foo/baz/quux", + expectMatch: true, + expectedPath: "http://test.example.io/qux/baz/quux", + }, + "lone_placeholder": { + rule: "http://pages.example.io/:placeholder http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foo", + expectMatch: true, + expectedPath: "http://test.example.io/qux/foo", + }, + "duplicate_placeholders": { + rule: "http://pages.example.io/foo/:placeholder/bar/:placeholder/baz http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foo/hello/bar/world/baz", + expectMatch: true, + expectedPath: "http://test.example.io/qux/hello", + }, + "duplicate_placeholder_replacements": { + rule: "http://pages.example.io/foo/:placeholder http://test.example.io/qux/:placeholder/:placeholder", + path: "http://pages.example.io/foo/hello", + expectMatch: true, + expectedPath: "http://test.example.io/qux/hello/hello", + }, + "splat_and_placeholder_named_splat": { + rule: "http://pages.example.io/foo/*/bar/:splat http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/hello/bar/world", + expectMatch: true, + expectedPath: "http://test.example.io/qux/hello", + }, + "placeholder_named_splat_and_splat": { + rule: "http://pages.example.io/foo/:splat/bar/* http://test.example.io/qux/:splat", + path: "http://pages.example.io/foo/hello/bar/world", + expectMatch: true, + expectedPath: "http://test.example.io/qux/hello", + }, + + // Note: These differ slightly from Netlify's matching behavior. + // GitLab replaces _all_ placeholders in the "to" path, even if + // the placeholder doesn't have corresponding match in the "from". + // Netlify only replaces placeholders that appear in the "from". + "missing_placeholder_exact_match": { + rule: "http://pages.example.io/foo/ http://test.example.io/qux/:placeholder", + path: "http://pages.example.io/foo/", + expectMatch: true, + + // Netlify would instead redirect to "/qux/:placeholder" + expectedPath: "http://test.example.io/qux/", + }, + "missing_placeholder_nonexact_match": { + rule: "http://pages.example.io/foo/:placeholderA http://test.example.io/qux/:placeholderB", + path: "http://pages.example.io/foo/bar", + expectMatch: true, + + // Netlify would instead redirect to "/qux/:placeholderB" + expectedPath: "http://test.example.io/qux/", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + rules, err := netlifyRedirects.ParseString(tt.rule) + require.NoError(t, err) + + parsedURL, err := url.Parse(tt.path) + require.NoError(t, err) + + isMatch, path := matchesRule(&rules[0], parsedURL) + require.Equal(t, tt.expectMatch, isMatch) + require.Equal(t, tt.expectedPath, path) + }) + } +} + +// Tests matching behavior when the `FF_ENABLE_DOMAIN_REDIRECT` +// feature flag is not enabled. These tests can be removed when the +// `FF_ENABLE_DOMAIN_REDIRECT` flag is removed. +func Test_matchesDomainRule_DomainRedirect_Disabled(t *testing.T) { + t.Setenv(feature.RedirectsPlaceholders.EnvVariable, "true") + t.Setenv(feature.DomainRedirects.EnvVariable, "false") + + tests := map[string]testCaseData{ + "exact_match": { + rule: "http://pages.example.io/foo/ http://test.example.io/bar/", + path: "http://pages.example.io/foo/", + expectMatch: false, + expectedPath: "", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + rules, err := netlifyRedirects.ParseString(tt.rule) + require.NoError(t, err) + + parsedURL, err := url.Parse(tt.path) + require.NoError(t, err) + + isMatch, path := matchesRule(&rules[0], parsedURL) require.Equal(t, tt.expectMatch, isMatch) require.Equal(t, tt.expectedPath, path) }) |