spark-server/internal/module/upload/logic/credential_test.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

98 lines
3.3 KiB
Go

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")
}
}