test(predastore): add real predastore fixture for integration tests

Extract predastore_integration_test.go's inline daemon startup into a
reusable testutil/predastore package so tests/integration can point
DaemonLite-style wiring at a real S3-compatible backend instead of an
unreachable host. Kept as its own leaf package rather than folding into
the shared testutil package: it transitively imports predastore's
quicclient, whose package-level connection pool starts a permanent
goroutine at init that would trip goleak checks in any of the ~80
other packages that import testutil for unrelated NATS helpers.
This commit is contained in:
joshsiv-mulga
2026-07-20 16:56:57 +10:00
parent f89c63f84d
commit b670ebca49
2 changed files with 382 additions and 285 deletions
@@ -1,28 +1,16 @@
package predastore
// Integration tests for predastore: start a real daemon and verify S3 bucket,
// auth, and file operations using AWS SDK Go v1.
// Package predastore_test contains integration tests for predastore: start a
// real daemon (via testpredastore.Start) and verify S3 bucket, auth, and
// file operations using AWS SDK Go v1.
package predastore_test
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
"testing"
"time"
@@ -30,273 +18,23 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/mulgadc/predastore/quic/quicclient"
testpredastore "github.com/mulgadc/spinifex/spinifex/testutil/predastore"
"github.com/stretchr/testify/assert"
)
// Test credentials from config.
// Test credentials from config, mirrored from the shared fixture
// (testpredastore.AccessKey etc.) so this file has a single source of truth
// for the daemon's seeded auth and default buckets.
const (
validAccessKey = "AKIAIOSFODNN7EXAMPLE"
validSecretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
validAccessKey = testpredastore.AccessKey
validSecretKey = testpredastore.SecretKey
invalidAccessKey = "INVALIDACCESSKEY"
invalidSecretKey = "InvalidSecretKey123"
testBucket = "test-bucket"
publicBucket = "public-bucket"
testRegion = "us-east-1"
testPort = 18443
testHost = "127.0.0.1"
testBucket = testpredastore.DefaultBucket
publicBucket = testpredastore.DefaultBucket2
testRegion = testpredastore.Region
)
// Global server instance for integration tests.
var (
serverStarted bool
serverStartMutex sync.Mutex
sharedTestDir string
sharedConfig *Config
)
// generateTestCertificate generates a self-signed TLS certificate for testing.
// The CA must be injected via quicclient.SetDefaultRootCAs before any dial.
func generateTestCertificate(certPath, keyPath string) (*x509.CertPool, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
notBefore := time.Now()
notAfter := notBefore.Add(24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Predastore Test"},
CommonName: "localhost",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"localhost"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, err
}
certOut, err := os.Create(certPath)
if err != nil {
return nil, err
}
defer certOut.Close()
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return nil, err
}
keyOut, err := os.Create(keyPath)
if err != nil {
return nil, err
}
defer keyOut.Close()
privBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, err
}
if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}); err != nil {
return nil, err
}
parsed, err := x509.ParseCertificate(derBytes)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AddCert(parsed)
return pool, nil
}
// startPredastoreServer starts the predastore server once for all tests.
func startPredastoreServer(t *testing.T) *Config {
serverStartMutex.Lock()
defer serverStartMutex.Unlock()
if serverStarted {
// Server already running, return shared config
return sharedConfig
}
// Create persistent test environment (not using t.TempDir() for shared server)
testDir, err := os.MkdirTemp("", "predastore-integration-*") //nolint:usetesting // shared server outlives individual test
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
sharedTestDir = testDir
// Create TLS cert directory
certDir := filepath.Join(testDir, "certs")
err = os.MkdirAll(certDir, 0755)
if err != nil {
t.Fatalf("Failed to create cert dir: %v", err)
}
certPath := filepath.Join(certDir, "test.crt")
keyPath := filepath.Join(certDir, "test.key")
caPool, err := generateTestCertificate(certPath, keyPath)
if err != nil {
t.Fatalf("Failed to generate certificate: %v", err)
}
// Inject the ephemeral cert for the QUIC client (s3d → shard nodes).
quicclient.SetDefaultRootCAs(caPool)
// SSL_CERT_FILE injects the cert for the s3db REST client's OS trust store
// (sync.Once-cached, must be set before the first dial).
t.Setenv("SSL_CERT_FILE", certPath)
// Predastore mandates a 32-byte master key at mode 0600 (rejected otherwise
// by internal/keyfile.Load).
encryptionKeyPath := filepath.Join(testDir, "encryption.key")
testEncryptionKey := make([]byte, 32)
if _, err := rand.Read(testEncryptionKey); err != nil {
t.Fatalf("Failed to generate test encryption key: %v", err)
}
if err := os.WriteFile(encryptionKeyPath, testEncryptionKey, 0600); err != nil {
t.Fatalf("Failed to write test encryption key: %v", err)
}
// Five nodes trigger dev-mode: all QUIC shards start as local goroutines.
configPath := filepath.Join(testDir, "predastore_test.toml")
configContent := `version = "1.0"
region = "us-east-1"
host = "127.0.0.1"
port = 18443
debug = false
disable_logging = false
base_path = "` + testDir + `/"
[rs]
data = 3
parity = 2
[[nodes]]
id = 1
host = "127.0.0.1"
port = 19991
path = "store/node-1/"
[[nodes]]
id = 2
host = "127.0.0.1"
port = 19992
path = "store/node-2/"
[[nodes]]
id = 3
host = "127.0.0.1"
port = 19993
path = "store/node-3/"
[[nodes]]
id = 4
host = "127.0.0.1"
port = 19994
path = "store/node-4/"
[[nodes]]
id = 5
host = "127.0.0.1"
port = 19995
path = "store/node-5/"
[[auth]]
access_key_id = "AKIAIOSFODNN7EXAMPLE"
secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
account_id = "123456789012"
`
err = os.WriteFile(configPath, []byte(configContent), 0644)
if err != nil {
t.Fatalf("Failed to write config: %v", err)
}
cfg := &Config{
ConfigPath: configPath,
Port: 18443,
Host: "127.0.0.1",
Debug: false,
BasePath: testDir,
TlsCert: certPath,
TlsKey: keyPath,
EncryptionKeyFile: encryptionKeyPath,
// NodeID -1 triggers dev mode: all QUIC nodes run in-process.
NodeID: -1,
}
sharedConfig = cfg
// Start server in background
go func() {
svc, _ := New(cfg)
svc.Start()
}()
// Wait for server to be ready
if !waitForServer(10 * time.Second) {
t.Fatal("Failed to start predastore server")
}
// Create the test buckets via the S3 API so they're registered in
// distributed globalState (config buckets aren't visible to ListBuckets).
setupClient := createS3Client(validAccessKey, validSecretKey)
for _, bucket := range []string{testBucket, publicBucket} {
if _, err := setupClient.CreateBucket(&s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil {
t.Fatalf("Failed to create bucket %s: %v", bucket, err)
}
}
serverStarted = true
t.Logf("Predastore server started successfully")
t.Logf("Test directory: %s", testDir)
t.Logf("Created buckets: %s, %s", testBucket, publicBucket)
// NOTE: Cleanup will happen automatically when test process exits
// Don't use t.Cleanup() as it runs after each test, not at the end
return cfg
}
// waitForServer waits for the predastore server to be ready.
func waitForServer(timeout time.Duration) bool {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Timeout: 1 * time.Second,
}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
resp, err := client.Get("https://" + net.JoinHostPort(testHost, strconv.Itoa(testPort)) + "/")
if err == nil {
resp.Body.Close()
// Give a bit more time for config to load
time.Sleep(500 * time.Millisecond)
return true
}
time.Sleep(100 * time.Millisecond)
}
return false
}
// createS3Client creates an AWS S3 client for testing.
func createS3Client(accessKey, secretKey string) *s3.S3 {
tr := &http.Transport{
@@ -306,7 +44,7 @@ func createS3Client(accessKey, secretKey string) *s3.S3 {
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(testRegion),
Endpoint: aws.String("https://" + net.JoinHostPort(testHost, strconv.Itoa(testPort))),
Endpoint: aws.String("https://" + testpredastore.Host),
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
S3ForcePathStyle: aws.Bool(true),
DisableSSL: aws.Bool(false),
@@ -323,7 +61,7 @@ func TestIntegration_BucketListing(t *testing.T) {
}
// Start server (only starts once)
startPredastoreServer(t)
testpredastore.Start(t)
// Create S3 client
client := createS3Client(validAccessKey, validSecretKey)
@@ -351,7 +89,7 @@ func TestIntegration_Authentication_Valid(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
// Create client with valid credentials
client := createS3Client(validAccessKey, validSecretKey)
@@ -371,7 +109,7 @@ func TestIntegration_Authentication_Invalid(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
// Create client with invalid credentials
client := createS3Client(invalidAccessKey, invalidSecretKey)
@@ -396,7 +134,7 @@ func TestIntegration_FileUpload_TestBucket(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
client := createS3Client(validAccessKey, validSecretKey)
@@ -438,7 +176,7 @@ func TestIntegration_FileUpload_PublicBucket(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
client := createS3Client(validAccessKey, validSecretKey)
@@ -479,7 +217,7 @@ func TestIntegration_FileUpload_PublicBucket2(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
client := createS3Client(validAccessKey, validSecretKey)
@@ -525,7 +263,7 @@ func TestIntegration_FileOperations_Complete(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
client := createS3Client(validAccessKey, validSecretKey)
@@ -617,7 +355,7 @@ func TestIntegration_MultipleFiles(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
client := createS3Client(validAccessKey, validSecretKey)
@@ -666,7 +404,7 @@ func TestIntegration_LargeFile(t *testing.T) {
t.Skip("Skipping integration test in short mode")
}
startPredastoreServer(t)
testpredastore.Start(t)
client := createS3Client(validAccessKey, validSecretKey)
+359
View File
@@ -0,0 +1,359 @@
// Package predastore starts a real predastore daemon for tests that need to
// exercise an actual S3-compatible backend rather than a mock. It is
// deliberately its own leaf package (not folded into spinifex/testutil,
// which is imported by most of the module's test files for lightweight NATS
// helpers): this package transitively imports predastore/quic/quicclient,
// whose package-level DefaultPool starts a permanent cleanup goroutine at
// init time. Folding this file into the shared testutil package would pull
// that goroutine into every test binary that imports testutil, and trip up
// any unrelated goroutine-leak check (go.uber.org/goleak) running in that
// same binary. Only callers that actually need a real predastore should pay
// for this import.
package predastore
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"sync"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/mulgadc/predastore/quic/quicclient"
predastoreserver "github.com/mulgadc/predastore/s3"
)
// Fixed connection details for the shared predastore fixture daemon started
// by Start. NodeID -1 (dev mode) runs every QUIC shard node in-process, so
// these values never need to vary per caller or per test run.
const (
Host = "127.0.0.1:18443"
Region = "us-east-1"
// AccessKey/SecretKey are the well-known AWS SDK example credentials
// (docs.aws.amazon.com/IAM/latest/UserGuide), used only to authenticate
// against this ephemeral, localhost-only test daemon — not a real secret.
AccessKey = "AKIAIOSFODNN7EXAMPLE" //nolint:gosec // well-known AWS SDK example key, test-only daemon
SecretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" //nolint:gosec // well-known AWS SDK example secret, test-only daemon
// DefaultBucket and DefaultBucket2 are pre-created by Start itself,
// matching the bucket set services/predastore's own integration tests
// were written against. Callers that need a different bucket should
// EnsureBucket one of their own against the fixture (see
// objectstore.NewS3ObjectStoreFromConfig) rather than reuse these —
// they're an implementation detail of this package's own tests, not a
// general-purpose bucket.
DefaultBucket = "test-bucket"
DefaultBucket2 = "public-bucket"
testHost = "127.0.0.1"
testPort = 18443
)
// Fixture describes a running predastore daemon ready for real
// S3/viperblock clients: a reachable endpoint, TLS material already trusted
// process-wide, and its default buckets created.
type Fixture struct {
Host string
Region string
AccessKey string
SecretKey string
}
// Package-level singleton: one predastore daemon per test binary (per Go
// package under test), amortising its startup cost across every test that
// calls Start instead of paying it per-test. Guarded by a mutex rather than
// sync.Once because a failed first attempt should not wedge every later
// caller into replaying a t.Fatalf from a different test's *testing.T.
var (
mu sync.Mutex
started bool
fixture *Fixture
)
// Start starts a real predastore daemon the first time it's called within a
// test binary and returns connection details for it; every later call in
// the same process returns the already-running fixture. The daemon
// deliberately outlives any individual test — its temp dir uses
// os.MkdirTemp rather than t.TempDir() so a finished test's cleanup can't
// delete files the shared daemon still has open — and is left running until
// the test process exits.
func Start(t *testing.T) *Fixture {
t.Helper()
mu.Lock()
defer mu.Unlock()
if started {
return fixture
}
testDir, err := os.MkdirTemp("", "predastore-fixture-*") //nolint:usetesting // shared daemon outlives individual tests
if err != nil {
t.Fatalf("predastore fixture: create temp dir: %v", err)
}
certDir := filepath.Join(testDir, "certs")
if err := os.MkdirAll(certDir, 0755); err != nil { //nolint:gosec // ephemeral test-only temp dir, not sensitive
t.Fatalf("predastore fixture: create cert dir: %v", err)
}
certPath := filepath.Join(certDir, "test.crt")
keyPath := filepath.Join(certDir, "test.key")
caPool, err := generateCertificate(certPath, keyPath)
if err != nil {
t.Fatalf("predastore fixture: generate certificate: %v", err)
}
// Inject the ephemeral cert for the QUIC client (s3d -> shard nodes).
quicclient.SetDefaultRootCAs(caPool)
// SSL_CERT_FILE injects the cert for the s3db REST client's OS trust
// store (sync.Once-cached there, so it must be set before that client's
// first dial). Scoped to this call via t.Setenv, but since the daemon and
// its first client dial both happen inside this same call, the cache is
// warm before this function returns — later tests in the process don't
// need the env var set again.
t.Setenv("SSL_CERT_FILE", certPath)
// Predastore mandates a 32-byte master key at mode 0600 (rejected
// otherwise by internal/keyfile.Load).
encryptionKeyPath := filepath.Join(testDir, "encryption.key")
testEncryptionKey := make([]byte, 32)
if _, err := rand.Read(testEncryptionKey); err != nil {
t.Fatalf("predastore fixture: generate encryption key: %v", err)
}
if err := os.WriteFile(encryptionKeyPath, testEncryptionKey, 0600); err != nil {
t.Fatalf("predastore fixture: write encryption key: %v", err)
}
// Five nodes trigger dev-mode: all QUIC shards start as local goroutines.
configPath := filepath.Join(testDir, "predastore_test.toml")
configContent := `version = "1.0"
region = "us-east-1"
host = "127.0.0.1"
port = 18443
debug = false
disable_logging = false
base_path = "` + testDir + `/"
[rs]
data = 3
parity = 2
[[nodes]]
id = 1
host = "127.0.0.1"
port = 19991
path = "store/node-1/"
[[nodes]]
id = 2
host = "127.0.0.1"
port = 19992
path = "store/node-2/"
[[nodes]]
id = 3
host = "127.0.0.1"
port = 19993
path = "store/node-3/"
[[nodes]]
id = 4
host = "127.0.0.1"
port = 19994
path = "store/node-4/"
[[nodes]]
id = 5
host = "127.0.0.1"
port = 19995
path = "store/node-5/"
[[auth]]
access_key_id = "` + AccessKey + `"
secret_access_key = "` + SecretKey + `"
account_id = "123456789012"
`
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { //nolint:gosec // ephemeral test-only config, contains no real secrets
t.Fatalf("predastore fixture: write config: %v", err)
}
// Built directly against predastore/s3 rather than spinifex's own
// services/predastore wrapper: that wrapper pulls in spinifex/utils,
// and spinifex/utils' own tests import spinifex/testutil, which would be
// an import cycle if this package were reached from there. The
// wrapper's only other job — pidfile bookkeeping and signal-triggered
// shutdown — is irrelevant for a fixture daemon that lives for the
// whole test binary anyway.
server, err := predastoreserver.NewServer(
predastoreserver.WithConfigPath(configPath),
predastoreserver.WithAddress(testHost, testPort),
predastoreserver.WithTLS(certPath, keyPath),
predastoreserver.WithBasePath(testDir),
predastoreserver.WithDebug(false),
predastoreserver.WithNodeID(-1),
predastoreserver.WithPprof(false, ""),
predastoreserver.WithEncryptionKeyFile(encryptionKeyPath),
)
if err != nil {
t.Fatalf("predastore fixture: create server: %v", err)
}
if err := server.ListenAndServeAsync(); err != nil {
t.Fatalf("predastore fixture: start server: %v", err)
}
if !waitForReady(10 * time.Second) {
t.Fatal("predastore fixture: server did not become ready")
}
// Create the default buckets via the S3 API so they're registered in
// distributed globalState (config buckets aren't visible to ListBuckets).
setupClient := s3Client(AccessKey, SecretKey)
for _, bucket := range []string{DefaultBucket, DefaultBucket2} {
if _, err := setupClient.CreateBucket(&s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil {
t.Fatalf("predastore fixture: create bucket %s: %v", bucket, err)
}
}
fixture = &Fixture{
Host: Host,
Region: Region,
AccessKey: AccessKey,
SecretKey: SecretKey,
}
started = true
t.Logf("predastore fixture started, test dir: %s", testDir)
return fixture
}
// generateCertificate generates a self-signed TLS certificate for the
// fixture daemon and returns a CertPool containing it, for injection into
// whichever client trust store a caller needs (see quicclient.SetDefaultRootCAs
// and the SSL_CERT_FILE handling in Start).
func generateCertificate(certPath, keyPath string) (*x509.CertPool, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
notBefore := time.Now()
notAfter := notBefore.Add(24 * time.Hour)
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Predastore Test"},
CommonName: "localhost",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"localhost"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, err
}
certOut, err := os.Create(certPath)
if err != nil {
return nil, err
}
defer certOut.Close()
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return nil, err
}
keyOut, err := os.Create(keyPath)
if err != nil {
return nil, err
}
defer keyOut.Close()
privBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, err
}
if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}); err != nil {
return nil, err
}
parsed, err := x509.ParseCertificate(derBytes)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AddCert(parsed)
return pool, nil
}
// waitForReady polls the fixture daemon's HTTPS endpoint until it accepts
// connections or timeout elapses.
func waitForReady(timeout time.Duration) bool {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // self-signed cert generated by this same fixture, localhost-only
},
Timeout: 1 * time.Second,
}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
resp, err := client.Get("https://" + net.JoinHostPort(testHost, strconv.Itoa(testPort)) + "/")
if err == nil {
resp.Body.Close()
// Give a bit more time for config to load.
time.Sleep(500 * time.Millisecond)
return true
}
time.Sleep(100 * time.Millisecond)
}
return false
}
// s3Client creates an AWS S3 client against the fixture daemon for fixture
// setup only (bucket creation); test bodies build their own clients against
// whatever bucket/credentials their scenario needs.
func s3Client(accessKey, secretKey string) *s3.S3 {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // self-signed cert generated by this same fixture, localhost-only
}
httpClient := &http.Client{Transport: tr}
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(Region),
Endpoint: aws.String("https://" + Host),
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
S3ForcePathStyle: aws.Bool(true),
DisableSSL: aws.Bool(false),
HTTPClient: httpClient,
}))
return s3.New(sess)
}