package objectstore import ( "errors" "os" "strings" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "go.uber.org/zap" "spark-server/internal/pkg/errs" ) func TestParseEndpoint(t *testing.T) { host, secure, err := parseEndpoint("http://47.236.66.9:9000", true) if err != nil || host != "47.236.66.9:9000" || secure { t.Fatalf("%s %v %v", host, secure, err) } host, secure, err = parseEndpoint("47.236.66.9:9000", false) if err != nil || host != "47.236.66.9:9000" || secure { t.Fatalf("plain %s %v %v", host, secure, err) } host, secure, err = parseEndpoint("https://fs.example.com", false) if err != nil || host != "fs.example.com" || !secure { t.Fatalf("https %s %v %v", host, secure, err) } } func TestOpenEmpty(t *testing.T) { s, err := Open(&Options{}) if err != nil || s.Ready() { t.Fatalf("ready=%v err=%v", s.Ready(), err) } } func TestPresignLocal(t *testing.T) { client := newS3Client(defaultRegion, "http://127.0.0.1:9000", "ak", "sk") s := &Store{client: client, presign: s3.NewPresignClient(client), bucket: "spark"} u, err := s.PresignPut(t.Context(), "chat/a.jpg", "image/jpeg", 1024, time.Hour) if err != nil { t.Fatal(err) } if !strings.Contains(u, "127.0.0.1:9000/spark/chat/a.jpg") { t.Fatalf("%s", u) } signed := strings.ToLower(u) if !strings.Contains(signed, "content-length") || !strings.Contains(signed, "if-none-match") { t.Fatalf("unsigned size/overwrite headers: %s", u) } g, err := s.PresignGet(t.Context(), "perm/avatar/1.jpg", time.Minute) if err != nil { t.Fatal(err) } if !strings.Contains(g, "perm/avatar/1.jpg") { t.Fatalf("%s", g) } } func TestPresignPutRejectsZeroSize(t *testing.T) { client := newS3Client(defaultRegion, "http://127.0.0.1:9000", "ak", "sk") s := &Store{client: client, presign: s3.NewPresignClient(client), bucket: "spark"} if _, err := s.PresignPut(t.Context(), "chat/a.jpg", "image/jpeg", 0, time.Hour); !errors.Is(err, errs.ErrInvalidArgument) { t.Fatalf("%v", err) } } func TestPresignPublicEndpoint(t *testing.T) { internal, public, err := newClients(&Options{ Endpoint: "http://rustfs:9000", PublicEndpoint: "https://sparkr.wyrs365.com/", AccessKey: "ak", SecretKey: "sk", }) if err != nil { t.Fatal(err) } s := &Store{client: internal, presign: s3.NewPresignClient(public), bucket: "spark"} u, err := s.PresignPut(t.Context(), "chat/a.jpg", "image/jpeg", 1024, time.Hour) if err != nil { t.Fatal(err) } if !strings.Contains(u, "https://sparkr.wyrs365.com/spark/chat/a.jpg") { t.Fatalf("%s", u) } if strings.Contains(u, "rustfs:9000") { t.Fatalf("internal host leaked: %s", u) } } func TestGetPutNotReady(t *testing.T) { s, err := Open(&Options{}) if err != nil { t.Fatal(err) } if _, err := s.Get(t.Context(), "perm/a"); !errors.Is(err, errs.ErrAvailable) { t.Fatalf("get %v", err) } if err := s.Put(t.Context(), "perm/a", []byte("x"), "image/jpeg"); !errors.Is(err, errs.ErrAvailable) { t.Fatalf("put %v", err) } } func TestPublicReadPolicyCoversAvatarsOnly(t *testing.T) { p := publicReadPolicy("spark") if !strings.Contains(p, "spark/perm/avatar/*") || !strings.Contains(p, "spark/perm/group/*") { t.Fatalf("%s", p) } if strings.Contains(p, "chat/") || strings.Contains(p, "perm/fav") || strings.Contains(p, "perm/moment") { t.Fatalf("chat/fav/moment must stay private: %s", p) } } func TestMapObjectErr(t *testing.T) { err := mapObjectErr(&types.NoSuchKey{Message: aws.String("gone")}) if !errors.Is(err, errs.ErrMediaExpired) { t.Fatalf("%v", err) } err = mapObjectErr(errors.New("boom")) if !errors.Is(err, errs.ErrOperatFail) { t.Fatalf("%v", err) } } func TestOpenLive(t *testing.T) { if os.Getenv("SPARK_S3_LIVE") == "" { t.Skip("set SPARK_S3_LIVE=1 to hit RustFS") } s, err := Open(&Options{ Endpoint: "47.236.66.9:9000", AccessKey: os.Getenv("SPARK_S3_ACCESS_KEY"), SecretKey: os.Getenv("SPARK_S3_SECRET_KEY"), Bucket: "spark", ChatExpireDays: 365, Log: zap.NewNop(), }) if err != nil { t.Fatal(err) } if !s.Ready() { t.Fatal("not ready") } if err := s.Ping(t.Context()); err != nil { t.Fatal(err) } }