package example

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"testing"
	"time"

	v2aws "github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/credentials"
	v2s3 "github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/aws/aws-sdk-go/aws"
	v1credentials "github.com/aws/aws-sdk-go/aws/credentials"
	v1signer "github.com/aws/aws-sdk-go/aws/signer/v4"
	v1s3 "github.com/aws/aws-sdk-go/service/s3"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// newS3V2Client creates an AWS SDK v2 S3 client from the test cluster.
func newS3V2Client(cluster *TestCluster) *v2s3.Client {
	return v2s3.New(v2s3.Options{
		Region:       testRegion,
		BaseEndpoint: v2aws.String(cluster.s3Endpoint),
		Credentials:  v2aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(testAccessKey, testSecretKey, "")),
		UsePathStyle: true,
	})
}

func TestGetObjectAttributes(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	cluster, err := startMiniCluster(t)
	require.NoError(t, err)
	defer cluster.Stop()

	t.Run("Basic", func(t *testing.T) {
		testGetObjectAttributesBasic(t, cluster)
	})
	t.Run("MultipartObject", func(t *testing.T) {
		testGetObjectAttributesMultipart(t, cluster)
	})
	t.Run("SelectiveAttributes", func(t *testing.T) {
		testGetObjectAttributesSelective(t, cluster)
	})
	t.Run("InvalidAttribute", func(t *testing.T) {
		testGetObjectAttributesInvalid(t, cluster)
	})
	t.Run("NonExistentObject", func(t *testing.T) {
		testGetObjectAttributesNotFound(t, cluster)
	})
	t.Run("VersionedObject", func(t *testing.T) {
		testGetObjectAttributesVersioned(t, cluster)
	})
	t.Run("ConditionalHeaders", func(t *testing.T) {
		testGetObjectAttributesConditionalHeaders(t, cluster)
	})
	t.Run("VersionedConditionalHeaders", func(t *testing.T) {
		testGetObjectAttributesVersionedConditionalHeaders(t, cluster)
	})
}

func testGetObjectAttributesBasic(t *testing.T, cluster *TestCluster) {
	bucketName := createTestBucket(t, cluster, "test-goa-basic-")
	objectKey := "test-object.txt"
	objectData := "Hello, GetObjectAttributes!"

	_, err := cluster.s3Client.PutObject(&v1s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
		Body:   bytes.NewReader([]byte(objectData)),
	})
	require.NoError(t, err)

	client := newS3V2Client(cluster)
	resp, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String(objectKey),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesEtag,
			types.ObjectAttributesStorageClass,
			types.ObjectAttributesObjectSize,
			types.ObjectAttributesObjectParts,
		},
	})
	require.NoError(t, err)

	// ETag should be present and non-empty
	require.NotNil(t, resp.ETag)
	assert.NotEmpty(t, *resp.ETag)
	assert.False(t, strings.Contains(*resp.ETag, `"`), "ETag in XML body should not have quotes")

	// ObjectSize should match
	require.NotNil(t, resp.ObjectSize)
	assert.Equal(t, int64(len(objectData)), *resp.ObjectSize)

	// StorageClass should be STANDARD (default)
	assert.Equal(t, "STANDARD", string(resp.StorageClass))

	// ObjectParts should be nil for non-multipart objects
	assert.Nil(t, resp.ObjectParts)

	// LastModified header should be present
	assert.NotNil(t, resp.LastModified)

	t.Logf("Basic GetObjectAttributes passed: ETag=%s, Size=%d, StorageClass=%s",
		*resp.ETag, *resp.ObjectSize, resp.StorageClass)
}

