Files
spinifex/tests/integration/predastore_test.go
T
Julian Sommer 2b4ddad78a chore: drop dead and redundant nolint directives
Removes directives for linters that are not enabled (noctx, exhaustive,
deadcode) and gosec suppressions already covered by config exclusions.
Tags ochrevector QueryResult and Filter with their existing field names
so musttag passes without changing the NATS wire format, and bounds the
RDS window slot conversion instead of suppressing G115.
2026-09-30 13:46:11 +10:00

131 lines
5.0 KiB
Go

//go:build integration
package integration_test
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"net/http"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
testpredastore "github.com/mulgadc/spinifex/tests/fixtures/predastore"
"github.com/stretchr/testify/require"
)
// TestPredastoreObjectLifecycle ports
// tests/e2e/single/predastore_test.go's runPredastoreObjectLifecycle: it
// verifies predastore's user-visible object contract end to end — an object
// is readable and intact after PutObject, absent after DeleteObject (both
// GET and ListObjectsV2 stop returning it), and the store keeps serving
// fresh writes once the delete has landed — against the real, in-process
// predastore daemon (testpredastore.Start), not a tofu-provisioned cluster.
// Not t.Parallel(): testpredastore.Start calls t.Setenv on whichever test
// first starts the shared fixture daemon in this binary, and t.Setenv panics
// if combined with t.Parallel on that same test.
func TestPredastoreObjectLifecycle(t *testing.T) {
fixture := testpredastore.Start(t)
cli := predastoreS3Client(t, fixture)
bucket := fmt.Sprintf("predastore-lifecycle-%d", time.Now().UnixNano())
_, err := cli.CreateBucket(&s3.CreateBucketInput{Bucket: aws.String(bucket)})
require.NoError(t, err, "create bucket %s", bucket)
t.Cleanup(func() {
_, _ = cli.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(bucket)})
})
const key = "lifecycle/object"
payload := bytes.Repeat([]byte("p"), 256<<10) // 256 KiB
// Write, then confirm the object is readable and byte-identical.
_, err = cli.PutObject(&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: bytes.NewReader(payload),
})
require.NoError(t, err, "put %s", key)
got, err := predastoreGetBytes(t, cli, bucket, key)
require.NoError(t, err, "get %s after put", key)
require.True(t, bytes.Equal(got, payload), "get %s after put returned %d bytes, want %d", key, len(got), len(payload))
// Delete, then confirm the object is gone from both GET and List.
_, err = cli.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)})
require.NoError(t, err, "delete %s", key)
_, err = cli.GetObject(&s3.GetObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)})
require.True(t, isPredastoreNoSuchKey(err), "get %s after delete: want NoSuchKey, got %v", key, err)
list, err := cli.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: aws.String(bucket), Prefix: aws.String(key)})
require.NoError(t, err, "list after delete")
require.Lenf(t, list.Contents, 0, "list after delete returned %d objects, want 0", len(list.Contents))
// The store must keep serving once the delete has landed: a fresh write
// round-trips, proving the delete did not wedge the bucket.
const key2 = "lifecycle/after-delete"
t.Cleanup(func() {
_, _ = cli.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(bucket), Key: aws.String(key2)})
})
_, err = cli.PutObject(&s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key2),
Body: bytes.NewReader(payload),
})
require.NoError(t, err, "put %s after delete", key2)
_, err = predastoreGetBytes(t, cli, bucket, key2)
require.NoError(t, err, "get %s after delete", key2)
}
// predastoreS3Client builds an aws-sdk-go v1 S3 client against the shared
// predastore fixture daemon. TLS verification is skipped: the fixture serves
// the S3 API under a self-signed certificate it generates for itself (see
// testpredastore.Start), which no trust store knows about.
func predastoreS3Client(t *testing.T, fixture *testpredastore.Fixture) *s3.S3 {
t.Helper()
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
sess, err := session.NewSession(&aws.Config{
Region: aws.String(fixture.Region),
Endpoint: aws.String("https://" + fixture.Host),
Credentials: credentials.NewStaticCredentials(fixture.AccessKey, fixture.SecretKey, ""),
S3ForcePathStyle: aws.Bool(true),
HTTPClient: httpClient,
})
require.NoError(t, err, "predastore s3 session")
return s3.New(sess)
}
// predastoreGetBytes fetches an object and returns its full body.
func predastoreGetBytes(t *testing.T, cli *s3.S3, bucket, key string) ([]byte, error) {
t.Helper()
out, err := cli.GetObject(&s3.GetObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)})
if err != nil {
return nil, err
}
defer func() { _ = out.Body.Close() }()
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(out.Body); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// isPredastoreNoSuchKey reports whether err is the S3 NoSuchKey error
// predastore returns for a GET on a deleted or missing key.
func isPredastoreNoSuchKey(err error) bool {
var aerr awserr.Error
if errors.As(err, &aerr) {
return aerr.Code() == s3.ErrCodeNoSuchKey
}
return false
}