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.
 
 
 
 
 
 

274 lines
9.2 KiB

package s3api
import (
"context"
"fmt"
"testing"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestListObjectVersionsDelimiter tests delimiter functionality in ListObjectVersions
func TestListObjectVersionsDelimiter(t *testing.T) {
client := getS3Client(t)
bucketName := getNewBucketName()
// Create bucket and enable versioning
createBucket(t, client, bucketName)
defer deleteBucket(t, client, bucketName)
enableVersioning(t, client, bucketName)
// Create test structure:
// - folder1/file1.txt
// - folder1/file2.txt
// - folder2/file3.txt
// - root-file.txt
testObjects := []string{
"folder1/file1.txt",
"folder1/file2.txt",
"folder2/file3.txt",
"root-file.txt",
}
for _, key := range testObjects {
putObject(t, client, bucketName, key, fmt.Sprintf("Content of %s", key))
}
t.Run("Delimiter groups folders correctly", func(t *testing.T) {
// List with delimiter='/' and no prefix
// Should return: root-file.txt and CommonPrefixes: folder1/, folder2/
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Delimiter: aws.String("/"),
})
require.NoError(t, err)
// Extract keys and prefixes
versionKeys := make([]string, 0)
for _, v := range resp.Versions {
versionKeys = append(versionKeys, *v.Key)
}
prefixValues := make([]string, 0)
for _, p := range resp.CommonPrefixes {
prefixValues = append(prefixValues, *p.Prefix)
}
// Verify results
assert.Contains(t, versionKeys, "root-file.txt", "Should include root-file.txt")
assert.Contains(t, prefixValues, "folder1/", "Should include folder1/ prefix")
assert.Contains(t, prefixValues, "folder2/", "Should include folder2/ prefix")
assert.NotContains(t, versionKeys, "folder1/file1.txt", "folder1/file1.txt should be grouped under folder1/")
assert.NotContains(t, versionKeys, "folder2/file3.txt", "folder2/file3.txt should be grouped under folder2/")
t.Logf("✓ Versions: %v", versionKeys)
t.Logf("✓ CommonPrefixes: %v", prefixValues)
})
t.Run("Prefix filtering with delimiter", func(t *testing.T) {
// List with delimiter='/' and prefix='folder1/'
// Should return: folder1/file1.txt, folder1/file2.txt
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Prefix: aws.String("folder1/"),
Delimiter: aws.String("/"),
})
require.NoError(t, err)
versionKeys := make([]string, 0)
for _, v := range resp.Versions {
versionKeys = append(versionKeys, *v.Key)
}
assert.Len(t, versionKeys, 2, "Should have 2 versions")
assert.Contains(t, versionKeys, "folder1/file1.txt")
assert.Contains(t, versionKeys, "folder1/file2.txt")
assert.Empty(t, resp.CommonPrefixes, "Should have no common prefixes")
t.Logf("✓ Prefix filtering works: %v", versionKeys)
})
t.Run("Without delimiter returns all versions", func(t *testing.T) {
// List without delimiter - should return all files
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
})
require.NoError(t, err)
assert.Len(t, resp.Versions, 4, "Should have all 4 versions")
assert.Empty(t, resp.CommonPrefixes, "Should have no common prefixes without delimiter")
t.Logf("✓ Without delimiter returns all %d versions", len(resp.Versions))
})
}
// TestListObjectVersionsDelimiterTruncation tests MaxKeys with delimiter
func TestListObjectVersionsDelimiterTruncation(t *testing.T) {
client := getS3Client(t)
bucketName := getNewBucketName()
// Create bucket and enable versioning
createBucket(t, client, bucketName)
defer deleteBucket(t, client, bucketName)
enableVersioning(t, client, bucketName)
// Create multiple folders to test truncation
for i := 0; i < 5; i++ {
key := fmt.Sprintf("folder%d/file.txt", i)
putObject(t, client, bucketName, key, fmt.Sprintf("Content %d", i))
}
// Add a root file
putObject(t, client, bucketName, "root.txt", "Root content")
t.Run("MaxKeys limits total items", func(t *testing.T) {
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Delimiter: aws.String("/"),
MaxKeys: aws.Int32(3),
})
require.NoError(t, err)
assert.NotNil(t, resp.IsTruncated, "IsTruncated should not be nil")
assert.True(t, *resp.IsTruncated, "Should be truncated")
assert.NotNil(t, resp.NextKeyMarker, "Should have NextKeyMarker for pagination")
count := len(resp.Versions) + len(resp.CommonPrefixes)
t.Logf("✓ MaxKeys truncation: %d items (versions: %d, prefixes: %d)",
count, len(resp.Versions), len(resp.CommonPrefixes))
})
t.Run("Pagination with delimiter", func(t *testing.T) {
// Collect all items through pagination
allKeys := make([]string, 0)
allPrefixes := make([]string, 0)
var keyMarker *string
var versionMarker *string
for {
input := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Delimiter: aws.String("/"),
MaxKeys: aws.Int32(2),
}
if keyMarker != nil {
input.KeyMarker = keyMarker
}
if versionMarker != nil {
input.VersionIdMarker = versionMarker
}
resp, err := client.ListObjectVersions(context.TODO(), input)
require.NoError(t, err)
// Collect versions
for _, v := range resp.Versions {
allKeys = append(allKeys, *v.Key)
}
// Collect prefixes
for _, p := range resp.CommonPrefixes {
allPrefixes = append(allPrefixes, *p.Prefix)
}
require.NotNil(t, resp.IsTruncated, "IsTruncated should not be nil")
if !*resp.IsTruncated {
break
}
keyMarker = resp.NextKeyMarker
versionMarker = resp.NextVersionIdMarker
}
// Should have collected all items
itemsCount := len(allKeys) + len(allPrefixes)
assert.GreaterOrEqual(t, itemsCount, 6, "Should collect all items through pagination")
t.Logf("✓ Pagination collected %d total items (keys: %d, prefixes: %d)",
itemsCount, len(allKeys), len(allPrefixes))
})
t.Run("CommonPrefixes are filtered by keyMarker (exclusive)", func(t *testing.T) {
// List with keyMarker that should skip some prefixes
// We have folder0/, folder1/, folder2/, folder3/, folder4/
// Setting keyMarker to "folder2/" should return folder3/, folder4/ and root.txt (if it's > folder2/)
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Delimiter: aws.String("/"),
KeyMarker: aws.String("folder2/"),
})
require.NoError(t, err)
prefixValues := make([]string, 0)
for _, p := range resp.CommonPrefixes {
prefixValues = append(prefixValues, *p.Prefix)
}
assert.NotContains(t, prefixValues, "folder0/", "Should skip folder0/")
assert.NotContains(t, prefixValues, "folder1/", "Should skip folder1/")
assert.NotContains(t, prefixValues, "folder2/", "Should skip folder2/ (exclusive marker)")
assert.Contains(t, prefixValues, "folder3/", "Should include folder3/")
assert.Contains(t, prefixValues, "folder4/", "Should include folder4/")
t.Logf("✓ CommonPrefixes filtered by keyMarker: %v", prefixValues)
})
}
// TestListObjectVersionsDelimiterWithMultipleVersions tests delimiter with multiple versions of same object
func TestListObjectVersionsDelimiterWithMultipleVersions(t *testing.T) {
client := getS3Client(t)
bucketName := getNewBucketName()
// Create bucket and enable versioning
createBucket(t, client, bucketName)
defer deleteBucket(t, client, bucketName)
enableVersioning(t, client, bucketName)
// Create multiple versions of objects in different folders
for i := 1; i <= 3; i++ {
putObject(t, client, bucketName, "folder1/file.txt", fmt.Sprintf("Version %d", i))
putObject(t, client, bucketName, "folder2/file.txt", fmt.Sprintf("Version %d", i))
}
t.Run("Delimiter groups all versions under prefix", func(t *testing.T) {
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Delimiter: aws.String("/"),
})
require.NoError(t, err)
// Should have no versions at root level, only common prefixes
assert.Empty(t, resp.Versions, "Should have no versions at root")
assert.Len(t, resp.CommonPrefixes, 2, "Should have 2 common prefixes")
prefixValues := make([]string, 0)
for _, p := range resp.CommonPrefixes {
prefixValues = append(prefixValues, *p.Prefix)
}
assert.Contains(t, prefixValues, "folder1/")
assert.Contains(t, prefixValues, "folder2/")
t.Logf("✓ All versions grouped under prefixes: %v", prefixValues)
})
t.Run("Listing within prefix shows all versions", func(t *testing.T) {
resp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
Prefix: aws.String("folder1/"),
Delimiter: aws.String("/"),
})
require.NoError(t, err)
assert.Len(t, resp.Versions, 3, "Should have 3 versions of folder1/file.txt")
assert.Empty(t, resp.CommonPrefixes, "Should have no common prefixes")
// Verify all versions are for the same key
for _, v := range resp.Versions {
assert.Equal(t, "folder1/file.txt", *v.Key)
}
t.Logf("✓ Found %d versions within prefix", len(resp.Versions))
})
}