package photos import ( "bytes" "context" "io" "strings" "testing" "galo/internal/models" "galo/internal/storage" ) // fakeBackend satisfies storage.Backend by embedding the interface; only the // methods exercised by the code under test are implemented. type fakeBackend struct { storage.Backend } func (fakeBackend) URL(_ context.Context, key string) (string, error) { return "signed:" + key, nil } func (fakeBackend) Open(_ context.Context, _ string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte("fake-video-bytes"))), nil } func (fakeBackend) Put(context.Context, string, io.Reader, int64, string) error { return nil } func TestAttachURLsPlayback(t *testing.T) { svc := &Service{storage: fakeBackend{}} ctx := context.Background() p := &models.Photo{ StorageKey: "objects/a/original.mp4", ThumbKey: "objects/a/thumb.webp", PreviewKey: "objects/a/preview.webp", PlaybackKey: "objects/a/playback.mp4", } if err := svc.attachURLs(ctx, p); err != nil { t.Fatalf("attachURLs: %v", err) } if p.PlaybackURL != "signed:objects/a/playback.mp4" { t.Fatalf("playback url = %q", p.PlaybackURL) } if p.ThumbURL != "signed:objects/a/thumb.webp" { t.Fatalf("thumb url = %q", p.ThumbURL) } // An image without a playback binding exposes no playback URL. img := &models.Photo{StorageKey: "objects/b/original.jpg", ThumbKey: "objects/b/thumb.webp"} if err := svc.attachURLs(ctx, img); err != nil { t.Fatalf("attachURLs image: %v", err) } if img.PlaybackURL != "" { t.Fatalf("image playback url = %q, want empty", img.PlaybackURL) } } func TestMediaKind(t *testing.T) { if got := mediaKind(""); got != KindImage { t.Fatalf("empty kind = %q, want image", got) } if got := mediaKind(KindVideo); got != KindVideo { t.Fatalf("video kind = %q", got) } if got := mediaKind("bogus"); got != KindImage { t.Fatalf("bogus kind = %q, want image", got) } } func TestResolveMedia(t *testing.T) { pngHead := []byte{0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a} cases := []struct { name string declared string filename string head []byte wantKind string wantMimePref string }{ {"image header", "image/png", "x.png", nil, KindImage, "image/png"}, {"video header", "video/mp4", "clip.mp4", nil, KindVideo, "video/mp4"}, {"video by extension", "", "clip.mp4", nil, KindVideo, "video/mp4"}, {"image by extension", "", "x.jpg", nil, KindImage, "image/jpeg"}, {"sniff png", "", "", pngHead, KindImage, "image/png"}, {"generic mime by extension", "application/octet-stream", "movie.mkv", nil, KindVideo, ""}, {"unsupported", "text/plain", "notes.txt", nil, "", ""}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { kind, mime := resolveMedia(tc.declared, tc.filename, tc.head) if kind != tc.wantKind { t.Fatalf("kind = %q, want %q", kind, tc.wantKind) } if tc.wantMimePref != "" && mime != tc.wantMimePref { t.Fatalf("mime = %q, want %q", mime, tc.wantMimePref) } if kind != "" && !strings.HasPrefix(mime, kind+"/") { t.Fatalf("mime %q does not match kind %q", mime, kind) } }) } } func TestUploadRejectsUnsupportedAndDisabledVideo(t *testing.T) { ctx := context.Background() svc := &Service{albums: stubAlbums{}, imageMax: 1 << 20, videoMax: 1 << 30, videoEnabled: false} _, err := svc.Upload(ctx, UploadInput{ AlbumID: "a", UserID: "u", Filename: "notes.txt", MimeType: "text/plain", Reader: bytes.NewReader([]byte("hello")), }) if err == nil || !strings.Contains(err.Error(), "image or video") { t.Fatalf("unsupported err = %v, want invalid_file", err) } _, err = svc.Upload(ctx, UploadInput{ AlbumID: "a", UserID: "u", Filename: "clip.mp4", MimeType: "video/mp4", Reader: bytes.NewReader([]byte("....")), }) if err == nil || !strings.Contains(err.Error(), "not available") { t.Fatalf("disabled video err = %v, want video_disabled", err) } }