func testGetObjectAttributesMultipart(t *testing.T, cluster *TestCluster) {
	bucketName := createTestBucket(t, cluster, "test-goa-mp-")
	objectKey := "test-multipart.bin"

	// Create a 2-part multipart upload
	part1Data := bytes.Repeat([]byte("A"), 5*1024*1024) // 5MB (minimum part size)
	part2Data := bytes.Repeat([]byte("B"), 3*1024*1024) // 3MB

	initResp, err := cluster.s3Client.CreateMultipartUpload(&v1s3.CreateMultipartUploadInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
	})
	require.NoError(t, err)
	uploadID := initResp.UploadId

	part1Resp, err := cluster.s3Client.UploadPart(&v1s3.UploadPartInput{
		Bucket:     aws.String(bucketName),
		Key:        aws.String(objectKey),
		PartNumber: aws.Int64(1),
		UploadId:   uploadID,
		Body:       bytes.NewReader(part1Data),
	})
	require.NoError(t, err)

	part2Resp, err := cluster.s3Client.UploadPart(&v1s3.UploadPartInput{
		Bucket:     aws.String(bucketName),
		Key:        aws.String(objectKey),
		PartNumber: aws.Int64(2),
		UploadId:   uploadID,
		Body:       bytes.NewReader(part2Data),
	})
	require.NoError(t, err)

	_, err = cluster.s3Client.CompleteMultipartUpload(&v1s3.CompleteMultipartUploadInput{
		Bucket:   aws.String(bucketName),
		Key:      aws.String(objectKey),
		UploadId: uploadID,
		MultipartUpload: &v1s3.CompletedMultipartUpload{
			Parts: []*v1s3.CompletedPart{
				{ETag: part1Resp.ETag, PartNumber: aws.Int64(1)},
				{ETag: part2Resp.ETag, PartNumber: aws.Int64(2)},
			},
		},
	})
	require.NoError(t, err)

	// Wait briefly for metadata to settle
	time.Sleep(200 * time.Millisecond)

	client := newS3V2Client(cluster)
	resp, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String(objectKey),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesObjectParts,
			types.ObjectAttributesObjectSize,
		},
	})
	require.NoError(t, err)

	require.NotNil(t, resp.ObjectSize)
	assert.Equal(t, int64(len(part1Data)+len(part2Data)), *resp.ObjectSize)

	require.NotNil(t, resp.ObjectParts, "ObjectParts should be present for multipart objects")
	assert.Equal(t, int32(2), *resp.ObjectParts.TotalPartsCount)
	require.Len(t, resp.ObjectParts.Parts, 2)
	assert.Equal(t, int32(1), *resp.ObjectParts.Parts[0].PartNumber)
	assert.Equal(t, int64(len(part1Data)), *resp.ObjectParts.Parts[0].Size)
	assert.Equal(t, int32(2), *resp.ObjectParts.Parts[1].PartNumber)
	assert.Equal(t, int64(len(part2Data)), *resp.ObjectParts.Parts[1].Size)

	// Test pagination: MaxParts=1
	resp2, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket:   v2aws.String(bucketName),
		Key:      v2aws.String(objectKey),
		MaxParts: v2aws.Int32(1),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesObjectParts,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, resp2.ObjectParts)
	assert.Len(t, resp2.ObjectParts.Parts, 1)
	assert.True(t, *resp2.ObjectParts.IsTruncated)
	assert.Equal(t, int32(2), *resp2.ObjectParts.TotalPartsCount)

	t.Logf("Multipart GetObjectAttributes passed: %d parts, total size %d",
		*resp.ObjectParts.TotalPartsCount, *resp.ObjectSize)
}

func testGetObjectAttributesSelective(t *testing.T, cluster *TestCluster) {
	bucketName := createTestBucket(t, cluster, "test-goa-sel-")
	objectKey := "test-selective.txt"
	objectData := "Selective attributes test"

	_, err := cluster.s3Client.PutObject(&v1s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
		Body:   bytes.NewReader([]byte(objectData)),
	})
	require.NoError(t, err)

	client := newS3V2Client(cluster)

	// Request only ETag
	resp, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String(objectKey),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesEtag,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, resp.ETag)
	assert.NotEmpty(t, *resp.ETag)
	assert.Nil(t, resp.ObjectSize, "ObjectSize should not be present when not requested")
	assert.Empty(t, string(resp.StorageClass), "StorageClass should not be present when not requested")
	assert.Nil(t, resp.ObjectParts, "ObjectParts should not be present when not requested")

	t.Logf("Selective GetObjectAttributes passed: ETag=%s", *resp.ETag)
}

