owpengram-server/internal/store/postgres/media_integration_test.go
2026-06-04 01:37:39 +08:00

210 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 blobnil 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 JSONBnil 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)
}
// ---- photosizes 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)
}
}
}