mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 10:52:04 +00:00
test(integration): migrate predastore object lifecycle off live guests
runPredastoreObjectLifecycle only needs a real predastore daemon, not a tofu-provisioned cluster: it now runs against tests/fixtures/predastore's in-process fixture, covering the same create/put/get/delete/list contract.
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
//go:build e2e
|
||||
|
||||
package single
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/mulgadc/spinifex/tests/e2e/harness"
|
||||
)
|
||||
|
||||
// runPredastoreObjectLifecycle drives predastore's user-visible object contract
|
||||
// (write → read → delete → gone → still-serving) against the deployed S3
|
||||
// endpoint. Predastore shares the host the AWS gateway binds, so resolve it the
|
||||
// same way harness.NewAWSClient does.
|
||||
func runPredastoreObjectLifecycle(t *testing.T, fix *Fixture) {
|
||||
host := "127.0.0.1"
|
||||
if len(fix.Env.ServiceIPs) > 0 {
|
||||
host = fix.Env.ServiceIPs[0]
|
||||
}
|
||||
harness.AssertPredastoreObjectLifecycle(context.Background(), t, host)
|
||||
}
|
||||
@@ -30,13 +30,6 @@ func TestAccountScoping(t *testing.T) {
|
||||
runAccountScoping(t, requireSingleNodeFixture(t))
|
||||
}
|
||||
|
||||
// TestPredastoreObjectLifecycle owns a dedicated bucket and never touches the
|
||||
// singleton VM or default VPC/SG, so it is parallel-safe.
|
||||
func TestPredastoreObjectLifecycle(t *testing.T) {
|
||||
t.Parallel()
|
||||
runPredastoreObjectLifecycle(t, requireSingleNodeFixture(t))
|
||||
}
|
||||
|
||||
func TestVPCSubnetE2E(t *testing.T) {
|
||||
t.Parallel()
|
||||
runVPCSubnetE2E(t, requireSingleNodeFixture(t))
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//go:build integration
|
||||
|
||||
package integration
|
||||
|
||||
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's
|
||||
// self-signed certificate is trusted process-wide for quicclient/the s3db
|
||||
// REST client (see testpredastore.Start), but a plain s3.S3 built here has
|
||||
// its own independent HTTP transport.
|
||||
func predastoreS3Client(t *testing.T, fixture *testpredastore.Fixture) *s3.S3 {
|
||||
t.Helper()
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // self-signed cert generated by this same fixture, localhost-only
|
||||
},
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user