func testGetObjectAttributesInvalid(t *testing.T, cluster *TestCluster) {
	bucketName := createTestBucket(t, cluster, "test-goa-inv-")
	objectKey := "test-object.txt"

	_, err := cluster.s3Client.PutObject(&v1s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
		Body:   bytes.NewReader([]byte("test")),
	})
	require.NoError(t, err)

	// Use raw HTTP to send an invalid attribute name since the SDK validates
	reqURL := fmt.Sprintf("%s/%s/%s?attributes", cluster.s3Endpoint, bucketName, objectKey)
	req, err := http.NewRequest("GET", reqURL, nil)
	require.NoError(t, err)
	req.Header.Set("X-Amz-Object-Attributes", "InvalidAttr")

	signer := v1signer.NewSigner(v1credentials.NewStaticCredentials(testAccessKey, testSecretKey, ""))
	_, err = signer.Sign(req, nil, "s3", testRegion, time.Now())
	require.NoError(t, err)

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer resp.Body.Close()
	io.Copy(io.Discard, resp.Body)

	assert.Equal(t, 400, resp.StatusCode)
	t.Logf("Invalid attribute test passed: got %d", resp.StatusCode)
}

func testGetObjectAttributesNotFound(t *testing.T, cluster *TestCluster) {
	bucketName := createTestBucket(t, cluster, "test-goa-nf-")

	client := newS3V2Client(cluster)
	_, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String("nonexistent-key"),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesEtag,
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "NoSuchKey")

	t.Logf("NotFound GetObjectAttributes passed")
}

func testGetObjectAttributesVersioned(t *testing.T, cluster *TestCluster) {
	client := newS3V2Client(cluster)
	bucketName := createTestBucket(t, cluster, "test-goa-ver-")

	// Enable versioning
	_, err := client.PutBucketVersioning(context.Background(), &v2s3.PutBucketVersioningInput{
		Bucket: v2aws.String(bucketName),
		VersioningConfiguration: &types.VersioningConfiguration{
			Status: types.BucketVersioningStatusEnabled,
		},
	})
	require.NoError(t, err)
	time.Sleep(200 * time.Millisecond)

	// Put two versions of the same object
	v1Data := "version 1 content"
	putResp1, err := client.PutObject(context.Background(), &v2s3.PutObjectInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String("versioned-key"),
		Body:   strings.NewReader(v1Data),
	})
	require.NoError(t, err)
	require.NotNil(t, putResp1.VersionId)
	versionId1 := *putResp1.VersionId

	v2Data := "version 2 content - longer"
	putResp2, err := client.PutObject(context.Background(), &v2s3.PutObjectInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String("versioned-key"),
		Body:   strings.NewReader(v2Data),
	})
	require.NoError(t, err)
	require.NotNil(t, putResp2.VersionId)
	versionId2 := *putResp2.VersionId

	assert.NotEqual(t, versionId1, versionId2, "versions should differ")

	// GetObjectAttributes for latest version (v2)
	resp, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String("versioned-key"),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesObjectSize,
			types.ObjectAttributesEtag,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, resp.ObjectSize)
	assert.Equal(t, int64(len(v2Data)), *resp.ObjectSize)
	require.NotNil(t, resp.VersionId)
	assert.Equal(t, versionId2, *resp.VersionId)

	// GetObjectAttributes for specific older version (v1)
	resp1, err := client.GetObjectAttributes(context.Background(), &v2s3.GetObjectAttributesInput{
		Bucket:    v2aws.String(bucketName),
		Key:       v2aws.String("versioned-key"),
		VersionId: v2aws.String(versionId1),
		ObjectAttributes: []types.ObjectAttributes{
			types.ObjectAttributesObjectSize,
			types.ObjectAttributesEtag,
		},
	})
	require.NoError(t, err)
	require.NotNil(t, resp1.ObjectSize)
	assert.Equal(t, int64(len(v1Data)), *resp1.ObjectSize)
	require.NotNil(t, resp1.VersionId)
	assert.Equal(t, versionId1, *resp1.VersionId)

	t.Logf("Versioned GetObjectAttributes passed: v1 size=%d (id=%s), v2 size=%d (id=%s)",
		*resp1.ObjectSize, versionId1, *resp.ObjectSize, versionId2)
}

// signedGetObjectAttributes creates a signed GET request for ?attributes with custom headers.
func signedGetObjectAttributes(t *testing.T, cluster *TestCluster, bucketName, objectKey string, extraHeaders map[string]string) *http.Response {
	reqURL := fmt.Sprintf("%s/%s/%s?attributes", cluster.s3Endpoint, bucketName, objectKey)
	req, err := http.NewRequest("GET", reqURL, nil)
	require.NoError(t, err)
	req.Header.Set("X-Amz-Object-Attributes", "ETag,ObjectSize")
	for k, v := range extraHeaders {
		req.Header.Set(k, v)
	}
	signer := v1signer.NewSigner(v1credentials.NewStaticCredentials(testAccessKey, testSecretKey, ""))
	_, err = signer.Sign(req, nil, "s3", testRegion, time.Now())
	require.NoError(t, err)
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	require.NoError(t, err)
	return resp
}

