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": [] }`)) }