chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -1,8 +1,10 @@
package postgres
import (
"bytes"
"context"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
@ -142,10 +144,10 @@ func TestMediaStoreRoundTrip(t *testing.T) {
}
// ---- profile photo 历史 ----
if err := s.AddProfilePhoto(ctx, domain.PeerTypeUser, ownerID, photoID, 1700000000); err != nil {
if err := s.AddProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, photoID, 1700000000); err != nil {
t.Fatalf("add profile photo: %v", err)
}
cur, ok, err := s.CurrentProfilePhoto(ctx, domain.PeerTypeUser, ownerID)
cur, ok, err := s.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile)
if err != nil || !ok || cur != photoID {
t.Fatalf("current profile photo = (%d,%v,%v)", cur, ok, err)
}
@ -153,7 +155,7 @@ func TestMediaStoreRoundTrip(t *testing.T) {
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)
ids, total, err := s.ListProfilePhotosKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, 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)
}
@ -161,26 +163,228 @@ func TestMediaStoreRoundTrip(t *testing.T) {
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 {
if _, ok, _ := s.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile); 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 {
if err := s.SaveFilePart(ctx, domain.UploadPart{OwnerUserID: ownerID, FileID: 555, Part: 0, Backend: domain.MediaBackendLocalFS, ObjectKey: "upload_parts/test/555/0-a.part", Size: 5, SHA256: []byte("hello")}); err != nil {
t.Fatalf("save file part: %v", err)
}
if err := s.SaveFilePart(ctx, domain.UploadPart{OwnerUserID: ownerID, FileID: 555, Part: 0, Backend: domain.MediaBackendLocalFS, ObjectKey: "upload_parts/test/555/0-b.part", Size: 6, SHA256: []byte("hello!")}); err != nil {
t.Fatalf("retry file part: %v", err)
}
parts, err := s.LoadFileParts(ctx, ownerID, 555)
if err != nil || len(parts) != 1 || string(parts[0].Bytes) != "hello" {
if err != nil || len(parts) != 1 || parts[0].ObjectKey != "upload_parts/test/555/0-b.part" || parts[0].Size != 6 {
t.Fatalf("load file parts: parts=%+v err=%v", parts, err)
}
if err := s.DeleteFileParts(ctx, ownerID, 555); err != nil {
usage, err := s.UploadPartUsage(ctx, ownerID)
if err != nil {
t.Fatalf("upload part usage: %v", err)
}
if usage.Bytes != 6 || usage.Parts != 1 || usage.Files != 1 {
t.Fatalf("upload part usage = %+v", usage)
}
slot, err := s.UploadPartSlot(ctx, ownerID, 555, 0)
if err != nil {
t.Fatalf("upload part slot: %v", err)
}
if !slot.Found || slot.ExistingBytes != 6 || slot.ObjectKey != "upload_parts/test/555/0-b.part" || slot.FileParts != 1 {
t.Fatalf("upload part slot = %+v", slot)
}
if err := s.SaveFilePart(ctx, domain.UploadPart{OwnerUserID: ownerID, FileID: 556, Part: 0, Backend: domain.MediaBackendLocalFS, ObjectKey: "upload_parts/test/556/0.part", Size: 3, SHA256: []byte("old")}); err != nil {
t.Fatalf("save old file part: %v", err)
}
if _, err := pool.Exec(ctx, "UPDATE upload_parts SET created_at = now() - interval '48 hours' WHERE owner_user_id = $1 AND file_id = 556", ownerID); err != nil {
t.Fatalf("age upload part: %v", err)
}
uploadDeleted, err := s.DeleteExpiredUploadParts(ctx, time.Now().Add(-24*time.Hour), 10)
if err != nil {
t.Fatalf("delete expired upload parts: %v", err)
}
if len(uploadDeleted) != 1 || uploadDeleted[0] != "upload_parts/test/556/0.part" {
t.Fatalf("delete expired upload parts deleted = %+v, want object key", uploadDeleted)
}
if stale, _ := s.LoadFileParts(ctx, ownerID, 556); len(stale) != 0 {
t.Fatalf("expired file parts still present: %+v", stale)
}
deletedPartKeys, err := s.DeleteFileParts(ctx, ownerID, 555)
if err != nil {
t.Fatalf("delete file parts: %v", err)
}
if len(deletedPartKeys) != 1 || deletedPartKeys[0] != "upload_parts/test/555/0-b.part" {
t.Fatalf("delete file parts keys = %+v", deletedPartKeys)
}
if parts, _ := s.LoadFileParts(ctx, ownerID, 555); len(parts) != 0 {
t.Fatal("file parts not cleared")
}
}
func TestMediaStoreDocumentCacheCopiesAndRefreshes(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
s := NewMediaStore(pool)
const docID = int64(9100000000000000101)
if _, err := pool.Exec(ctx, `DELETE FROM documents WHERE id = $1`, docID); err != nil {
t.Fatalf("cleanup document: %v", err)
}
t.Cleanup(func() {
_, _ = pool.Exec(context.Background(), `DELETE FROM documents WHERE id = $1`, docID)
})
doc := domain.Document{
ID: docID,
AccessHash: 101,
FileReference: []byte{1, 2, 3},
Date: 123,
MimeType: "application/octet-stream",
Size: 10,
DCID: 2,
Attributes: []domain.DocumentAttribute{{
Kind: domain.DocAttrAudio,
Waveform: []byte{4, 5, 6},
}},
Thumbs: []domain.PhotoSize{{
Kind: domain.PhotoSizeKindCached,
Type: "m",
Bytes: []byte{7, 8, 9},
Sizes: []int{1, 2, 3},
}},
}
if err := s.PutDocument(ctx, doc); err != nil {
t.Fatalf("put document: %v", err)
}
doc.FileReference[0] = 99
doc.Attributes[0].Waveform[0] = 99
doc.Thumbs[0].Bytes[0] = 99
doc.Thumbs[0].Sizes[0] = 99
got, ok, err := s.GetDocument(ctx, docID)
if err != nil || !ok {
t.Fatalf("get document: ok=%v err=%v", ok, err)
}
if !bytes.Equal(got.FileReference, []byte{1, 2, 3}) ||
!bytes.Equal(got.Attributes[0].Waveform, []byte{4, 5, 6}) ||
!bytes.Equal(got.Thumbs[0].Bytes, []byte{7, 8, 9}) ||
got.Thumbs[0].Sizes[0] != 1 {
t.Fatalf("cached document was mutated: %+v", got)
}
got.FileReference[0] = 42
got.Attributes[0].Waveform[0] = 42
got.Thumbs[0].Bytes[0] = 42
got.Thumbs[0].Sizes[0] = 42
again, ok, err := s.GetDocument(ctx, docID)
if err != nil || !ok {
t.Fatalf("get document again: ok=%v err=%v", ok, err)
}
if !bytes.Equal(again.FileReference, []byte{1, 2, 3}) ||
!bytes.Equal(again.Attributes[0].Waveform, []byte{4, 5, 6}) ||
!bytes.Equal(again.Thumbs[0].Bytes, []byte{7, 8, 9}) ||
again.Thumbs[0].Sizes[0] != 1 {
t.Fatalf("cache returned shared slices: %+v", again)
}
docs, err := s.GetDocuments(ctx, []int64{0, docID, docID, docID + 1})
if err != nil {
t.Fatalf("get documents: %v", err)
}
if len(docs) != 1 || docs[0].ID != docID {
t.Fatalf("get documents = %+v, want one cached document", docs)
}
updated := again
updated.AccessHash = 202
updated.FileReference = []byte{10, 11, 12}
updated.Attributes[0].Waveform = []byte{13, 14, 15}
if err := s.PutDocument(ctx, updated); err != nil {
t.Fatalf("put updated document: %v", err)
}
refreshed, ok, err := s.GetDocument(ctx, docID)
if err != nil || !ok {
t.Fatalf("get refreshed document: ok=%v err=%v", ok, err)
}
if refreshed.AccessHash != 202 ||
!bytes.Equal(refreshed.FileReference, []byte{10, 11, 12}) ||
!bytes.Equal(refreshed.Attributes[0].Waveform, []byte{13, 14, 15}) {
t.Fatalf("document cache did not refresh after PutDocument: %+v", refreshed)
}
}
func TestMediaStoreListProfilePhotoDetailsBatchesPhotosAndRefreshesMaxID(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
s := NewMediaStore(pool)
const ownerID = int64(9100000000000000199)
photoIDs := []int64{9100000000000000201, 9100000000000000202, 9100000000000000203, 9100000000000000204}
cleanupProfilePhotoDetailsRows(t, ctx, pool, ownerID, photoIDs)
t.Cleanup(func() {
cleanupProfilePhotoDetailsRows(t, context.Background(), pool, ownerID, photoIDs)
})
for i, id := range photoIDs {
photo := domain.Photo{
ID: id,
AccessHash: int64(700 + i),
FileReference: []byte{byte(i + 1)},
Date: 1700000100 + i,
DCID: 2,
Sizes: []domain.PhotoSize{{Kind: domain.PhotoSizeKindDefault, Type: "x", W: 100 + i, H: 100 + i, Size: 1000 + i}},
}
if err := s.PutPhoto(ctx, photo); err != nil {
t.Fatalf("put photo %d: %v", id, err)
}
}
if err := s.AddProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, photoIDs[0], 1700000101); err != nil {
t.Fatalf("add first profile photo: %v", err)
}
if err := s.AddProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, photoIDs[1], 1700000102); err != nil {
t.Fatalf("add second profile photo: %v", err)
}
if err := s.AddProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, photoIDs[2], 1700000103); err != nil {
t.Fatalf("add third profile photo: %v", err)
}
if err := s.AddProfilePhotoKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindFallback, photoIDs[3], 1700000104); err != nil {
t.Fatalf("add fallback photo: %v", err)
}
photos, total, err := s.ListProfilePhotoDetailsKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, 0, 2, 0)
if err != nil {
t.Fatalf("list profile photo details: %v", err)
}
if total != 3 {
t.Fatalf("total = %d, want 3 profile photos only", total)
}
if gotIDs := photoIDsFromDomain(photos); len(gotIDs) != 2 || gotIDs[0] != photoIDs[2] || gotIDs[1] != photoIDs[1] {
t.Fatalf("first page ids = %v, want newest profile photos [%d %d]", gotIDs, photoIDs[2], photoIDs[1])
}
if len(photos[0].Sizes) != 1 || photos[0].Sizes[0].W != 102 {
t.Fatalf("joined photo sizes not decoded: %+v", photos[0])
}
refreshed, total, err := s.ListProfilePhotoDetailsKind(ctx, domain.PeerTypeUser, ownerID, domain.ProfilePhotoKindProfile, -1, 1, photoIDs[1])
if err != nil {
t.Fatalf("refresh profile photo by max_id: %v", err)
}
if total != 3 {
t.Fatalf("refresh total = %d, want 3", total)
}
if gotIDs := photoIDsFromDomain(refreshed); len(gotIDs) != 1 || gotIDs[0] != photoIDs[1] {
t.Fatalf("refresh ids = %v, want exact max_id %d", gotIDs, photoIDs[1])
}
}
func photoIDsFromDomain(photos []domain.Photo) []int64 {
ids := make([]int64, 0, len(photos))
for _, photo := range photos {
ids = append(ids, photo.ID)
}
return ids
}
func cleanupMediaStoreRoundTripRows(t *testing.T, ctx context.Context, pool *pgxpool.Pool) {
t.Helper()
@ -194,7 +398,7 @@ func cleanupMediaStoreRoundTripRows(t *testing.T, ctx context.Context, pool *pgx
sql string
args []any
}{
{sql: "DELETE FROM upload_parts WHERE owner_user_id = $1 AND file_id = 555", args: []any{ownerID}},
{sql: "DELETE FROM upload_parts WHERE owner_user_id = $1 AND file_id IN (555, 556)", 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}},
@ -208,3 +412,13 @@ func cleanupMediaStoreRoundTripRows(t *testing.T, ctx context.Context, pool *pgx
}
}
}
func cleanupProfilePhotoDetailsRows(t *testing.T, ctx context.Context, pool *pgxpool.Pool, ownerID int64, photoIDs []int64) {
t.Helper()
if _, err := pool.Exec(ctx, "DELETE FROM profile_photos WHERE owner_peer_type = 'user' AND owner_peer_id = $1 AND photo_id = ANY($2::bigint[])", ownerID, photoIDs); err != nil {
t.Fatalf("cleanup profile photo details profile_photos: %v", err)
}
if _, err := pool.Exec(ctx, "DELETE FROM photos WHERE id = ANY($1::bigint[])", photoIDs); err != nil {
t.Fatalf("cleanup profile photo details photos: %v", err)
}
}