You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
7.5 KiB
258 lines
7.5 KiB
package s3api
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// mockAccountManager implements AccountManager for testing
|
|
type mockAccountManager struct {
|
|
accounts map[string]string
|
|
}
|
|
|
|
func (m *mockAccountManager) GetAccountNameById(id string) string {
|
|
if name, exists := m.accounts[id]; exists {
|
|
return name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *mockAccountManager) GetAccountIdByEmail(email string) string {
|
|
return ""
|
|
}
|
|
|
|
func TestNewListEntryOwnerDisplayName(t *testing.T) {
|
|
// Create mock IAM with test accounts
|
|
iam := &mockAccountManager{
|
|
accounts: map[string]string{
|
|
"testid": "M. Tester",
|
|
"userid123": "John Doe",
|
|
},
|
|
}
|
|
|
|
// Create test entry with owner metadata
|
|
entry := &filer_pb.Entry{
|
|
Name: "test-object",
|
|
Attributes: &filer_pb.FuseAttributes{
|
|
Mtime: time.Now().Unix(),
|
|
FileSize: 1024,
|
|
},
|
|
Extended: map[string][]byte{
|
|
s3_constants.ExtAmzOwnerKey: []byte("testid"),
|
|
},
|
|
}
|
|
|
|
// Test that display name is correctly looked up from IAM
|
|
listEntry := newListEntry(entry, "", "dir", "test-object", "/buckets/test/", true, false, false, iam)
|
|
|
|
assert.NotNil(t, listEntry.Owner, "Owner should be set when fetchOwner is true")
|
|
assert.Equal(t, "testid", listEntry.Owner.ID, "Owner ID should match stored owner")
|
|
assert.Equal(t, "M. Tester", listEntry.Owner.DisplayName, "Display name should be looked up from IAM")
|
|
|
|
// Test with owner that doesn't exist in IAM (should fallback to ID)
|
|
entry.Extended[s3_constants.ExtAmzOwnerKey] = []byte("unknown-user")
|
|
listEntry = newListEntry(entry, "", "dir", "test-object", "/buckets/test/", true, false, false, iam)
|
|
|
|
assert.Equal(t, "unknown-user", listEntry.Owner.ID, "Owner ID should match stored owner")
|
|
assert.Equal(t, "unknown-user", listEntry.Owner.DisplayName, "Display name should fallback to ID when not found in IAM")
|
|
|
|
// Test with no owner metadata (should use anonymous)
|
|
entry.Extended = make(map[string][]byte)
|
|
listEntry = newListEntry(entry, "", "dir", "test-object", "/buckets/test/", true, false, false, iam)
|
|
|
|
assert.Equal(t, s3_constants.AccountAnonymousId, listEntry.Owner.ID, "Should use anonymous ID when no owner metadata")
|
|
assert.Equal(t, "anonymous", listEntry.Owner.DisplayName, "Should use anonymous display name when no owner metadata")
|
|
|
|
// Test with fetchOwner false (should not set owner)
|
|
listEntry = newListEntry(entry, "", "dir", "test-object", "/buckets/test/", false, false, false, iam)
|
|
|
|
assert.Nil(t, listEntry.Owner, "Owner should not be set when fetchOwner is false")
|
|
}
|
|
|
|
func TestRemoveDuplicateSlashes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
expectedResult string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
path: "",
|
|
expectedResult: "",
|
|
},
|
|
{
|
|
name: "slash",
|
|
path: "/",
|
|
expectedResult: "/",
|
|
},
|
|
{
|
|
name: "object",
|
|
path: "object",
|
|
expectedResult: "object",
|
|
},
|
|
{
|
|
name: "correct path",
|
|
path: "/path/to/object",
|
|
expectedResult: "/path/to/object",
|
|
},
|
|
{
|
|
name: "path with duplicates",
|
|
path: "///path//to/object//",
|
|
expectedResult: "/path/to/object/",
|
|
},
|
|
}
|
|
|
|
for _, tst := range tests {
|
|
t.Run(tst.name, func(t *testing.T) {
|
|
obj := removeDuplicateSlashes(tst.path)
|
|
assert.Equal(t, tst.expectedResult, obj)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestS3ApiServer_toFilerUrl(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args string
|
|
want string
|
|
}{
|
|
{
|
|
"simple",
|
|
"/uploads/eaf10b3b-3b3a-4dcd-92a7-edf2a512276e/67b8b9bf-7cca-4cb6-9b34-22fcb4d6e27d/Bildschirmfoto 2022-09-19 um 21.38.37.png",
|
|
"/uploads/eaf10b3b-3b3a-4dcd-92a7-edf2a512276e/67b8b9bf-7cca-4cb6-9b34-22fcb4d6e27d/Bildschirmfoto%202022-09-19%20um%2021.38.37.png",
|
|
},
|
|
{
|
|
"double prefix",
|
|
"//uploads/t.png",
|
|
"/uploads/t.png",
|
|
},
|
|
{
|
|
"triple prefix",
|
|
"///uploads/t.png",
|
|
"/uploads/t.png",
|
|
},
|
|
{
|
|
"empty prefix",
|
|
"uploads/t.png",
|
|
"/uploads/t.png",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equalf(t, tt.want, urlEscapeObject(tt.args), "clean %v", tt.args)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPartNumberWithRangeHeader(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
partStartOffset int64 // Part's start offset in the object
|
|
partEndOffset int64 // Part's end offset in the object
|
|
clientRangeHeader string
|
|
expectedStart int64 // Expected absolute start offset
|
|
expectedEnd int64 // Expected absolute end offset
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "No client range - full part",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999,
|
|
clientRangeHeader: "",
|
|
expectedStart: 1000,
|
|
expectedEnd: 1999,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Range within part - start and end",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999, // Part size: 1000 bytes
|
|
clientRangeHeader: "bytes=0-99",
|
|
expectedStart: 1000, // 1000 + 0
|
|
expectedEnd: 1099, // 1000 + 99
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Range within part - start to end",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999,
|
|
clientRangeHeader: "bytes=100-",
|
|
expectedStart: 1100, // 1000 + 100
|
|
expectedEnd: 1999, // 1000 + 999 (end of part)
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Range suffix - last 100 bytes",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999, // Part size: 1000 bytes
|
|
clientRangeHeader: "bytes=-100",
|
|
expectedStart: 1900, // 1000 + (1000 - 100)
|
|
expectedEnd: 1999, // 1000 + 999
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Range suffix larger than part",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999, // Part size: 1000 bytes
|
|
clientRangeHeader: "bytes=-2000",
|
|
expectedStart: 1000, // Start of part (clamped)
|
|
expectedEnd: 1999, // End of part
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Range start beyond part size",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999,
|
|
clientRangeHeader: "bytes=1000-1100",
|
|
expectedStart: 0,
|
|
expectedEnd: 0,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "Range end clamped to part size",
|
|
partStartOffset: 1000,
|
|
partEndOffset: 1999,
|
|
clientRangeHeader: "bytes=0-2000",
|
|
expectedStart: 1000, // 1000 + 0
|
|
expectedEnd: 1999, // Clamped to end of part
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Single byte range at start",
|
|
partStartOffset: 5000,
|
|
partEndOffset: 9999, // Part size: 5000 bytes
|
|
clientRangeHeader: "bytes=0-0",
|
|
expectedStart: 5000,
|
|
expectedEnd: 5000,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Single byte range in middle",
|
|
partStartOffset: 5000,
|
|
partEndOffset: 9999,
|
|
clientRangeHeader: "bytes=100-100",
|
|
expectedStart: 5100,
|
|
expectedEnd: 5100,
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Test the actual range adjustment logic from GetObjectHandler
|
|
startOffset, endOffset, err := adjustRangeForPart(tt.partStartOffset, tt.partEndOffset, tt.clientRangeHeader)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, "Expected error for range %s", tt.clientRangeHeader)
|
|
} else {
|
|
assert.NoError(t, err, "Unexpected error for range %s: %v", tt.clientRangeHeader, err)
|
|
assert.Equal(t, tt.expectedStart, startOffset, "Start offset mismatch")
|
|
assert.Equal(t, tt.expectedEnd, endOffset, "End offset mismatch")
|
|
}
|
|
})
|
|
}
|
|
}
|