Initial open source release
This commit is contained in:
commit
74992e893f
377 changed files with 118084 additions and 0 deletions
210
internal/store/postgres/media_integration_test.go
Normal file
210
internal/store/postgres/media_integration_test.go
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// TestMediaStoreRoundTrip 验证 MediaStore 各表的写读往返(含 nil bytea 归一、JSONB attributes/sizes、
|
||||
// 头像历史 current/list/delete、上传分片)。直接证明媒体元数据落 PG 后可原样读回。
|
||||
func TestMediaStoreRoundTrip(t *testing.T) {
|
||||
pool := testPool(t)
|
||||
ctx := context.Background()
|
||||
s := NewMediaStore(pool)
|
||||
|
||||
const docID = int64(9100000000000000001)
|
||||
const photoID = int64(9100000000000000002)
|
||||
const setID = int64(9100000000000000003)
|
||||
const ownerID = int64(9100000000000000099)
|
||||
const reactionEmoji = "\U0001f9ea"
|
||||
|
||||
cleanupMediaStoreRoundTripRows(t, ctx, pool)
|
||||
t.Cleanup(func() {
|
||||
cleanupMediaStoreRoundTripRows(t, context.Background(), pool)
|
||||
})
|
||||
|
||||
// ---- file blob(nil sha256 应被归一为空,不报 NOT NULL)----
|
||||
if err := s.PutFileBlob(ctx, domain.FileBlob{
|
||||
LocationKey: "doc:9100000000000000001",
|
||||
ObjectKey: "ab/cd/abcdef",
|
||||
Size: 1234,
|
||||
MimeType: "application/x-tgsticker",
|
||||
}); err != nil {
|
||||
t.Fatalf("put file blob (nil sha256): %v", err)
|
||||
}
|
||||
blob, ok, err := s.GetFileBlob(ctx, "doc:9100000000000000001")
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("get file blob: ok=%v err=%v", ok, err)
|
||||
}
|
||||
if blob.ObjectKey != "ab/cd/abcdef" || blob.Size != 1234 || blob.Backend != domain.MediaBackendLocalFS {
|
||||
t.Fatalf("file blob mismatch: %+v", blob)
|
||||
}
|
||||
|
||||
// ---- document(含 sticker 属性 + thumbs JSONB;nil file_reference 路径)----
|
||||
doc := domain.Document{
|
||||
ID: docID,
|
||||
AccessHash: 77,
|
||||
DCID: 2,
|
||||
MimeType: "application/x-tgsticker",
|
||||
Size: 2048,
|
||||
Attributes: []domain.DocumentAttribute{
|
||||
{Kind: domain.DocAttrImageSize, W: 512, H: 512},
|
||||
{Kind: domain.DocAttrSticker, Alt: "\U0001f600", StickerSetID: setID, StickerSetAccessHash: 5},
|
||||
},
|
||||
Thumbs: []domain.PhotoSize{{Kind: domain.PhotoSizeKindPath, Type: "j", Bytes: []byte{1, 2, 3}}},
|
||||
}
|
||||
if err := s.PutDocument(ctx, doc); err != nil {
|
||||
t.Fatalf("put document: %v", err)
|
||||
}
|
||||
got, ok, err := s.GetDocument(ctx, docID)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("get document: ok=%v err=%v", ok, err)
|
||||
}
|
||||
if got.DCID != 2 || len(got.Attributes) != 2 || len(got.Thumbs) != 1 {
|
||||
t.Fatalf("document mismatch: %+v", got)
|
||||
}
|
||||
if id, hash, ok := got.StickerSetRef(); !ok || id != setID || hash != 5 {
|
||||
t.Fatalf("document sticker set ref = (%d,%d,%v)", id, hash, ok)
|
||||
}
|
||||
docs, err := s.GetDocuments(ctx, []int64{docID})
|
||||
if err != nil || len(docs) != 1 {
|
||||
t.Fatalf("get documents: n=%d err=%v", len(docs), err)
|
||||
}
|
||||
|
||||
// ---- photo(sizes JSONB)----
|
||||
photo := domain.Photo{
|
||||
ID: photoID,
|
||||
AccessHash: 88,
|
||||
DCID: 2,
|
||||
Sizes: []domain.PhotoSize{{Kind: domain.PhotoSizeKindDefault, Type: "x", W: 800, H: 600, Size: 4096}},
|
||||
}
|
||||
if err := s.PutPhoto(ctx, photo); err != nil {
|
||||
t.Fatalf("put photo: %v", err)
|
||||
}
|
||||
gotPhoto, ok, err := s.GetPhoto(ctx, photoID)
|
||||
if err != nil || !ok || len(gotPhoto.Sizes) != 1 || gotPhoto.Sizes[0].Type != "x" {
|
||||
t.Fatalf("get photo mismatch: ok=%v err=%v photo=%+v", ok, err, gotPhoto)
|
||||
}
|
||||
|
||||
// ---- sticker set ----
|
||||
set := domain.StickerSet{
|
||||
ID: setID,
|
||||
AccessHash: 5,
|
||||
ShortName: "telesrv_test_set_9100000000000000003",
|
||||
Title: "Test Set",
|
||||
Count: 1,
|
||||
Kind: domain.StickerSetKindStickers,
|
||||
Animated: true,
|
||||
Installed: true,
|
||||
DocumentIDs: []int64{docID},
|
||||
Packs: []domain.StickerPack{{Emoticon: "\U0001f600", DocumentIDs: []int64{docID}}},
|
||||
SystemKey: "test_system_9100000000000000003",
|
||||
}
|
||||
if err := s.PutStickerSet(ctx, set); err != nil {
|
||||
t.Fatalf("put sticker set: %v", err)
|
||||
}
|
||||
byID, ok, err := s.GetStickerSetByID(ctx, setID)
|
||||
if err != nil || !ok || len(byID.DocumentIDs) != 1 || len(byID.Packs) != 1 {
|
||||
t.Fatalf("get sticker set by id: ok=%v err=%v set=%+v", ok, err, byID)
|
||||
}
|
||||
if byShort, ok, _ := s.GetStickerSetByShortName(ctx, set.ShortName); !ok || byShort.ID != setID {
|
||||
t.Fatalf("get sticker set by short name failed: ok=%v", ok)
|
||||
}
|
||||
if bySys, ok, _ := s.GetStickerSetBySystemKey(ctx, set.SystemKey); !ok || bySys.ID != setID {
|
||||
t.Fatalf("get sticker set by system key failed: ok=%v", ok)
|
||||
}
|
||||
|
||||
// ---- available reaction ----
|
||||
if err := s.PutAvailableReaction(ctx, domain.AvailableReaction{
|
||||
Reaction: reactionEmoji, Title: "Test", StaticIconID: docID, SelectAnimationID: docID, Order: 9999,
|
||||
}); err != nil {
|
||||
t.Fatalf("put available reaction: %v", err)
|
||||
}
|
||||
reactions, err := s.ListAvailableReactions(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("list available reactions: %v", err)
|
||||
}
|
||||
foundReaction := false
|
||||
for _, r := range reactions {
|
||||
if r.Reaction == reactionEmoji {
|
||||
foundReaction = true
|
||||
if r.StaticIconID != docID {
|
||||
t.Fatalf("reaction static icon id = %d", r.StaticIconID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundReaction {
|
||||
t.Fatal("inserted reaction not found in list")
|
||||
}
|
||||
|
||||
// ---- profile photo 历史 ----
|
||||
if err := s.AddProfilePhoto(ctx, domain.PeerTypeUser, ownerID, photoID, 1700000000); err != nil {
|
||||
t.Fatalf("add profile photo: %v", err)
|
||||
}
|
||||
cur, ok, err := s.CurrentProfilePhoto(ctx, domain.PeerTypeUser, ownerID)
|
||||
if err != nil || !ok || cur != photoID {
|
||||
t.Fatalf("current profile photo = (%d,%v,%v)", cur, ok, err)
|
||||
}
|
||||
refs, err := s.CurrentProfilePhotos(ctx, domain.PeerTypeUser, []int64{ownerID})
|
||||
if err != nil || refs[ownerID].PhotoID != photoID || refs[ownerID].DCID != 2 {
|
||||
t.Fatalf("current profile photos batch = %+v err=%v", refs, err)
|
||||
}
|
||||
ids, total, err := s.ListProfilePhotos(ctx, domain.PeerTypeUser, ownerID, 0, 10, 0)
|
||||
if err != nil || total < 1 || len(ids) < 1 {
|
||||
t.Fatalf("list profile photos: ids=%v total=%d err=%v", ids, total, err)
|
||||
}
|
||||
deleted, err := s.DeleteProfilePhotos(ctx, domain.PeerTypeUser, ownerID, []int64{photoID})
|
||||
if err != nil || len(deleted) != 1 {
|
||||
t.Fatalf("delete profile photos: deleted=%v err=%v", deleted, err)
|
||||
}
|
||||
if _, ok, _ := s.CurrentProfilePhoto(ctx, domain.PeerTypeUser, ownerID); ok {
|
||||
t.Fatal("profile photo still current after delete")
|
||||
}
|
||||
|
||||
// ---- upload parts ----
|
||||
if err := s.SaveFilePart(ctx, domain.UploadPart{OwnerUserID: ownerID, FileID: 555, Part: 0, Bytes: []byte("hello")}); err != nil {
|
||||
t.Fatalf("save file part: %v", err)
|
||||
}
|
||||
parts, err := s.LoadFileParts(ctx, ownerID, 555)
|
||||
if err != nil || len(parts) != 1 || string(parts[0].Bytes) != "hello" {
|
||||
t.Fatalf("load file parts: parts=%+v err=%v", parts, err)
|
||||
}
|
||||
if err := s.DeleteFileParts(ctx, ownerID, 555); err != nil {
|
||||
t.Fatalf("delete file parts: %v", err)
|
||||
}
|
||||
if parts, _ := s.LoadFileParts(ctx, ownerID, 555); len(parts) != 0 {
|
||||
t.Fatal("file parts not cleared")
|
||||
}
|
||||
}
|
||||
|
||||
func cleanupMediaStoreRoundTripRows(t *testing.T, ctx context.Context, pool *pgxpool.Pool) {
|
||||
t.Helper()
|
||||
|
||||
const docID = int64(9100000000000000001)
|
||||
const photoID = int64(9100000000000000002)
|
||||
const setID = int64(9100000000000000003)
|
||||
const ownerID = int64(9100000000000000099)
|
||||
const reactionEmoji = "\U0001f9ea"
|
||||
|
||||
statements := []struct {
|
||||
sql string
|
||||
args []any
|
||||
}{
|
||||
{sql: "DELETE FROM upload_parts WHERE owner_user_id = $1 AND file_id = 555", args: []any{ownerID}},
|
||||
{sql: "DELETE FROM profile_photos WHERE owner_peer_type = 'user' AND owner_peer_id = $1 AND photo_id = $2", args: []any{ownerID, photoID}},
|
||||
{sql: "DELETE FROM available_reactions WHERE reaction IN ($1, 'telesrv-test-😀')", args: []any{reactionEmoji}},
|
||||
{sql: "DELETE FROM sticker_sets WHERE id = $1 OR short_name = 'telesrv_test_set_9100000000000000003' OR system_key = 'test_system_9100000000000000003'", args: []any{setID}},
|
||||
{sql: "DELETE FROM file_blobs WHERE location_key = 'doc:9100000000000000001'"},
|
||||
{sql: "DELETE FROM documents WHERE id = $1", args: []any{docID}},
|
||||
{sql: "DELETE FROM photos WHERE id = $1", args: []any{photoID}},
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if _, err := pool.Exec(ctx, stmt.sql, stmt.args...); err != nil {
|
||||
t.Fatalf("cleanup media store round trip rows: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue