aboutsummaryrefslogtreecommitdiff
path: root/test/s3/sse/s3_range_headers_test.go
blob: e54004eb7acf7ef876f1c999715e0986468ea001 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package sse_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"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"
)

// TestPlainObjectRangeAndHeadHeaders ensures non-SSE objects advertise correct
// Content-Length and Content-Range information for both HEAD and ranged GETs.
func TestPlainObjectRangeAndHeadHeaders(t *testing.T) {
	ctx := context.Background()

	client, err := createS3Client(ctx, defaultConfig)
	require.NoError(t, err, "failed to create S3 client")

	bucketName, err := createTestBucket(ctx, client, defaultConfig.BucketPrefix+"range-plain-")
	require.NoError(t, err, "failed to create test bucket")
	defer cleanupTestBucket(ctx, client, bucketName)

	// SeaweedFS S3 auto-chunks uploads at 8MiB (see chunkSize in putToFiler).
	// Using 16MiB ensures at least two chunks without stressing CI resources.
	const chunkSize = 8 * 1024 * 1024
	const objectSize = 2 * chunkSize
	objectKey := "plain-range-validation"
	testData := generateTestData(objectSize)

	_, err = client.PutObject(ctx, &s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
		Body:   bytes.NewReader(testData),
	})
	require.NoError(t, err, "failed to upload test object")

	t.Run("HeadObject reports accurate Content-Length", func(t *testing.T) {
		resp, err := client.HeadObject(ctx, &s3.HeadObjectInput{
			Bucket: aws.String(bucketName),
			Key:    aws.String(objectKey),
		})
		require.NoError(t, err, "HeadObject request failed")
		assert.Equal(t, int64(objectSize), resp.ContentLength, "Content-Length mismatch on HEAD")
		assert.Equal(t, "bytes", aws.ToString(resp.AcceptRanges), "Accept-Ranges should advertise bytes")
	})

	t.Run("Range request across chunk boundary", func(t *testing.T) {
		// Test range that spans an 8MiB chunk boundary (chunkSize - 1KB to chunkSize + 3KB)
		rangeStart := int64(chunkSize - 1024)
		rangeEnd := rangeStart + 4096 - 1
		rangeHeader := fmt.Sprintf("bytes=%d-%d", rangeStart, rangeEnd)

		resp, err := client.GetObject(ctx, &s3.GetObjectInput{
			Bucket: aws.String(bucketName),
			Key:    aws.String(objectKey),
			Range:  aws.String(rangeHeader),
		})
		require.NoError(t, err, "GetObject range request failed")
		defer resp.Body.Close()

		expectedLen := rangeEnd - rangeStart + 1
		assert.Equal(t, expectedLen, resp.ContentLength, "Content-Length must match requested range size")
		assert.Equal(t,
			fmt.Sprintf("bytes %d-%d/%d", rangeStart, rangeEnd, objectSize),
			aws.ToString(resp.ContentRange),
			"Content-Range header mismatch")

		body, err := io.ReadAll(resp.Body)
		require.NoError(t, err, "failed to read range response body")
		assert.Equal(t, int(expectedLen), len(body), "actual bytes read mismatch")
		assert.Equal(t, testData[rangeStart:rangeEnd+1], body, "range payload mismatch")
	})

	t.Run("Suffix range request", func(t *testing.T) {
		const suffixSize = 2048
		resp, err := client.GetObject(ctx, &s3.GetObjectInput{
			Bucket: aws.String(bucketName),
			Key:    aws.String(objectKey),
			Range:  aws.String(fmt.Sprintf("bytes=-%d", suffixSize)),
		})
		require.NoError(t, err, "GetObject suffix range request failed")
		defer resp.Body.Close()

		expectedStart := int64(objectSize - suffixSize)
		expectedEnd := int64(objectSize - 1)
		expectedLen := expectedEnd - expectedStart + 1

		assert.Equal(t, expectedLen, resp.ContentLength, "suffix Content-Length mismatch")
		assert.Equal(t,
			fmt.Sprintf("bytes %d-%d/%d", expectedStart, expectedEnd, objectSize),
			aws.ToString(resp.ContentRange),
			"suffix Content-Range mismatch")

		body, err := io.ReadAll(resp.Body)
		require.NoError(t, err, "failed to read suffix range response body")
		assert.Equal(t, int(expectedLen), len(body), "suffix range byte count mismatch")
		assert.Equal(t, testData[expectedStart:expectedEnd+1], body, "suffix range payload mismatch")
	})
}