func testGetObjectAttributesConditionalHeaders(t *testing.T, cluster *TestCluster) {
	bucketName := createTestBucket(t, cluster, "test-goa-cond-")
	objectKey := "cond-test.txt"

	_, err := cluster.s3Client.PutObject(&v1s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
		Body:   bytes.NewReader([]byte("conditional headers test")),
	})
	require.NoError(t, err)

	// Get the ETag and Last-Modified for the object
	headResp, err := cluster.s3Client.HeadObject(&v1s3.HeadObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
	})
	require.NoError(t, err)
	etag := aws.StringValue(headResp.ETag)
	lastModified := headResp.LastModified
	require.NotNil(t, lastModified)

	pastDate := lastModified.Add(-1 * time.Hour).UTC().Format(http.TimeFormat)
	futureDate := lastModified.Add(1 * time.Hour).UTC().Format(http.TimeFormat)

	// RFC 7232: If-Match true + If-Unmodified-Since false => 200 OK
	// If-Unmodified-Since is ignored when If-Match is present
	t.Run("IfMatch_true_IfUnmodifiedSince_false", func(t *testing.T) {
		resp := signedGetObjectAttributes(t, cluster, bucketName, objectKey, map[string]string{
			"If-Match":            etag,
			"If-Unmodified-Since": pastDate, // object was modified after this => false
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 200, resp.StatusCode,
			"If-Match=true should return 200 even when If-Unmodified-Since=false (RFC 7232 Section 3.4)")
	})

	// RFC 7232: If-None-Match false + If-Modified-Since true => 304 Not Modified
	// If-Modified-Since is ignored when If-None-Match is present
	t.Run("IfNoneMatch_false_IfModifiedSince_true", func(t *testing.T) {
		resp := signedGetObjectAttributes(t, cluster, bucketName, objectKey, map[string]string{
			"If-None-Match":     etag,
			"If-Modified-Since": pastDate, // object was modified after this => true
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 304, resp.StatusCode,
			"If-None-Match=false (ETag match) should return 304 even when If-Modified-Since=true (RFC 7232 Section 3.3)")
	})

	// If-Match succeeds, If-Unmodified-Since also succeeds => 200
	t.Run("IfMatch_true_IfUnmodifiedSince_true", func(t *testing.T) {
		resp := signedGetObjectAttributes(t, cluster, bucketName, objectKey, map[string]string{
			"If-Match":            etag,
			"If-Unmodified-Since": futureDate,
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 200, resp.StatusCode)
	})

	// If-None-Match passes (ETag differs), If-Modified-Since ignored => 200
	// Per RFC 7232, If-Modified-Since is ignored when If-None-Match is present
	t.Run("IfNoneMatch_true_IfModifiedSince_ignored", func(t *testing.T) {
		resp := signedGetObjectAttributes(t, cluster, bucketName, objectKey, map[string]string{
			"If-None-Match":     `"nonexistent-etag"`,
			"If-Modified-Since": futureDate, // would fail alone, but is ignored
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 200, resp.StatusCode,
			"If-None-Match=true means If-Modified-Since is ignored, should return 200 (RFC 7232 Section 3.3)")
	})

	// If-Match fails => 412 regardless of If-Unmodified-Since
	t.Run("IfMatch_false", func(t *testing.T) {
		resp := signedGetObjectAttributes(t, cluster, bucketName, objectKey, map[string]string{
			"If-Match":            `"wrong-etag"`,
			"If-Unmodified-Since": futureDate,
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 412, resp.StatusCode)
	})

	t.Logf("Conditional headers tests passed")
}

// signedGetObjectAttributesVersioned creates a signed GET request for ?attributes&versionId=... with custom headers.
func signedGetObjectAttributesVersioned(t *testing.T, cluster *TestCluster, bucketName, objectKey, versionId string, extraHeaders map[string]string) *http.Response {
	reqURL := fmt.Sprintf("%s/%s/%s?attributes&versionId=%s", cluster.s3Endpoint, bucketName, objectKey, versionId)
	req, err := http.NewRequest("GET", reqURL, nil)
	require.NoError(t, err)
	req.Header.Set("X-Amz-Object-Attributes", "ETag,ObjectSize")
	for k, v := range extraHeaders {
		req.Header.Set(k, v)
	}
	signer := v1signer.NewSigner(v1credentials.NewStaticCredentials(testAccessKey, testSecretKey, ""))
	_, err = signer.Sign(req, nil, "s3", testRegion, time.Now())
	require.NoError(t, err)
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	require.NoError(t, err)
	return resp
}

func testGetObjectAttributesVersionedConditionalHeaders(t *testing.T, cluster *TestCluster) {
	client := newS3V2Client(cluster)
	bucketName := createTestBucket(t, cluster, "test-goa-vcond-")

	// Enable versioning
	_, err := client.PutBucketVersioning(context.Background(), &v2s3.PutBucketVersioningInput{
		Bucket: v2aws.String(bucketName),
		VersioningConfiguration: &types.VersioningConfiguration{
			Status: types.BucketVersioningStatusEnabled,
		},
	})
	require.NoError(t, err)
	time.Sleep(200 * time.Millisecond)

	// Put two versions with different content (different ETags)
	v1Data := "version 1 - original"
	putResp1, err := client.PutObject(context.Background(), &v2s3.PutObjectInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String("vcond-key"),
		Body:   strings.NewReader(v1Data),
	})
	require.NoError(t, err)
	require.NotNil(t, putResp1.VersionId)
	vid1 := *putResp1.VersionId

	v2Data := "version 2 - updated content"
	putResp2, err := client.PutObject(context.Background(), &v2s3.PutObjectInput{
		Bucket: v2aws.String(bucketName),
		Key:    v2aws.String("vcond-key"),
		Body:   strings.NewReader(v2Data),
	})
	require.NoError(t, err)
	require.NotNil(t, putResp2.VersionId)
	vid2 := *putResp2.VersionId

	// Get ETags for each version
	headV1, err := client.HeadObject(context.Background(), &v2s3.HeadObjectInput{
		Bucket:    v2aws.String(bucketName),
		Key:       v2aws.String("vcond-key"),
		VersionId: v2aws.String(vid1),
	})
	require.NoError(t, err)
	etagV1 := *headV1.ETag

	headV2, err := client.HeadObject(context.Background(), &v2s3.HeadObjectInput{
		Bucket:    v2aws.String(bucketName),
		Key:       v2aws.String("vcond-key"),
		VersionId: v2aws.String(vid2),
	})
	require.NoError(t, err)
	etagV2 := *headV2.ETag
	require.NotEqual(t, etagV1, etagV2, "versions should have different ETags")

	// If-Match with v1's ETag + versionId=v1 => 200
	// Before the fix, this would fail with 412 because conditional headers
	// were evaluated against the latest version (v2) whose ETag differs
	t.Run("IfMatch_v1_etag_versionId_v1", func(t *testing.T) {
		resp := signedGetObjectAttributesVersioned(t, cluster, bucketName, "vcond-key", vid1, map[string]string{
			"If-Match": etagV1,
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 200, resp.StatusCode,
			"If-Match with v1 ETag targeting versionId=v1 should return 200")
	})

	// If-Match with v2's ETag + versionId=v1 => 412
	// The ETag doesn't match v1, so this should fail
	t.Run("IfMatch_v2_etag_versionId_v1", func(t *testing.T) {
		resp := signedGetObjectAttributesVersioned(t, cluster, bucketName, "vcond-key", vid1, map[string]string{
			"If-Match": etagV2,
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 412, resp.StatusCode,
			"If-Match with v2 ETag targeting versionId=v1 should return 412")
	})

	// If-None-Match with v1's ETag + versionId=v1 => 304
	t.Run("IfNoneMatch_v1_etag_versionId_v1", func(t *testing.T) {
		resp := signedGetObjectAttributesVersioned(t, cluster, bucketName, "vcond-key", vid1, map[string]string{
			"If-None-Match": etagV1,
		})
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		assert.Equal(t, 304, resp.StatusCode,
			"If-None-Match with v1 ETag targeting versionId=v1 should return 304")
	})

	t.Logf("Versioned conditional headers tests passed: vid1=%s, vid2=%s", vid1, vid2)
}
