mirror of
https://github.com/versity/versitygw.git
synced 2026-07-02 16:54:25 +00:00
f08f76fea4
Integrate x-amz-website-redirect-location across object metadata flows so uploads, copies, multipart creation, HEAD, and GET preserve and return redirect locations, and website hosting applies object-level redirects from the stored value.
311 lines
7.2 KiB
Go
311 lines
7.2 KiB
Go
// Copyright 2026 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package s3response
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
func TestWebsiteConfiguration_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config WebsiteConfiguration
|
|
wantErr bool
|
|
errCode string
|
|
}{
|
|
{
|
|
name: "valid index document only",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
},
|
|
},
|
|
{
|
|
name: "valid index and error document",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
ErrorDocument: &ErrorDocument{Key: "error.html"},
|
|
},
|
|
},
|
|
{
|
|
name: "valid redirect all requests",
|
|
config: WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &RedirectAllRequestsTo{
|
|
HostName: "example.com",
|
|
Protocol: "https",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid routing rules",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Condition: &RoutingRuleCondition{
|
|
KeyPrefixEquals: "docs/",
|
|
},
|
|
Redirect: &Redirect{
|
|
ReplaceKeyPrefixWith: "documents/",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "missing index document",
|
|
config: WebsiteConfiguration{},
|
|
wantErr: true,
|
|
errCode: "MalformedXML",
|
|
},
|
|
{
|
|
name: "empty index suffix",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: ""},
|
|
},
|
|
wantErr: true,
|
|
errCode: "InvalidArgument",
|
|
},
|
|
{
|
|
name: "index suffix with slash",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "dir/index.html"},
|
|
},
|
|
wantErr: true,
|
|
errCode: "InvalidArgument",
|
|
},
|
|
{
|
|
name: "redirect all with index document",
|
|
config: WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &RedirectAllRequestsTo{HostName: "example.com"},
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
},
|
|
wantErr: true,
|
|
errCode: "MalformedXML",
|
|
},
|
|
{
|
|
name: "redirect all with empty hostname",
|
|
config: WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &RedirectAllRequestsTo{HostName: ""},
|
|
},
|
|
wantErr: true,
|
|
errCode: "MalformedXML",
|
|
},
|
|
{
|
|
name: "redirect all with invalid protocol",
|
|
config: WebsiteConfiguration{
|
|
RedirectAllRequestsTo: &RedirectAllRequestsTo{
|
|
HostName: "example.com",
|
|
Protocol: "ftp",
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errCode: "InvalidRequest",
|
|
},
|
|
{
|
|
name: "routing rule with both replace key fields",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Redirect: &Redirect{
|
|
ReplaceKeyWith: "newkey",
|
|
ReplaceKeyPrefixWith: "newprefix/",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errCode: "InvalidRequest",
|
|
},
|
|
{
|
|
name: "routing rule with empty condition",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Condition: &RoutingRuleCondition{},
|
|
Redirect: &Redirect{
|
|
HostName: "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errCode: "MalformedXML",
|
|
},
|
|
{
|
|
name: "routing rule with empty redirect",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Condition: &RoutingRuleCondition{
|
|
KeyPrefixEquals: "docs/",
|
|
},
|
|
Redirect: &Redirect{},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errCode: "MalformedXML",
|
|
},
|
|
{
|
|
name: "routing rule with invalid redirect code",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Redirect: &Redirect{
|
|
HttpRedirectCode: "200",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errCode: "InvalidRequest",
|
|
},
|
|
{
|
|
name: "routing rule with valid redirect code",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Redirect: &Redirect{
|
|
HttpRedirectCode: "301",
|
|
HostName: "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "error document with empty key",
|
|
config: WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
ErrorDocument: &ErrorDocument{Key: ""},
|
|
},
|
|
wantErr: true,
|
|
errCode: "InvalidArgument",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.config.Validate()
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
var apiErr s3err.S3Error
|
|
if !errors.As(err, &apiErr) {
|
|
t.Fatalf("expected S3 error, got %T: %v", err, err)
|
|
}
|
|
if apiErr.BaseError().Code != tt.errCode {
|
|
t.Errorf("expected error code %q, got %q", tt.errCode, apiErr.BaseError().Code)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWebsiteConfiguration_MatchPrefetchRoutingRuleUsesPrefixOnlyRules(t *testing.T) {
|
|
config := WebsiteConfiguration{
|
|
IndexDocument: &IndexDocument{Suffix: "index.html"},
|
|
RoutingRules: []RoutingRule{
|
|
{
|
|
Condition: &RoutingRuleCondition{
|
|
HttpErrorCodeReturnedEquals: "404",
|
|
},
|
|
Redirect: &Redirect{
|
|
HostName: "error.example.com",
|
|
},
|
|
},
|
|
{
|
|
Condition: &RoutingRuleCondition{
|
|
KeyPrefixEquals: "old/",
|
|
HttpErrorCodeReturnedEquals: "404",
|
|
},
|
|
Redirect: &Redirect{
|
|
HostName: "both.example.com",
|
|
},
|
|
},
|
|
{
|
|
Condition: &RoutingRuleCondition{
|
|
KeyPrefixEquals: "old/",
|
|
},
|
|
Redirect: &Redirect{
|
|
HostName: "prefix.example.com",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
rule := config.MatchPrefetchRoutingRule("old/page.html")
|
|
if rule == nil {
|
|
t.Fatal("expected a matching rule, got nil")
|
|
}
|
|
if rule.Redirect.HostName != "prefix.example.com" {
|
|
t.Fatalf("expected prefix-only rule to match, got %q", rule.Redirect.HostName)
|
|
}
|
|
}
|
|
|
|
func TestRoutingRuleCondition_MatchesUsesAndLogic(t *testing.T) {
|
|
condition := RoutingRuleCondition{
|
|
KeyPrefixEquals: "old/",
|
|
HttpErrorCodeReturnedEquals: "404",
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
statusCode int
|
|
want bool
|
|
}{
|
|
{
|
|
name: "both match",
|
|
key: "old/missing.html",
|
|
statusCode: 404,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "prefix only",
|
|
key: "old/existing.html",
|
|
statusCode: 200,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "status only",
|
|
key: "other/missing.html",
|
|
statusCode: 404,
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := condition.Matches(tt.key, tt.statusCode); got != tt.want {
|
|
t.Fatalf("Matches() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|