mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-11 03:47:18 +02:00
177 lines
5.8 KiB
Go
177 lines
5.8 KiB
Go
|
|
package nativeapi
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"slices"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/conf"
|
||
|
|
"github.com/navidrome/navidrome/conf/configtest"
|
||
|
|
"github.com/navidrome/navidrome/core/auth"
|
||
|
|
"github.com/navidrome/navidrome/core/external"
|
||
|
|
"github.com/navidrome/navidrome/model"
|
||
|
|
"github.com/navidrome/navidrome/server"
|
||
|
|
"github.com/navidrome/navidrome/tests"
|
||
|
|
. "github.com/onsi/ginkgo/v2"
|
||
|
|
. "github.com/onsi/gomega"
|
||
|
|
)
|
||
|
|
|
||
|
|
type fakeProvider struct {
|
||
|
|
external.Provider
|
||
|
|
mu sync.Mutex
|
||
|
|
called []string
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *fakeProvider) RefreshInfo(_ context.Context, kind model.Kind, id string) error {
|
||
|
|
f.mu.Lock()
|
||
|
|
defer f.mu.Unlock()
|
||
|
|
f.called = append(f.called, kind.Prefix()+"/"+id)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *fakeProvider) calls() []string {
|
||
|
|
f.mu.Lock()
|
||
|
|
defer f.mu.Unlock()
|
||
|
|
return slices.Clone(f.called)
|
||
|
|
}
|
||
|
|
|
||
|
|
var _ = Describe("Metadata API", func() {
|
||
|
|
var ds *tests.MockDataStore
|
||
|
|
var artRepo *tests.MockArtworkRepo
|
||
|
|
var queueRepo *tests.MockArtworkQueueRepo
|
||
|
|
var albumRepo *tests.MockAlbumRepo
|
||
|
|
var provider *fakeProvider
|
||
|
|
var router http.Handler
|
||
|
|
var adminToken, userToken string
|
||
|
|
|
||
|
|
BeforeEach(func() {
|
||
|
|
DeferCleanup(configtest.SetupConfig())
|
||
|
|
conf.Server.EnableSharing = false
|
||
|
|
artRepo = tests.CreateMockArtworkRepo()
|
||
|
|
queueRepo = tests.CreateMockArtworkQueueRepo()
|
||
|
|
albumRepo = tests.CreateMockAlbumRepo()
|
||
|
|
artistRepo := tests.CreateMockArtistRepo()
|
||
|
|
playlistRepo := tests.CreateMockPlaylistRepo()
|
||
|
|
Expect(albumRepo.Put(&model.Album{ID: "al-1", Name: "Kid A"})).To(Succeed())
|
||
|
|
Expect(artistRepo.Put(&model.Artist{ID: "ar-1", Name: "Radiohead"})).To(Succeed())
|
||
|
|
Expect(playlistRepo.Put(&model.Playlist{ID: "pl-1", Name: "My Playlist"})).To(Succeed())
|
||
|
|
ds = &tests.MockDataStore{
|
||
|
|
MockedArtwork: artRepo,
|
||
|
|
MockedArtworkQueue: queueRepo,
|
||
|
|
MockedAlbum: albumRepo,
|
||
|
|
MockedArtist: artistRepo,
|
||
|
|
MockedPlaylist: playlistRepo,
|
||
|
|
}
|
||
|
|
auth.Init(ds)
|
||
|
|
provider = &fakeProvider{}
|
||
|
|
nativeRouter := New(ds, nil, nil, nil, tests.NewMockLibraryService(), tests.NewMockUserService(), nil, nil, nil, provider)
|
||
|
|
router = server.JWTVerifier(nativeRouter)
|
||
|
|
|
||
|
|
adminUser := model.User{ID: "admin-1", UserName: "admin", IsAdmin: true, NewPassword: "adminpass"}
|
||
|
|
regularUser := model.User{ID: "user-1", UserName: "regular", IsAdmin: false, NewPassword: "userpass"}
|
||
|
|
Expect(ds.User(context.TODO()).Put(&adminUser)).To(Succeed())
|
||
|
|
Expect(ds.User(context.TODO()).Put(®ularUser)).To(Succeed())
|
||
|
|
|
||
|
|
var err error
|
||
|
|
adminToken, err = auth.CreateToken(&adminUser)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
userToken, err = auth.CreateToken(®ularUser)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
})
|
||
|
|
|
||
|
|
Describe("POST /api/metadata/{kind}/{id}/refresh", func() {
|
||
|
|
It("clears state and enqueues a Bump for admins", func() {
|
||
|
|
Expect(artRepo.PutItemArtwork(&model.ItemArtwork{
|
||
|
|
ItemKind: "al", ItemID: "al-1", Hash: "oldhash", Source: "external",
|
||
|
|
})).To(Succeed())
|
||
|
|
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/al/al-1/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
||
|
|
|
||
|
|
_, err := artRepo.GetItemArtwork(model.KindAlbumArtwork, "al-1", model.ImageTypePrimary)
|
||
|
|
Expect(err).To(MatchError(model.ErrNotFound))
|
||
|
|
|
||
|
|
queued, err := queueRepo.DequeueBatch(1000)
|
||
|
|
Expect(err).ToNot(HaveOccurred())
|
||
|
|
Expect(queued).To(ContainElement(SatisfyAll(
|
||
|
|
HaveField("ItemKind", "al"),
|
||
|
|
HaveField("ItemID", "al-1"),
|
||
|
|
HaveField("Priority", model.ArtworkPriorityBump),
|
||
|
|
)))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns 400 for an invalid kind", func() {
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/xx/id-1/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("denies access to regular users", func() {
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/al/al-1/refresh", nil, userToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusForbidden))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("denies access without authentication", func() {
|
||
|
|
req := createUnauthenticatedRequest("POST", "/metadata/al/al-1/refresh", nil)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("triggers an external info refresh for albums", func() {
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/al/al-1/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
||
|
|
Eventually(provider.calls).Should(ContainElement("al/al-1"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("triggers an external info refresh for artists", func() {
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/ar/ar-1/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
||
|
|
Eventually(provider.calls).Should(ContainElement("ar/ar-1"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("skips the external info refresh for kinds without external info", func() {
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/pl/pl-1/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusNoContent))
|
||
|
|
Consistently(provider.calls).ShouldNot(ContainElement("pl/pl-1"))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns 404 for an unknown id", func() {
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/al/nope/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusNotFound))
|
||
|
|
})
|
||
|
|
|
||
|
|
It("returns 500 when the lookup fails for a reason other than not-found", func() {
|
||
|
|
albumRepo.SetError(true)
|
||
|
|
|
||
|
|
req := createAuthenticatedRequest("POST", "/metadata/al/al-1/refresh", nil, adminToken)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|