Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions pkg/storage/minio/media_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ import (
"github.com/minio/minio-go/v7/pkg/credentials"
)

// minioBucketAdmin is satisfied by *minio.Client; extracted for tests.
type minioBucketAdmin interface {
BucketExists(ctx context.Context, bucketName string) (bool, error)
MakeBucket(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error
}

func ensureBucketExists(ctx context.Context, client minioBucketAdmin, bucketName, region string) error {
exists, err := client.BucketExists(ctx, bucketName)
if err != nil {
return fmt.Errorf("failed to check if bucket exists: %w", err)
}

if !exists {
err = client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: region})
if err != nil {
return fmt.Errorf("failed to create bucket: %w", err)
}
fmt.Printf("Successfully created bucket %s\n", bucketName)
}
Comment on lines +28 to +34
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Avoid direct fmt.Printf in library code and rely on a logger or remove non-essential logging.

Writing to stdout from this helper can be unexpected for callers and noisy in production. Please either remove this success log or send it through the service’s logger so callers can control log levels and outputs.

Suggested change
if !exists {
err = client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: region})
if err != nil {
return fmt.Errorf("failed to create bucket: %w", err)
}
fmt.Printf("Successfully created bucket %s\n", bucketName)
}
if !exists {
err = client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: region})
if err != nil {
return fmt.Errorf("failed to create bucket: %w", err)
}
}


return nil
}

type MinioMediaStorage struct {
client *minio.Client
bucketName string
Expand Down Expand Up @@ -71,6 +94,10 @@ func NewMinioMediaStorage(
return nil, fmt.Errorf("failed to create MinIO client: %w", err)
}

if err := ensureBucketExists(context.Background(), client, bucketName, region); err != nil {
return nil, err
}

// Try to set bucket policy to allow public access (optional for some providers)
err = setBucketPolicy(client, bucketName)
if err != nil {
Expand Down
56 changes: 56 additions & 0 deletions pkg/storage/minio/media_storage_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build integration

package minio_storage

import (
"os"
"strconv"
"testing"
)

// TestNewMinioMediaStorage_CreatesBucket runs against a real MinIO/S3-compatible endpoint.
// Example:
//
// MINIO_INTEGRATION_TEST=1 \
// MINIO_ENDPOINT=localhost:9000 \
// MINIO_ACCESS_KEY=minioadmin \
// MINIO_SECRET_KEY=minioadmin \
// MINIO_BUCKET=evolution-test-bucket \
// MINIO_REGION=us-east-1 \
// MINIO_USE_SSL=false \
// go test -tags=integration ./pkg/storage/minio/...
func TestNewMinioMediaStorage_CreatesBucket(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Integration test name suggests bucket creation but the test only asserts successful initialization

Right now it only verifies that NewMinioMediaStorage returns a non-nil storage without error. Please either extend it to assert that the bucket actually exists after initialization (e.g. via a MinIO client and BucketExists on the configured bucket), or rename the test (for example to TestNewMinioMediaStorage_InitializesStorage) so the behavior is accurately described.

Suggested change
func TestNewMinioMediaStorage_CreatesBucket(t *testing.T) {
func TestNewMinioMediaStorage_InitializesStorage(t *testing.T) {

if os.Getenv("MINIO_INTEGRATION_TEST") == "" {
t.Skip("set MINIO_INTEGRATION_TEST=1 and MinIO env vars to run")
}

endpoint := os.Getenv("MINIO_ENDPOINT")
access := os.Getenv("MINIO_ACCESS_KEY")
secret := os.Getenv("MINIO_SECRET_KEY")
bucket := os.Getenv("MINIO_BUCKET")
region := os.Getenv("MINIO_REGION")
if region == "" {
region = "us-east-1"
}

useSSL := false
if v := os.Getenv("MINIO_USE_SSL"); v != "" {
var err error
useSSL, err = strconv.ParseBool(v)
if err != nil {
t.Fatalf("MINIO_USE_SSL: %v", err)
}
}

if endpoint == "" || access == "" || secret == "" || bucket == "" {
t.Fatal("MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_BUCKET are required")
}

st, err := NewMinioMediaStorage(endpoint, access, secret, bucket, region, useSSL)
if err != nil {
t.Fatalf("NewMinioMediaStorage: %v", err)
}
if st == nil {
t.Fatal("expected non-nil storage")
}
}
78 changes: 78 additions & 0 deletions pkg/storage/minio/media_storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package minio_storage

import (
"context"
"errors"
"testing"

"github.com/minio/minio-go/v7"
)

type fakeMinioBucket struct {
exists bool
existsErr error
makeErr error
makeCalls int
lastBucket string
lastRegion string
existsCalls int
}

func (f *fakeMinioBucket) BucketExists(ctx context.Context, bucketName string) (bool, error) {
_ = ctx
f.existsCalls++
f.lastBucket = bucketName
return f.exists, f.existsErr
}

func (f *fakeMinioBucket) MakeBucket(ctx context.Context, bucketName string, opts minio.MakeBucketOptions) error {
_ = ctx
f.makeCalls++
f.lastBucket = bucketName
f.lastRegion = opts.Region
return f.makeErr
}

func TestEnsureBucketExists_AlreadyThere(t *testing.T) {
f := &fakeMinioBucket{exists: true}
err := ensureBucketExists(context.Background(), f, "my-bucket", "us-east-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if f.makeCalls != 0 {
t.Fatalf("MakeBucket should not run when bucket exists, calls=%d", f.makeCalls)
}
if f.existsCalls != 1 {
t.Fatalf("expected 1 BucketExists call, got %d", f.existsCalls)
}
}

func TestEnsureBucketExists_CreatesWhenMissing(t *testing.T) {
f := &fakeMinioBucket{exists: false}
err := ensureBucketExists(context.Background(), f, "new-bucket", "eu-west-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if f.makeCalls != 1 {
t.Fatalf("expected 1 MakeBucket call, got %d", f.makeCalls)
}
if f.lastBucket != "new-bucket" || f.lastRegion != "eu-west-1" {
t.Fatalf("unexpected MakeBucket args: bucket=%q region=%q", f.lastBucket, f.lastRegion)
}
}

func TestEnsureBucketExists_BucketExistsError(t *testing.T) {
f := &fakeMinioBucket{existsErr: errors.New("network down")}
err := ensureBucketExists(context.Background(), f, "b", "r")
if err == nil {
t.Fatal("expected error")
}
}

func TestEnsureBucketExists_MakeBucketError(t *testing.T) {
f := &fakeMinioBucket{exists: false, makeErr: errors.New("access denied")}
err := ensureBucketExists(context.Background(), f, "b", "r")
if err == nil {
t.Fatal("expected error")
}
}