2025-06-01 14:34:45 -04:00
|
|
|
package nativeapi
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"net/url"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
|
|
|
"github.com/navidrome/navidrome/conf/configtest"
|
|
|
|
|
"github.com/navidrome/navidrome/consts"
|
|
|
|
|
"github.com/navidrome/navidrome/core/auth"
|
|
|
|
|
"github.com/navidrome/navidrome/model"
|
|
|
|
|
"github.com/navidrome/navidrome/server"
|
|
|
|
|
"github.com/navidrome/navidrome/tests"
|
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
|
|
|
. "github.com/onsi/gomega"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _ = Describe("Song Endpoints", func() {
|
|
|
|
|
var (
|
|
|
|
|
router http.Handler
|
|
|
|
|
ds *tests.MockDataStore
|
|
|
|
|
mfRepo *tests.MockMediaFileRepo
|
|
|
|
|
userRepo *tests.MockedUserRepo
|
|
|
|
|
w *httptest.ResponseRecorder
|
|
|
|
|
testUser model.User
|
|
|
|
|
testSongs model.MediaFiles
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
|
DeferCleanup(configtest.SetupConfig())
|
2026-07-04 19:55:34 -04:00
|
|
|
conf.Server.EnableSharing = false
|
2025-06-01 14:34:45 -04:00
|
|
|
conf.Server.SessionTimeout = time.Minute
|
|
|
|
|
|
|
|
|
|
// Setup mock repositories
|
|
|
|
|
mfRepo = tests.CreateMockMediaFileRepo()
|
|
|
|
|
userRepo = tests.CreateMockUserRepo()
|
|
|
|
|
|
|
|
|
|
ds = &tests.MockDataStore{
|
|
|
|
|
MockedMediaFile: mfRepo,
|
|
|
|
|
MockedUser: userRepo,
|
|
|
|
|
MockedProperty: &tests.MockedPropertyRepo{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize auth system
|
|
|
|
|
auth.Init(ds)
|
|
|
|
|
|
|
|
|
|
// Create test user
|
|
|
|
|
testUser = model.User{
|
|
|
|
|
ID: "user-1",
|
|
|
|
|
UserName: "testuser",
|
|
|
|
|
Name: "Test User",
|
|
|
|
|
IsAdmin: false,
|
|
|
|
|
NewPassword: "testpass",
|
|
|
|
|
}
|
|
|
|
|
err := userRepo.Put(&testUser)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
// Create test songs
|
|
|
|
|
testSongs = model.MediaFiles{
|
|
|
|
|
{
|
|
|
|
|
ID: "song-1",
|
|
|
|
|
Title: "Test Song 1",
|
|
|
|
|
Artist: "Test Artist 1",
|
|
|
|
|
Album: "Test Album 1",
|
|
|
|
|
AlbumID: "album-1",
|
|
|
|
|
ArtistID: "artist-1",
|
|
|
|
|
Duration: 180.5,
|
|
|
|
|
BitRate: 320,
|
|
|
|
|
Path: "/music/song1.mp3",
|
|
|
|
|
Suffix: "mp3",
|
|
|
|
|
Size: 5242880,
|
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
ID: "song-2",
|
|
|
|
|
Title: "Test Song 2",
|
|
|
|
|
Artist: "Test Artist 2",
|
|
|
|
|
Album: "Test Album 2",
|
|
|
|
|
AlbumID: "album-2",
|
|
|
|
|
ArtistID: "artist-2",
|
|
|
|
|
Duration: 240.0,
|
|
|
|
|
BitRate: 256,
|
|
|
|
|
Path: "/music/song2.mp3",
|
|
|
|
|
Suffix: "mp3",
|
|
|
|
|
Size: 7340032,
|
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
mfRepo.SetData(testSongs)
|
|
|
|
|
|
|
|
|
|
// Create the native API router and wrap it with the JWTVerifier middleware
|
feat(ui): add Refresh Metadata to the album and artist context menus (#6036)
* refactor(artwork): move artworkItemName into core/artwork as ItemName
* feat(external): add RefreshInfo to force an external info refresh
RefreshInfo re-fetches and re-saves external info for one artist or
album, bypassing the TTL check that UpdateArtistInfo/UpdateAlbumInfo
use. It is synchronous; callers that must not block detach it themselves.
Also makes MockArtistRepo/MockAlbumRepo.UpdateExternalInfo persist to
Data (previously a no-op) and adds the new method to the e2e noopProvider,
both required so the interface addition compiles and is observable in tests.
* feat(external): broadcast RefreshResource after external info is saved
populateArtistInfo and populateAlbumInfo now emit the same RefreshResource
event the artwork worker uses, so the UI learns about both foreground and
background metadata refreshes.
* feat(nativeapi): replace artwork refresh endpoint with metadata refresh
* feat(ui): add refreshMetadata to the data provider
* feat(ui): add a Refresh Metadata item to the album and artist context menus
* fix(ui): re-fetch artist info when the record is refreshed
* test: fix mislabeled spec, add kind-gate negative case, guard nil mock maps
- Rename the RefreshInfo spec that claimed to cover the save-failure/broadcast
path: SetError(true) fails Get too, so it only proves RefreshInfo bails out
early at getArtist.
- Add a spec proving playlist refreshes skip the external-info step, since
that asymmetry (al/ar only) was documented but unasserted.
- Add lazy nil-map init to MockAlbumRepo/MockArtistRepo.UpdateExternalInfo so
a composite-literal-constructed mock doesn't panic on first save.
* test: relocate discArtworkName specs from cmd to core/artwork
artworkItemName moved into core/artwork as ItemName in an earlier commit, but
its disc-name specs stayed behind in cmd/artwork_test.go, reaching across
packages. Move them to core/artwork/item_name_test.go where the code now lives.
* fix(ui): shape refreshMetadata like a react-admin response
react-admin validates custom dataProvider methods and rejects any response
without a `data` key, so the raw httpClient promise made every click surface
an error toast instead of the success message. The unit test mocked
useDataProvider, which skips that validation.
Also folds "which kinds have external info" into external.HasInfo so the
handler stops restating it, drops the nil-broker guard that only existed for
tests, and delegates the mocks' UpdateExternalInfo to Put.
* refactor(external): unexport infoKinds
Only HasInfo is used outside the package, so the slice itself does not need
to be exported.
* refactor(artwork): fold ItemName into housekeeping.go next to Refresh
ItemName exists to guard Refresh from ids that would orphan a queue row, and
both callers invoke them back to back. A separate file hid that pairing; it was
only split out to keep the move out of cmd/ legible in review.
* fix(nativeapi): return 500 when the refresh lookup fails for a non-ErrNotFound reason
A transient repository error told the admin the id did not exist, and the error
was dropped without a log line, so nothing pointed at the real cause.
Also drops the inherited claim that clearing artwork state shows a placeholder.
Reads fall back to local resolution, so that only holds when there is no local art.
* fix(ui): move Refresh Metadata above Get Info in the context menu
Menu order follows key insertion order in the options object, so the new spec
pins the position rather than leaving it to be shuffled by the next addition.
2026-08-25 23:59:40 -04:00
|
|
|
nativeRouter := New(ds, nil, nil, nil, tests.NewMockLibraryService(), tests.NewMockUserService(), nil, nil, nil, nil)
|
2025-06-01 14:34:45 -04:00
|
|
|
router = server.JWTVerifier(nativeRouter)
|
|
|
|
|
w = httptest.NewRecorder()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Helper function to create unauthenticated request
|
|
|
|
|
createUnauthenticatedRequest := func(method, path string, body []byte) *http.Request {
|
|
|
|
|
var req *http.Request
|
|
|
|
|
if body != nil {
|
|
|
|
|
req = httptest.NewRequest(method, path, bytes.NewReader(body))
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
} else {
|
|
|
|
|
req = httptest.NewRequest(method, path, nil)
|
|
|
|
|
}
|
|
|
|
|
return req
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to create authenticated request with JWT token
|
|
|
|
|
createAuthenticatedRequest := func(method, path string, body []byte) *http.Request {
|
|
|
|
|
req := createUnauthenticatedRequest(method, path, body)
|
|
|
|
|
|
|
|
|
|
// Create JWT token for the test user
|
|
|
|
|
token, err := auth.CreateToken(&testUser)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
// Add JWT token to Authorization header
|
|
|
|
|
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+token)
|
|
|
|
|
|
|
|
|
|
return req
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Describe("GET /song", func() {
|
|
|
|
|
Context("when user is authenticated", func() {
|
|
|
|
|
It("returns all songs", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
|
|
|
|
|
var response []model.MediaFile
|
|
|
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
Expect(response).To(HaveLen(2))
|
|
|
|
|
Expect(response[0].ID).To(Equal("song-1"))
|
|
|
|
|
Expect(response[0].Title).To(Equal("Test Song 1"))
|
|
|
|
|
Expect(response[1].ID).To(Equal("song-2"))
|
|
|
|
|
Expect(response[1].Title).To(Equal("Test Song 2"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("handles repository errors gracefully", func() {
|
|
|
|
|
mfRepo.SetError(true)
|
|
|
|
|
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Context("when user is not authenticated", func() {
|
|
|
|
|
It("returns unauthorized", func() {
|
|
|
|
|
req := createUnauthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Describe("GET /song/{id}", func() {
|
|
|
|
|
Context("when user is authenticated", func() {
|
|
|
|
|
It("returns the specific song", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song/song-1", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
|
|
|
|
|
var response model.MediaFile
|
|
|
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
Expect(response.ID).To(Equal("song-1"))
|
|
|
|
|
Expect(response.Title).To(Equal("Test Song 1"))
|
|
|
|
|
Expect(response.Artist).To(Equal("Test Artist 1"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("returns 404 for non-existent song", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song/non-existent", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusNotFound))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("handles repository errors gracefully", func() {
|
|
|
|
|
mfRepo.SetError(true)
|
|
|
|
|
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song/song-1", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Context("when user is not authenticated", func() {
|
|
|
|
|
It("returns unauthorized", func() {
|
|
|
|
|
req := createUnauthenticatedRequest("GET", "/song/song-1", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Describe("Song endpoints are read-only", func() {
|
|
|
|
|
Context("POST /song", func() {
|
|
|
|
|
It("should not be available (songs are not persistable)", func() {
|
|
|
|
|
newSong := model.MediaFile{
|
|
|
|
|
Title: "New Song",
|
|
|
|
|
Artist: "New Artist",
|
|
|
|
|
Album: "New Album",
|
|
|
|
|
Duration: 200.0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body, _ := json.Marshal(newSong)
|
|
|
|
|
req := createAuthenticatedRequest("POST", "/song", body)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
// Should return 405 Method Not Allowed or 404 Not Found
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusMethodNotAllowed))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Context("PUT /song/{id}", func() {
|
|
|
|
|
It("should not be available (songs are not persistable)", func() {
|
|
|
|
|
updatedSong := model.MediaFile{
|
|
|
|
|
ID: "song-1",
|
|
|
|
|
Title: "Updated Song",
|
|
|
|
|
Artist: "Updated Artist",
|
|
|
|
|
Album: "Updated Album",
|
|
|
|
|
Duration: 250.0,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body, _ := json.Marshal(updatedSong)
|
|
|
|
|
req := createAuthenticatedRequest("PUT", "/song/song-1", body)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
// Should return 405 Method Not Allowed or 404 Not Found
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusMethodNotAllowed))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Context("DELETE /song/{id}", func() {
|
|
|
|
|
It("should not be available (songs are not persistable)", func() {
|
|
|
|
|
req := createAuthenticatedRequest("DELETE", "/song/song-1", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
// Should return 405 Method Not Allowed or 404 Not Found
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusMethodNotAllowed))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Describe("Query parameters and filtering", func() {
|
|
|
|
|
Context("when using query parameters", func() {
|
|
|
|
|
It("handles pagination parameters", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song?_start=0&_end=1", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
|
|
|
|
|
var response []model.MediaFile
|
|
|
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
// Should still return all songs since our mock doesn't implement pagination
|
|
|
|
|
// but the request should be processed successfully
|
|
|
|
|
Expect(len(response)).To(BeNumerically(">=", 1))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("handles sort parameters", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song?_sort=title&_order=ASC", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
|
|
|
|
|
var response []model.MediaFile
|
|
|
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
Expect(response).To(HaveLen(2))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("handles filter parameters", func() {
|
|
|
|
|
// Properly encode the URL with query parameters
|
|
|
|
|
baseURL := "/song"
|
|
|
|
|
params := url.Values{}
|
|
|
|
|
params.Add("title", "Test Song 1")
|
|
|
|
|
fullURL := baseURL + "?" + params.Encode()
|
|
|
|
|
|
|
|
|
|
req := createAuthenticatedRequest("GET", fullURL, nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
|
|
|
|
|
var response []model.MediaFile
|
|
|
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
// Mock doesn't implement filtering, but request should be processed
|
|
|
|
|
Expect(len(response)).To(BeNumerically(">=", 1))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Describe("Response headers and content type", func() {
|
|
|
|
|
It("sets correct content type for JSON responses", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
Expect(w.Header().Get("Content-Type")).To(ContainSubstring("application/json"))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("includes total count header when available", func() {
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
// The X-Total-Count header might be set by the REST framework
|
|
|
|
|
// We just verify the request is processed successfully
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Describe("Edge cases and error handling", func() {
|
|
|
|
|
Context("when repository is unavailable", func() {
|
|
|
|
|
It("handles database connection errors", func() {
|
|
|
|
|
mfRepo.SetError(true)
|
|
|
|
|
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusInternalServerError))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Context("when no songs exist", func() {
|
|
|
|
|
It("returns empty array when no songs are found", func() {
|
|
|
|
|
mfRepo.SetData(model.MediaFiles{}) // Empty dataset
|
|
|
|
|
|
|
|
|
|
req := createAuthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
|
|
|
|
|
var response []model.MediaFile
|
|
|
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
Expect(response).To(HaveLen(0))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Describe("Authentication middleware integration", func() {
|
|
|
|
|
Context("with different user types", func() {
|
|
|
|
|
It("works with admin users", func() {
|
|
|
|
|
adminUser := model.User{
|
|
|
|
|
ID: "admin-1",
|
|
|
|
|
UserName: "admin",
|
|
|
|
|
Name: "Admin User",
|
|
|
|
|
IsAdmin: true,
|
|
|
|
|
NewPassword: "adminpass",
|
|
|
|
|
}
|
|
|
|
|
err := userRepo.Put(&adminUser)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
// Create JWT token for admin user
|
|
|
|
|
token, err := auth.CreateToken(&adminUser)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
req := createUnauthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+token)
|
|
|
|
|
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("works with regular users", func() {
|
|
|
|
|
regularUser := model.User{
|
|
|
|
|
ID: "user-2",
|
|
|
|
|
UserName: "regular",
|
|
|
|
|
Name: "Regular User",
|
|
|
|
|
IsAdmin: false,
|
|
|
|
|
NewPassword: "userpass",
|
|
|
|
|
}
|
|
|
|
|
err := userRepo.Put(®ularUser)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
// Create JWT token for regular user
|
|
|
|
|
token, err := auth.CreateToken(®ularUser)
|
|
|
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
|
|
|
|
|
|
req := createUnauthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+token)
|
|
|
|
|
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Context("with missing authentication context", func() {
|
|
|
|
|
It("rejects requests without user context", func() {
|
|
|
|
|
req := createUnauthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
// No authentication header added
|
|
|
|
|
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
It("rejects requests with invalid JWT tokens", func() {
|
|
|
|
|
req := createUnauthenticatedRequest("GET", "/song", nil)
|
|
|
|
|
req.Header.Set(consts.UIAuthorizationHeader, "Bearer invalid.token.here")
|
|
|
|
|
|
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
|
|
|
|
|
|
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|