package logic import ( "context" "errors" "strings" "testing" "time" "spark-server/internal/infrastructure/id" "spark-server/internal/module/upload/entity" "spark-server/internal/pkg/errs" "spark-server/internal/pkg/reqctx" ) type fakeStore struct { put, get string putErr error } func (f *fakeStore) PresignPut(_ context.Context, key, _ string, _ time.Duration) (string, error) { if f.putErr != nil { return "", f.putErr } if f.put != "" { return f.put, nil } return "http://rustfs.test/" + key + "?sig=1", nil } func (f *fakeStore) PresignGet(_ context.Context, key string, _ time.Duration) (string, error) { if f.get != "" { return f.get, nil } return "http://rustfs.test/" + key + "?get=1", nil } func testUpload(t *testing.T) *Service { t.Helper() ids, err := id.New(1) if err != nil { t.Fatal(err) } return NewService(&fakeStore{}, ids, Options{ TTL: 5 * time.Minute, Now: func() time.Time { return time.Date(2026, 9, 17, 8, 0, 0, 0, time.UTC) }, }) } func TestCreateCredentialChatVsAvatar(t *testing.T) { svc := testUpload(t) ctx := reqctx.With(t.Context(), &reqctx.RequestContext{UserID: 42, Language: "zh-CN"}) chat, err := svc.CreateCredential(ctx, 42, &CreateInput{Purpose: entity.PurposeChatImage, Mime: "image/jpeg", Size: 1024}) if err != nil { t.Fatal(err) } if !strings.HasPrefix(chat.ObjectKey, "chat/20260917/42/") || chat.Method != "PUT" || chat.Headers["Content-Type"] != "image/jpeg" { t.Fatalf("%+v", chat) } avatar, err := svc.CreateCredential(ctx, 42, &CreateInput{Purpose: entity.PurposeAvatar, Mime: "image/png", Size: 2048}) if err != nil || !strings.HasPrefix(avatar.ObjectKey, "perm/avatar/42/20260917/") { t.Fatalf("%+v %v", avatar, err) } group, err := svc.CreateCredential(ctx, 42, &CreateInput{Purpose: entity.PurposeGroupAvatar, Mime: "image/jpeg", Size: 1024, GroupID: 7}) if err != nil || !strings.HasPrefix(group.ObjectKey, "perm/group/7/") { t.Fatalf("%+v %v", group, err) } if _, err := svc.CreateCredential(ctx, 42, &CreateInput{Purpose: entity.PurposeGroupAvatar, Mime: "image/jpeg", Size: 1024}); err == nil { t.Fatal("group avatar without group_id") } } func TestCreateCredentialRejectsBadMIMEAndSize(t *testing.T) { svc := testUpload(t) ctx := reqctx.With(t.Context(), &reqctx.RequestContext{UserID: 1, Language: "zh-CN"}) if _, err := svc.CreateCredential(ctx, 1, &CreateInput{Purpose: entity.PurposeAvatar, Mime: "application/pdf", Size: 100}); err == nil { t.Fatal("pdf avatar") } if _, err := svc.CreateCredential(ctx, 1, &CreateInput{Purpose: entity.PurposeChatImage, Mime: "image/jpeg", Size: 50 << 20}); err == nil { t.Fatal("huge image") } if _, err := svc.CreateCredential(ctx, 0, &CreateInput{Purpose: entity.PurposeChatImage, Mime: "image/jpeg", Size: 1}); !errors.Is(err, errs.ErrUnauthorized) { t.Fatalf("uid %v", err) } } func TestCreateDownloadValidatesKey(t *testing.T) { svc := testUpload(t) ctx := reqctx.With(t.Context(), &reqctx.RequestContext{UserID: 1, Language: "en"}) got, err := svc.CreateDownload(ctx, 1, &DownloadInput{ObjectKey: "chat/20260917/1/2.jpg"}) if err != nil || got.Method != "GET" || !strings.Contains(got.DownloadURL, "chat/20260917/1/2.jpg") { t.Fatalf("%+v %v", got, err) } if _, err := svc.CreateDownload(ctx, 1, &DownloadInput{ObjectKey: "../secret"}); err == nil { t.Fatal("path escape") } }