mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-07-29 00:18:54 +00:00
Handle photo not found case in API routes (#1201)
* Handle media not found issue * temporary run workflows for master pushes only; tests * revert temp workflow changes --------- Co-authored-by: Konstantin Koval
This commit is contained in:
@@ -26,6 +26,11 @@ func RegisterPhotoRoutes(db *gorm.DB, router *mux.Router) {
|
||||
}
|
||||
|
||||
media := mediaURL.Media
|
||||
if media == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("404 - Media not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if success, response, status, err := authenticateMedia(media, db, r); !success {
|
||||
if err != nil {
|
||||
@@ -46,7 +51,7 @@ func RegisterPhotoRoutes(db *gorm.DB, router *mux.Router) {
|
||||
|
||||
if _, err := os.Stat(cachedPath); os.IsNotExist((err)) {
|
||||
// err := db.Transaction(func(tx *gorm.DB) error {
|
||||
if err = scanner.ProcessSingleMedia(db, media); err != nil {
|
||||
if err = scanner.ProcessSingleMediaFunc(db, media); err != nil {
|
||||
log.Printf("ERROR: processing image not found in cache (%s): %s\n", cachedPath, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(internalServerError))
|
||||
|
||||
122
api/routes/photos_test.go
Normal file
122
api/routes/photos_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/photoview/photoview/api/graphql/auth"
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/scanner"
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
"github.com/photoview/photoview/api/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestPhotoRoutes(t *testing.T) {
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
user, err := models.RegisterUser(db, "testuser", nil, false)
|
||||
assert.NoError(t, err)
|
||||
album := models.Album{Title: "test_album", Path: "/photos"}
|
||||
assert.NoError(t, db.Model(&user).Association("Albums").Append(&album))
|
||||
|
||||
media := models.Media{
|
||||
Title: "test_media",
|
||||
Path: "/photos/test_image.jpg",
|
||||
AlbumID: album.ID,
|
||||
DateShot: time.Now(),
|
||||
Type: models.MediaTypePhoto,
|
||||
}
|
||||
assert.NoError(t, db.Save(&media).Error)
|
||||
|
||||
mediaURL := models.MediaURL{
|
||||
MediaID: media.ID,
|
||||
Media: &media,
|
||||
MediaName: "test_image.jpg",
|
||||
Width: 1000,
|
||||
Height: 800,
|
||||
Purpose: models.PhotoThumbnail,
|
||||
ContentType: "image/jpeg",
|
||||
FileSize: 1024,
|
||||
}
|
||||
assert.NoError(t, db.Save(&mediaURL).Error)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
orig := utils.MediaCachePath()
|
||||
utils.ConfigureTestCache(tempDir)
|
||||
defer utils.ConfigureTestCache(orig)
|
||||
|
||||
router := mux.NewRouter()
|
||||
RegisterPhotoRoutes(db, router)
|
||||
|
||||
// -- Test cases --
|
||||
|
||||
// Non-existent media_name => 404 (no auth required)
|
||||
t.Run("media not found", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/does_not_exist.jpg", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusNotFound, rec.Code)
|
||||
assert.Equal(t, "404 - Media not found", rec.Body.String())
|
||||
})
|
||||
|
||||
// Missing auth => 403
|
||||
t.Run("auth failure", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/test_image.jpg", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "unauthorized")
|
||||
})
|
||||
|
||||
// Cache miss + scan error => 500
|
||||
t.Run("scan failure yields 500", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/test_image.jpg", nil)
|
||||
ctx := auth.AddUserToContext(req.Context(), user)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// ensure no cached file
|
||||
cachedPath, err := mediaURL.CachedPath()
|
||||
assert.NoError(t, err)
|
||||
os.Remove(cachedPath)
|
||||
|
||||
// mock scan to fail
|
||||
origScan := scanner.ProcessSingleMediaFunc
|
||||
scanner.ProcessSingleMediaFunc = func(db *gorm.DB, m *models.Media) error {
|
||||
return fmt.Errorf("scan error")
|
||||
}
|
||||
defer func() { scanner.ProcessSingleMediaFunc = origScan }()
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "server error")
|
||||
})
|
||||
|
||||
// Cache hit => 200 with correct body and headers
|
||||
t.Run("cache hit serves file", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/test_image.jpg", nil)
|
||||
ctx := auth.AddUserToContext(req.Context(), user)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// pre-create cached file
|
||||
cachedPath, err := mediaURL.CachedPath()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.MkdirAll(path.Dir(cachedPath), 0755))
|
||||
content := []byte("cached-binary")
|
||||
assert.NoError(t, os.WriteFile(cachedPath, content, 0644))
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "cached-binary", rec.Body.String())
|
||||
assert.Equal(t, "private, max-age=86400, immutable", rec.Header().Get("Cache-Control"))
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var ProcessSingleMediaFunc = ProcessSingleMedia
|
||||
|
||||
func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *scanner_cache.AlbumScannerCache) (*models.Media, bool, error) {
|
||||
mediaName := path.Base(mediaPath)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user