438 lines
14 KiB
Go
438 lines
14 KiB
Go
package files
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func TestCreateStickerSetInvalidatesNegativeCacheAndLinksDocuments(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := &fakeMediaStore{
|
|
docs: map[int64]domain.Document{
|
|
101: {ID: 101, AccessHash: 1001, DCID: 2, Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
|
|
},
|
|
photos: map[int64]domain.Photo{},
|
|
sets: map[int64]domain.StickerSet{},
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
|
|
_, _, found, err := svc.ResolveStickerSet(ctx, domain.StickerSetRef{Kind: domain.StickerSetRefByShortName, ShortName: "fresh_pack"})
|
|
if err != nil {
|
|
t.Fatalf("resolve before create: %v", err)
|
|
}
|
|
if found {
|
|
t.Fatalf("resolve before create found set, want miss")
|
|
}
|
|
|
|
set, docs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Fresh Pack",
|
|
ShortName: "fresh_pack",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 101,
|
|
DocumentAccessHash: 1001,
|
|
Emoji: "🙂",
|
|
Keywords: "fresh, happy, fresh",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create sticker set: %v", err)
|
|
}
|
|
if set.ShortName != "fresh_pack" || !set.Creator || set.CreatorUserID != 1000000001 || set.Count != 1 {
|
|
t.Fatalf("created set = %+v, want creator-owned fresh_pack with one item", set)
|
|
}
|
|
if len(set.Keywords) != 1 || len(set.Keywords[0].Keywords) != 2 {
|
|
t.Fatalf("keywords = %+v, want deduped keyword list", set.Keywords)
|
|
}
|
|
if len(docs) != 1 {
|
|
t.Fatalf("created docs = %d, want 1", len(docs))
|
|
}
|
|
id, hash, ok := docs[0].StickerSetRef()
|
|
if !ok || id != set.ID || hash != set.AccessHash {
|
|
t.Fatalf("document sticker set ref = %d/%d/%v, want %d/%d/true", id, hash, ok, set.ID, set.AccessHash)
|
|
}
|
|
|
|
resolved, resolvedDocs, found, err := svc.ResolveStickerSet(ctx, domain.StickerSetRef{Kind: domain.StickerSetRefByShortName, ShortName: "fresh_pack"})
|
|
if err != nil {
|
|
t.Fatalf("resolve after create: %v", err)
|
|
}
|
|
if !found || resolved.ID != set.ID || len(resolvedDocs) != 1 {
|
|
t.Fatalf("resolve after create = found %v set %+v docs %d, want created set", found, resolved, len(resolvedDocs))
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetAcceptsUploadedStickerMaterial(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := &fakeMediaStore{
|
|
docs: map[int64]domain.Document{
|
|
201: {
|
|
ID: 201,
|
|
AccessHash: 2001,
|
|
DCID: 2,
|
|
MimeType: "application/octet-stream",
|
|
Size: 4096,
|
|
Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrFilename, FileName: "local.tgs"}},
|
|
},
|
|
},
|
|
sets: map[int64]domain.StickerSet{},
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
|
|
set, docs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Uploads",
|
|
ShortName: "uploads_pack",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 201,
|
|
DocumentAccessHash: 2001,
|
|
Emoji: "👋",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create with uploaded material: %v", err)
|
|
}
|
|
if len(docs) != 1 || !docs[0].IsSticker() {
|
|
t.Fatalf("created docs = %+v, want sticker-tagged uploaded document", docs)
|
|
}
|
|
if id, hash, ok := docs[0].StickerSetRef(); !ok || id != set.ID || hash != set.AccessHash {
|
|
t.Fatalf("uploaded doc sticker ref = %d/%d/%v, want %d/%d/true", id, hash, ok, set.ID, set.AccessHash)
|
|
}
|
|
if !documentHasAttr(docs[0], domain.DocAttrImageSize) || !documentHasAttr(docs[0], domain.DocAttrFilename) {
|
|
t.Fatalf("uploaded doc attrs = %+v, want filename preserved and image size added", docs[0].Attributes)
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetNormalizesUploadedTGSMime(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := newFakeMediaStore()
|
|
media.docs[206] = domain.Document{
|
|
ID: 206,
|
|
AccessHash: 2006,
|
|
DCID: 2,
|
|
Size: 1024,
|
|
Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrFilename, FileName: "local.tgs"}},
|
|
}
|
|
media.blobs["doc:206"] = domain.FileBlob{
|
|
LocationKey: "doc:206",
|
|
Size: 1024,
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
|
|
_, docs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Uploads",
|
|
ShortName: "tgs_uploads",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 206,
|
|
DocumentAccessHash: 2006,
|
|
Emoji: "👋",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create with uploaded tgs material: %v", err)
|
|
}
|
|
if len(docs) != 1 || docs[0].MimeType != stickerMaterialMimeTGS {
|
|
t.Fatalf("created docs = %+v, want normalized tgs mime", docs)
|
|
}
|
|
stored := media.docs[206]
|
|
if stored.MimeType != stickerMaterialMimeTGS {
|
|
t.Fatalf("stored document mime = %q, want %q", stored.MimeType, stickerMaterialMimeTGS)
|
|
}
|
|
blob := media.blobs["doc:206"]
|
|
if blob.MimeType != stickerMaterialMimeTGS {
|
|
t.Fatalf("stored blob mime = %q, want %q", blob.MimeType, stickerMaterialMimeTGS)
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetClonesDocumentWhenReusedAcrossSets(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := &fakeMediaStore{
|
|
docs: map[int64]domain.Document{
|
|
301: {ID: 301, AccessHash: 3001, DCID: 2, Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
|
|
},
|
|
sets: map[int64]domain.StickerSet{},
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
|
|
emojiSet, emojiDocs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Emoji Pack",
|
|
ShortName: "emoji_pack",
|
|
Kind: domain.StickerSetKindEmoji,
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 301,
|
|
DocumentAccessHash: 3001,
|
|
Emoji: "🙂",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create emoji set: %v", err)
|
|
}
|
|
stickerSet, stickerDocs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Sticker Pack",
|
|
ShortName: "sticker_pack",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 301,
|
|
DocumentAccessHash: 3001,
|
|
Emoji: "😄",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create sticker set from existing emoji doc: %v", err)
|
|
}
|
|
if len(emojiDocs) != 1 || len(stickerDocs) != 1 {
|
|
t.Fatalf("created docs: emoji=%+v sticker=%+v, want one doc each", emojiDocs, stickerDocs)
|
|
}
|
|
if stickerDocs[0].ID == 301 || stickerSet.DocumentIDs[0] == 301 {
|
|
t.Fatalf("sticker set reused source doc id, set=%+v docs=%+v", stickerSet, stickerDocs)
|
|
}
|
|
source := media.docs[301]
|
|
if !source.IsCustomEmoji() {
|
|
t.Fatalf("source doc attrs = %+v, want custom emoji preserved", source.Attributes)
|
|
}
|
|
if id, _, ok := source.StickerSetRef(); !ok || id != emojiSet.ID {
|
|
t.Fatalf("source doc set ref = %d/%v, want emoji set %d", id, ok, emojiSet.ID)
|
|
}
|
|
if !stickerDocs[0].IsSticker() || stickerDocs[0].IsCustomEmoji() {
|
|
t.Fatalf("cloned doc attrs = %+v, want regular sticker", stickerDocs[0].Attributes)
|
|
}
|
|
if id, _, ok := stickerDocs[0].StickerSetRef(); !ok || id != stickerSet.ID {
|
|
t.Fatalf("cloned doc set ref = %d/%v, want sticker set %d", id, ok, stickerSet.ID)
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetAcceptsWebPMaterialWithClientImageSize(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := &fakeMediaStore{
|
|
docs: map[int64]domain.Document{
|
|
202: {
|
|
ID: 202,
|
|
AccessHash: 2002,
|
|
DCID: 2,
|
|
MimeType: "image/webp",
|
|
Size: 4096,
|
|
Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrImageSize, W: 512, H: 512}},
|
|
},
|
|
},
|
|
sets: map[int64]domain.StickerSet{},
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
|
|
_, docs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "WebP Uploads",
|
|
ShortName: "webp_uploads",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 202,
|
|
DocumentAccessHash: 2002,
|
|
Emoji: "🙂",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create with client-sized webp: %v", err)
|
|
}
|
|
if len(docs) != 1 || !docs[0].IsSticker() || !documentHasAttr(docs[0], domain.DocAttrImageSize) {
|
|
t.Fatalf("created docs = %+v, want sticker with image size preserved", docs)
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetConvertsLottieJSONMaterialToTGS(t *testing.T) {
|
|
ctx := context.Background()
|
|
raw := testLottieJSON()
|
|
blobs, err := NewLocalFS(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("new local fs: %v", err)
|
|
}
|
|
objectKey, err := blobs.Put(ctx, raw)
|
|
if err != nil {
|
|
t.Fatalf("put lottie json blob: %v", err)
|
|
}
|
|
media := newFakeMediaStore()
|
|
media.docs[204] = domain.Document{
|
|
ID: 204,
|
|
AccessHash: 2004,
|
|
DCID: 2,
|
|
MimeType: "application/json",
|
|
Size: int64(len(raw)),
|
|
Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrFilename, FileName: "wave.json"}},
|
|
}
|
|
if err := media.PutFileBlob(ctx, domain.FileBlob{
|
|
LocationKey: "doc:204",
|
|
Backend: domain.MediaBackend(blobs.Name()),
|
|
ObjectKey: objectKey,
|
|
Size: int64(len(raw)),
|
|
MimeType: "application/json",
|
|
}); err != nil {
|
|
t.Fatalf("put lottie file blob: %v", err)
|
|
}
|
|
svc := NewService(media, blobs, 2)
|
|
|
|
_, docs, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Lottie Uploads",
|
|
ShortName: "lottie_uploads",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 204,
|
|
DocumentAccessHash: 2004,
|
|
Emoji: "👋",
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create with lottie json: %v", err)
|
|
}
|
|
if len(docs) != 1 || !docs[0].IsSticker() || docs[0].MimeType != "application/x-tgsticker" {
|
|
t.Fatalf("created docs = %+v, want sticker-tagged tgs document", docs)
|
|
}
|
|
if !documentHasAttr(docs[0], domain.DocAttrImageSize) {
|
|
t.Fatalf("converted doc attrs = %+v, want image size", docs[0].Attributes)
|
|
}
|
|
if got := documentFileName(docs[0]); got != "wave.tgs" {
|
|
t.Fatalf("converted filename = %q, want wave.tgs", got)
|
|
}
|
|
blob, found, err := media.GetFileBlob(ctx, "doc:204")
|
|
if err != nil || !found {
|
|
t.Fatalf("converted file blob found=%v err=%v", found, err)
|
|
}
|
|
if blob.MimeType != "application/x-tgsticker" || blob.Size != docs[0].Size {
|
|
t.Fatalf("converted file blob = %+v doc size %d, want tgs metadata", blob, docs[0].Size)
|
|
}
|
|
data, total, err := blobs.GetRange(ctx, blob.ObjectKey, 0, blob.Size)
|
|
if err != nil {
|
|
t.Fatalf("read converted tgs blob: %v", err)
|
|
}
|
|
if int64(len(data)) != total || !validTGSStickerData(data) {
|
|
t.Fatalf("converted blob len=%d total=%d valid=%v, want valid tgs", len(data), total, validTGSStickerData(data))
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetRejectsInvalidLottieJSONMaterial(t *testing.T) {
|
|
ctx := context.Background()
|
|
raw := []byte(`{"v":"5.7.4","layers":[]}`)
|
|
blobs, err := NewLocalFS(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("new local fs: %v", err)
|
|
}
|
|
objectKey, err := blobs.Put(ctx, raw)
|
|
if err != nil {
|
|
t.Fatalf("put invalid lottie json blob: %v", err)
|
|
}
|
|
media := newFakeMediaStore()
|
|
media.docs[205] = domain.Document{
|
|
ID: 205,
|
|
AccessHash: 2005,
|
|
DCID: 2,
|
|
MimeType: "application/json",
|
|
Size: int64(len(raw)),
|
|
Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrFilename, FileName: "bad.json"}},
|
|
}
|
|
if err := media.PutFileBlob(ctx, domain.FileBlob{
|
|
LocationKey: "doc:205",
|
|
Backend: domain.MediaBackend(blobs.Name()),
|
|
ObjectKey: objectKey,
|
|
Size: int64(len(raw)),
|
|
MimeType: "application/json",
|
|
}); err != nil {
|
|
t.Fatalf("put invalid lottie file blob: %v", err)
|
|
}
|
|
svc := NewService(media, blobs, 2)
|
|
|
|
_, _, err = svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Bad Lottie Uploads",
|
|
ShortName: "bad_lottie_uploads",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 205,
|
|
DocumentAccessHash: 2005,
|
|
Emoji: "👋",
|
|
}},
|
|
})
|
|
if !errors.Is(err, domain.ErrStickerSetFileInvalid) {
|
|
t.Fatalf("create with invalid lottie json err = %v, want ErrStickerSetFileInvalid", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetRejectsWebPMaterialWithoutShape(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := &fakeMediaStore{
|
|
docs: map[int64]domain.Document{
|
|
203: {ID: 203, AccessHash: 2003, MimeType: "image/webp", Size: 4096},
|
|
},
|
|
sets: map[int64]domain.StickerSet{},
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
|
|
_, _, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Bad WebP Uploads",
|
|
ShortName: "bad_webp_uploads",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 203,
|
|
DocumentAccessHash: 2003,
|
|
Emoji: "🙂",
|
|
}},
|
|
})
|
|
if !errors.Is(err, domain.ErrStickerSetFileInvalid) {
|
|
t.Fatalf("create with unsized webp err = %v, want ErrStickerSetFileInvalid", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateStickerSetRejectsDuplicateShortNameCaseInsensitive(t *testing.T) {
|
|
ctx := context.Background()
|
|
media := &fakeMediaStore{
|
|
docs: map[int64]domain.Document{
|
|
101: {ID: 101, AccessHash: 1001, Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
|
|
},
|
|
sets: map[int64]domain.StickerSet{
|
|
10: {ID: 10, ShortName: "Fresh_Pack", Kind: domain.StickerSetKindStickers},
|
|
},
|
|
}
|
|
svc := NewService(media, nil, 2)
|
|
_, _, err := svc.CreateStickerSet(ctx, domain.CreateStickerSetRequest{
|
|
CreatorUserID: 1000000001,
|
|
Title: "Other",
|
|
ShortName: "fresh_pack",
|
|
Items: []domain.StickerSetItemInput{{
|
|
DocumentID: 101,
|
|
DocumentAccessHash: 1001,
|
|
Emoji: "🙂",
|
|
}},
|
|
})
|
|
if !errors.Is(err, domain.ErrStickerSetShortNameOccupied) {
|
|
t.Fatalf("duplicate create err = %v, want ErrStickerSetShortNameOccupied", err)
|
|
}
|
|
}
|
|
|
|
func documentHasAttr(doc domain.Document, kind domain.DocumentAttributeKind) bool {
|
|
for _, attr := range doc.Attributes {
|
|
if attr.Kind == kind {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func documentFileName(doc domain.Document) string {
|
|
for _, attr := range doc.Attributes {
|
|
if attr.Kind == domain.DocAttrFilename {
|
|
return attr.FileName
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func testLottieJSON() []byte {
|
|
return []byte(strings.TrimSpace(`{
|
|
"v": "5.7.4",
|
|
"fr": 30,
|
|
"ip": 0,
|
|
"op": 30,
|
|
"w": 512,
|
|
"h": 512,
|
|
"layers": []
|
|
}`))
|
|
}
|