Files
go-omni/storage/s3/s3_test.go

145 lines
4.6 KiB
Go

package s3
import (
"context"
"errors"
"fmt"
"git.yuntuo.hk/k2safe/go-omni/internal/storagefixture"
"git.yuntuo.hk/k2safe/go-omni/storage"
"git.yuntuo.hk/k2safe/go-omni/storage/storagetest"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
)
func TestS3ProtocolAndR2Config(t *testing.T) {
server := httptest.NewTLSServer(storagefixture.Handler())
defer server.Close()
creds := credentials.NewStaticV4("fixtureAccess", "fixtureSecret", "")
s, err := Open(Config{Endpoint: strings.TrimPrefix(server.URL, "https://"), Region: "us-east-1", Bucket: "testbucket", Secure: true, MaxBytes: 64, Credentials: creds, Transport: server.Client().Transport})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if _, err = s.Get(ctx, "open-config-missing"); !errors.Is(err, storage.ErrNotFound) {
t.Fatal("configured client did not reach protocol fixture", err)
}
// Malformed-body cases intentionally cause SDK errors. Disable retries on
// this test-owned client so randomized backoff cannot exhaust the shared
// contract deadline. Open above still checks the production client config.
client, err := minio.New(strings.TrimPrefix(server.URL, "https://"), &minio.Options{
Creds: creds, Region: "us-east-1", Secure: true, Transport: server.Client().Transport, MaxRetries: 1,
})
if err != nil {
t.Fatal(err)
}
contractStore, err := New(client, "testbucket", 64)
if err != nil {
t.Fatal(err)
}
storagetest.Run(t, contractStore)
cfg, err := R2Config(strings.Repeat("a", 32), "bucket", creds)
if err != nil {
t.Fatal(err)
}
r2, err := Open(cfg)
if err != nil {
t.Fatal(err)
}
u, err := r2.PresignGet(context.Background(), "path/file", time.Minute)
if err != nil || u.Host != strings.Repeat("a", 32)+".r2.cloudflarestorage.com" || u.Query().Get("X-Amz-Signature") == "" || !strings.Contains(u.Query().Get("X-Amz-Credential"), "/auto/s3/") {
t.Fatal("R2 presign settings", err)
}
}
func TestS3Integration(t *testing.T) {
address := os.Getenv("GO_OMNI_S3_TEST_ADDR")
if address == "" {
t.Skip("requires isolated MinIO")
}
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
ready := false
for n := 0; n < 100; n++ {
req, _ := http.NewRequestWithContext(ctx, "GET", "http://"+address+"/minio/health/ready", nil)
r, err := http.DefaultClient.Do(req)
if err == nil {
r.Body.Close()
if r.StatusCode == 200 {
ready = true
break
}
}
if ctx.Err() != nil {
break
}
time.Sleep(100 * time.Millisecond)
}
if !ready {
t.Fatal("isolated S3 server did not start")
}
client, err := minio.New(address, &minio.Options{Creds: credentials.NewStaticV4("goOmniTest", "goOmniTestSecret", ""), Region: "us-east-1"})
if err != nil {
t.Fatal(err)
}
bucket := fmt.Sprintf("test-%d", time.Now().UnixNano())
if err = client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}); err != nil {
t.Fatal(err)
}
defer client.RemoveBucket(context.Background(), bucket)
uploads, _ := New(client, bucket, 6<<20)
httpClient := &http.Client{Timeout: 10 * time.Second}
storagetest.RunUploads(t, uploads, httpClient)
link, err := uploads.PresignPut(ctx, "constrained", 5, "text/plain", time.Minute)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct{ body, contentType string }{{"longer", "text/plain"}, {"hello", "image/png"}} {
req, _ := http.NewRequestWithContext(ctx, link.Method, link.URL, strings.NewReader(tc.body))
req.Header = link.Headers.Clone()
req.Header.Set("Content-Type", tc.contentType)
response, requestErr := httpClient.Do(req)
if requestErr != nil {
t.Fatal(requestErr)
}
response.Body.Close()
if response.StatusCode != 403 {
t.Fatalf("tampered upload constraints accepted: %d", response.StatusCode)
}
}
s, _ := New(client, bucket, 64)
storagetest.Run(t, s)
s.Put(ctx, "signed", strings.NewReader("hello"), 5, "text/plain")
defer s.Delete(context.Background(), "signed")
u, err := s.PresignGet(ctx, "signed", time.Minute)
if err != nil {
t.Fatal(err)
}
response, err := http.Get(u.String())
if err != nil {
t.Fatal(err)
}
raw, _ := io.ReadAll(response.Body)
response.Body.Close()
if response.StatusCode != 200 || string(raw) != "hello" {
t.Fatal(response.StatusCode, string(raw))
}
q := u.Query()
q.Set("X-Amz-Signature", strings.Repeat("0", 64))
u.RawQuery = q.Encode()
response, err = http.Get(u.String())
if err != nil {
t.Fatal(err)
}
response.Body.Close()
if response.StatusCode != 403 {
t.Fatal("tampered signature", response.StatusCode)
}
}