124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
// Package minio provides a real S3-compatible service backed by a MinIO
|
|
// Testcontainer for integration tests.
|
|
package minio
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
tcminio "github.com/testcontainers/testcontainers-go/modules/minio"
|
|
|
|
storepb "github.com/usememos/memos/proto/gen/store"
|
|
)
|
|
|
|
const image = "minio/minio:RELEASE.2024-01-16T16-07-38Z"
|
|
|
|
// Server is an S3-compatible MinIO container with an AWS SDK client for
|
|
// arranging and inspecting test objects.
|
|
type Server struct {
|
|
Endpoint string
|
|
Region string
|
|
Username string
|
|
Password string
|
|
|
|
client *awss3.Client
|
|
}
|
|
|
|
// New starts a MinIO container and creates the requested buckets.
|
|
func New(t *testing.T, buckets ...string) *Server {
|
|
t.Helper()
|
|
if testing.Short() {
|
|
t.Skip("skipping MinIO integration test in short mode")
|
|
}
|
|
if os.Getenv("SKIP_CONTAINER_TESTS") == "1" {
|
|
t.Skip("skipping MinIO integration test (SKIP_CONTAINER_TESTS=1)")
|
|
}
|
|
testcontainers.SkipIfProviderIsNotHealthy(t)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
defer cancel()
|
|
container, err := tcminio.Run(
|
|
ctx,
|
|
image,
|
|
tcminio.WithUsername("memos-test-access"),
|
|
tcminio.WithPassword("memos-test-secret"),
|
|
)
|
|
require.NoError(t, err)
|
|
testcontainers.CleanupContainer(t, container)
|
|
|
|
connection, err := container.ConnectionString(ctx)
|
|
require.NoError(t, err)
|
|
server := &Server{
|
|
Endpoint: "http://" + connection,
|
|
Region: "us-east-1",
|
|
Username: container.Username,
|
|
Password: container.Password,
|
|
}
|
|
|
|
config, err := awsconfig.LoadDefaultConfig(
|
|
ctx,
|
|
awsconfig.WithRegion(server.Region),
|
|
awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(server.Username, server.Password, "")),
|
|
)
|
|
require.NoError(t, err)
|
|
server.client = awss3.NewFromConfig(config, func(options *awss3.Options) {
|
|
options.BaseEndpoint = aws.String(server.Endpoint)
|
|
options.UsePathStyle = true
|
|
})
|
|
for _, bucket := range buckets {
|
|
_, err := server.client.CreateBucket(ctx, &awss3.CreateBucketInput{Bucket: aws.String(bucket)})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return server
|
|
}
|
|
|
|
// Config returns a storage configuration connected to the requested bucket.
|
|
func (s *Server) Config(bucket string) *storepb.StorageS3Config {
|
|
return &storepb.StorageS3Config{
|
|
AccessKeyId: s.Username,
|
|
AccessKeySecret: s.Password,
|
|
Endpoint: s.Endpoint,
|
|
Region: s.Region,
|
|
Bucket: bucket,
|
|
UsePathStyle: true,
|
|
}
|
|
}
|
|
|
|
// PutObject uploads an object directly for arranging a test fixture.
|
|
func (s *Server) PutObject(bucket, key, contentType string, content []byte) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
_, err := s.client.PutObject(ctx, &awss3.PutObjectInput{
|
|
Bucket: aws.String(bucket),
|
|
Key: aws.String(key),
|
|
ContentType: aws.String(contentType),
|
|
Body: bytes.NewReader(content),
|
|
})
|
|
return err
|
|
}
|
|
|
|
// GetObject reads an object directly for asserting test results.
|
|
func (s *Server) GetObject(bucket, key string) ([]byte, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
object, err := s.client.GetObject(ctx, &awss3.GetObjectInput{
|
|
Bucket: aws.String(bucket),
|
|
Key: aws.String(key),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer object.Body.Close()
|
|
return io.ReadAll(object.Body)
|
|
}
|