package photos import ( "context" "testing" "time" ) func TestCopySharesStorageObjects(t *testing.T) { pool := testPool(t) ctx := context.Background() svc, cl := newTestService(t, pool) user := createUser(t, ctx, pool) albumA := createAlbum(t, ctx, pool, user) albumB := createAlbum(t, ctx, pool, user) p, err := svc.Upload(ctx, UploadInput{ AlbumID: albumA, UserID: user, Filename: "p.png", MimeType: "image/png", Data: makePNG(t), }) if err != nil { t.Fatalf("upload: %v", err) } t.Cleanup(func() { cleanupPhoto(t, ctx, pool, cl, p.ID) }) w := NewWorker(svc, time.Second, 10, time.Minute, 2) w.processVariantJobs(ctx) srcIDs, err := svc.store.PhotoObjectIDs(ctx, p.ID) if err != nil || len(srcIDs) != 3 { t.Fatalf("source objects = %v err=%v", srcIDs, err) } clones, err := svc.CopyBatch(ctx, []string{p.ID}, user, albumB) if err != nil || len(clones) != 1 { t.Fatalf("copy: %v len=%d", err, len(clones)) } clone := clones[0] t.Cleanup(func() { cleanupPhoto(t, ctx, pool, cl, clone.ID) }) cloneIDs, err := svc.store.PhotoObjectIDs(ctx, clone.ID) if err != nil || len(cloneIDs) != 3 { t.Fatalf("clone objects = %v err=%v", cloneIDs, err) } if !sameSet(srcIDs, cloneIDs) { t.Fatalf("clone does not share objects: src=%v clone=%v", srcIDs, cloneIDs) } for _, id := range cloneIDs { if n, err := svc.store.ObjectRefCount(ctx, id); err != nil || n != 2 { t.Fatalf("object %s ref count = %d err=%v, want 2", id, n, err) } } // Move the source into the same album; the storage key and shared objects // must be untouched. beforeKey := p.StorageKey moved, err := svc.MoveBatch(ctx, []string{p.ID}, user, albumB) if err != nil || moved != 1 { t.Fatalf("move: %v moved=%d", err, moved) } after, err := svc.store.GetByID(ctx, p.ID, "") if err != nil { t.Fatalf("get after move: %v", err) } if after.AlbumID != albumB { t.Fatalf("photo not moved: album=%q", after.AlbumID) } if after.StorageKey != beforeKey { t.Fatalf("move changed storage key: %q -> %q", beforeKey, after.StorageKey) } for _, id := range cloneIDs { if n, err := svc.store.ObjectRefCount(ctx, id); err != nil || n != 2 { t.Fatalf("object %s ref count after move = %d err=%v, want 2", id, n, err) } } } func sameSet(a, b []string) bool { if len(a) != len(b) { return false } m := make(map[string]int, len(a)) for _, v := range a { m[v]++ } for _, v := range b { m[v]-- if m[v] < 0 { return false } } return true }