From 2e7c635fffc044d395feb0f5bf537b82ee03c8ec Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Tue, 25 Apr 2023 23:57:34 -0400 Subject: [PATCH] Test S3 client via dependency injection --- aws/s3.go | 14 ++++++- aws/s3_test.go | 103 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 109 insertions(+), 8 deletions(-) diff --git a/aws/s3.go b/aws/s3.go index e9141884..e3991d9f 100644 --- a/aws/s3.go +++ b/aws/s3.go @@ -11,6 +11,10 @@ import ( "github.com/aws/aws-sdk-go/service/s3/s3manager" ) +type uploader interface { + UploadWithContext(ctx aws.Context, input *s3manager.UploadInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) +} + // S3Client is a client for uploading data to S3. type S3Client struct { region string @@ -18,6 +22,8 @@ type S3Client struct { secretKey string bucket string key string + + uploader uploader // for testing via dependency injection } // NewS3Client returns an instance of an S3Client. @@ -46,7 +52,13 @@ func (s *S3Client) Upload(ctx context.Context, reader io.Reader) error { return fmt.Errorf("failed to create S3 session: %w", err) } - uploader := s3manager.NewUploader(sess) + // If an uploader was not provided, use a real S3 uploader. + var uploader uploader + if s.uploader == nil { + uploader = s3manager.NewUploader(sess) + } else { + uploader = s.uploader + } _, err = uploader.UploadWithContext(ctx, &s3manager.UploadInput{ Bucket: aws.String(s.bucket), diff --git a/aws/s3_test.go b/aws/s3_test.go index 49d64c78..32e0c53f 100644 --- a/aws/s3_test.go +++ b/aws/s3_test.go @@ -1,29 +1,118 @@ package aws -import "testing" +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/s3/s3manager" +) func Test_NewS3Client(t *testing.T) { c := NewS3Client("region1", "access", "secret", "bucket2", "key3") if c.region != "region1" { - t.Errorf("expected region to be %q, got %q", "region1", c.region) + t.Fatalf("expected region to be %q, got %q", "region1", c.region) } if c.accessKey != "access" { - t.Errorf("expected accessKey to be %q, got %q", "access", c.accessKey) + t.Fatalf("expected accessKey to be %q, got %q", "access", c.accessKey) } if c.secretKey != "secret" { - t.Errorf("expected secretKey to be %q, got %q", "secret", c.secretKey) + t.Fatalf("expected secretKey to be %q, got %q", "secret", c.secretKey) } if c.bucket != "bucket2" { - t.Errorf("expected bucket to be %q, got %q", "bucket2", c.bucket) + t.Fatalf("expected bucket to be %q, got %q", "bucket2", c.bucket) } if c.key != "key3" { - t.Errorf("expected key to be %q, got %q", "key3", c.key) + t.Fatalf("expected key to be %q, got %q", "key3", c.key) } } func Test_S3Client_String(t *testing.T) { c := NewS3Client("region1", "access", "secret", "bucket2", "key3") if c.String() != "s3://bucket2/key3" { - t.Errorf("expected String() to be %q, got %q", "s3://bucket2/key3", c.String()) + t.Fatalf("expected String() to be %q, got %q", "s3://bucket2/key3", c.String()) + } +} + +func TestS3ClientUploadOK(t *testing.T) { + region := "us-west-2" + accessKey := "your-access-key" + secretKey := "your-secret-key" + bucket := "your-bucket" + key := "your/key/path" + + mockUploader := &mockUploader{ + uploadFn: func(ctx aws.Context, input *s3manager.UploadInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) { + if *input.Bucket != bucket { + t.Errorf("expected bucket to be %q, got %q", bucket, *input.Bucket) + } + if *input.Key != key { + t.Errorf("expected key to be %q, got %q", key, *input.Key) + } + if input.Body == nil { + t.Errorf("expected body to be non-nil") + } + return &s3manager.UploadOutput{}, nil + }, + } + + client := &S3Client{ + region: region, + accessKey: accessKey, + secretKey: secretKey, + bucket: bucket, + key: key, + uploader: mockUploader, + } + + reader := strings.NewReader("test data") + err := client.Upload(context.Background(), reader) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestS3ClientUploadFail(t *testing.T) { + region := "us-west-2" + accessKey := "your-access-key" + secretKey := "your-secret-key" + bucket := "your-bucket" + key := "your/key/path" + + mockUploader := &mockUploader{ + uploadFn: func(ctx aws.Context, input *s3manager.UploadInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) { + return &s3manager.UploadOutput{}, fmt.Errorf("some error related to S3") + }, + } + + client := &S3Client{ + region: region, + accessKey: accessKey, + secretKey: secretKey, + bucket: bucket, + key: key, + uploader: mockUploader, + } + + reader := strings.NewReader("test data") + err := client.Upload(context.Background(), reader) + if err == nil { + t.Fatal("Expected error, got nil") + } + if !strings.Contains(err.Error(), "some error related to S3") { + t.Fatalf("Expected error to contain %q, got %q", "some error related to S3", err.Error()) + } +} + +type mockUploader struct { + uploadFn func(ctx aws.Context, input *s3manager.UploadInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) +} + +func (m *mockUploader) UploadWithContext(ctx aws.Context, input *s3manager.UploadInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) { + if m.uploadFn != nil { + return m.uploadFn(ctx, input, opts...) } + return &s3manager.UploadOutput{}, nil }