package persistence
import (
"context"
"time"
"github.com/Masterminds/squirrel"
"github.com/deluan/rest"
"github.com/navidrome/navidrome/conf/configtest"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("ShareRepository", func() {
var repo model.ShareRepository
var ctx context.Context
var adminUser = model.User{ID: "admin", UserName: "admin", IsAdmin: true}
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
ctx = request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
repo = NewShareRepository(GetDBXBuilder())
ur := NewUserRepository(GetDBXBuilder())
err := ur.Put(ctx, &adminUser)
Expect(err).ToNot(HaveOccurred())
db := GetDBXBuilder()
_, err = db.NewQuery("DELETE FROM share").Execute()
Expect(err).ToNot(HaveOccurred())
})
Describe("Headless Access", func() {
Context("Repository creation and basic operations", func() {
var headlessCtx context.Context
BeforeEach(func() {
headlessCtx = GinkgoT().Context()
})
It("should create repository successfully with no user context", func() {
headlessRepo := NewShareRepository(GetDBXBuilder())
Expect(headlessRepo).ToNot(BeNil())
})
It("should handle GetAll for headless processes", func() {
shareID := "headless-test-share"
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": shareID,
"user": adminUser.ID,
"desc": "Headless Test Share",
"type": "song",
"ids": "song-1",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
headlessRepo := NewShareRepository(GetDBXBuilder())
shares, err := headlessRepo.GetAll(headlessCtx)
Expect(err).ToNot(HaveOccurred())
found := false
for _, s := range shares {
if s.ID == shareID {
found = true
break
}
}
Expect(found).To(BeTrue(), "Headless process should see all shares")
})
It("should handle individual share retrieval for headless processes", func() {
shareID := "headless-get-share"
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": shareID,
"user": adminUser.ID,
"desc": "Headless Get Share",
"type": "song",
"ids": "song-2",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
headlessRepo := NewShareRepository(GetDBXBuilder())
share, err := headlessRepo.Get(headlessCtx, shareID)
Expect(err).ToNot(HaveOccurred())
Expect(share.ID).To(Equal(shareID))
Expect(share.Description).To(Equal("Headless Get Share"))
})
})
})
Describe("SQL ambiguity fix verification", func() {
It("should handle share operations without SQL ambiguity errors", func() {
shareID := "sql-test-share"
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": shareID,
"user": adminUser.ID,
"desc": "SQL Test Share",
"type": "album",
"ids": "non-existent-album",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
share, err := repo.Get(ctx, shareID)
Expect(err).ToNot(HaveOccurred())
Expect(share.ID).To(Equal(shareID))
Expect(share.Albums).To(BeEmpty())
})
})
Describe("Playlist share library scoping", func() {
var otherLib model.Library
var owner model.User
var plsID string
BeforeEach(func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
lr := NewLibraryRepository(GetDBXBuilder())
otherLib = model.Library{ID: 0, Name: "Share Other Library", Path: "/share/other/lib"}
Expect(lr.Put(adminCtx, &otherLib)).To(Succeed())
mr := NewMediaFileRepository(GetDBXBuilder())
Expect(mr.Put(adminCtx, &model.MediaFile{ID: "share-other", LibraryID: otherLib.ID, Path: "s/other.mp3", Title: "ShareOther"})).To(Succeed())
Expect(mr.Put(adminCtx, &model.MediaFile{ID: "share-ok", LibraryID: 1, Path: "s/ok.mp3", Title: "ShareOK"})).To(Succeed())
owner = createUserWithLibraries("share-owner", []int{1})
ur := NewUserRepository(GetDBXBuilder())
Expect(ur.Put(adminCtx, &owner)).To(Succeed())
Expect(ur.SetUserLibraries(adminCtx, owner.ID, []int{1})).To(Succeed())
plsID = "share-scope-pls"
ownerCtx := request.WithUser(log.NewContext(GinkgoT().Context()), owner)
pr := NewPlaylistRepository(GetDBXBuilder())
pls := &model.Playlist{ID: plsID, Name: "Scope Test", OwnerID: owner.ID}
pls.AddMediaFiles(model.MediaFiles{{ID: "share-ok"}, {ID: "share-other"}})
Expect(pr.Put(ownerCtx, pls)).To(Succeed())
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": "share-scope", "user": owner.ID, "desc": "Scope test share",
"type": "playlist", "ids": plsID, "created": time.Now(), "updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
b := GetDBXBuilder()
_, _ = b.NewQuery(`DELETE FROM share WHERE id = 'share-scope'`).Execute()
pr := NewPlaylistRepository(b)
_ = pr.Delete(adminCtx, plsID)
mr := NewMediaFileRepository(b).(*mediaFileRepository)
_, _ = mr.executeSQL(adminCtx, squirrel.Delete("media_file").Where(squirrel.Eq{"id": []string{"share-other", "share-ok"}}))
lr := NewLibraryRepository(b).(*libraryRepository)
_ = lr.delete(adminCtx, squirrel.Eq{"id": otherLib.ID})
_ = NewUserRepository(b).Delete(adminCtx, owner.ID)
})
It("excludes tracks the owner cannot access from the shared playlist", func() {
adminCtx := request.WithUser(ctx, adminUser)
adminRepo := NewShareRepository(GetDBXBuilder())
share, err := adminRepo.Get(adminCtx, "share-scope")
Expect(err).ToNot(HaveOccurred())
Expect(share.Tracks).To(ContainElement(HaveField("ID", "share-ok")))
Expect(share.Tracks).ToNot(ContainElement(HaveField("ID", "share-other")),
"a track outside the owner's libraries must not appear in the share")
})
It("returns no tracks when the playlist is not visible to the owner", func() {
privatePlsID := "private-pls"
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
pr := NewPlaylistRepository(GetDBXBuilder())
privatePls := &model.Playlist{ID: privatePlsID, Name: "Private", OwnerID: adminUser.ID, Public: false}
privatePls.AddMediaFiles(model.MediaFiles{{ID: "share-ok"}})
Expect(pr.Put(adminCtx, privatePls)).To(Succeed())
DeferCleanup(func() { _ = pr.Delete(adminCtx, privatePlsID) })
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": "share-private", "user": owner.ID, "desc": "Private share",
"type": "playlist", "ids": privatePlsID, "created": time.Now(), "updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
DeferCleanup(func() { _, _ = GetDBXBuilder().NewQuery(`DELETE FROM share WHERE id = 'share-private'`).Execute() })
adminRepo := NewShareRepository(GetDBXBuilder())
share, err := adminRepo.Get(adminCtx, "share-private")
Expect(err).ToNot(HaveOccurred())
Expect(share.Tracks).To(BeEmpty())
})
})
Describe("Artist, album and media file share library scoping", func() {
var otherLib model.Library
var owner model.User
const primaryID = "share-aa-primary"
const secondaryID = "share-aa-secondary"
BeforeEach(func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
b := GetDBXBuilder()
lr := NewLibraryRepository(b)
otherLib = model.Library{ID: 0, Name: "Artist Share Other Library", Path: "/share/artist/other"}
Expect(lr.Put(adminCtx, &otherLib)).To(Succeed())
ar := NewArtistRepository(b)
Expect(createArtistWithLibrary(adminCtx, ar, &model.Artist{ID: primaryID, Name: "AA Primary", OrderArtistName: "aa primary"}, 1)).To(Succeed())
Expect(createArtistWithLibrary(adminCtx, ar, &model.Artist{ID: secondaryID, Name: "AA Secondary", OrderArtistName: "aa secondary"}, 1)).To(Succeed())
aaParticipants := model.Participants{model.RoleAlbumArtist: {
{Artist: model.Artist{ID: primaryID, Name: "AA Primary"}},
{Artist: model.Artist{ID: secondaryID, Name: "AA Secondary"}},
}}
alr := NewAlbumRepository(b)
Expect(alr.Put(ctx, &model.Album{ID: "art-album-ok", LibraryID: 1, Name: "Art Album OK", AlbumArtistID: primaryID, AlbumArtist: "AA Primary", Participants: aaParticipants})).To(Succeed())
Expect(alr.Put(ctx, &model.Album{ID: "art-album-other", LibraryID: otherLib.ID, Name: "Art Album Other", AlbumArtistID: primaryID, AlbumArtist: "AA Primary", Participants: aaParticipants})).To(Succeed())
mr := NewMediaFileRepository(b)
Expect(mr.Put(adminCtx, &model.MediaFile{ID: "art-ok", LibraryID: 1, AlbumID: "art-album-ok", Path: "a/ok.mp3", Title: "ArtOK", AlbumArtistID: primaryID, Participants: aaParticipants})).To(Succeed())
Expect(mr.Put(adminCtx, &model.MediaFile{ID: "art-other", LibraryID: otherLib.ID, AlbumID: "art-album-other", Path: "a/other.mp3", Title: "ArtOther", AlbumArtistID: primaryID, Participants: aaParticipants})).To(Succeed())
owner = createUserWithLibraries("artist-share-owner", []int{1})
ur := NewUserRepository(b)
Expect(ur.Put(adminCtx, &owner)).To(Succeed())
Expect(ur.SetUserLibraries(adminCtx, owner.ID, []int{1})).To(Succeed())
for _, s := range []struct{ id, typ, ids string }{
{"art-share", "artist", secondaryID},
{"art-album-share", "album", "art-album-ok,art-album-other"},
{"art-mf-share", "media_file", "art-ok,art-other"},
} {
_, err := b.NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": s.id, "user": owner.ID, "desc": "Scope share",
"type": s.typ, "ids": s.ids, "created": time.Now(), "updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
}
})
AfterEach(func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
b := GetDBXBuilder()
_, _ = b.NewQuery(`DELETE FROM share WHERE id IN ('art-share', 'art-album-share', 'art-mf-share')`).Execute()
mr := NewMediaFileRepository(b).(*mediaFileRepository)
_, _ = mr.executeSQL(adminCtx, squirrel.Delete("media_file").Where(squirrel.Eq{"id": []string{"art-ok", "art-other"}}))
alr := NewAlbumRepository(b).(*albumRepository)
_, _ = alr.executeSQL(adminCtx, squirrel.Delete("album").Where(squirrel.Eq{"id": []string{"art-album-ok", "art-album-other"}}))
ar := NewArtistRepository(b).(*artistRepository)
_, _ = ar.executeSQL(adminCtx, squirrel.Delete("artist").Where(squirrel.Eq{"id": []string{primaryID, secondaryID}}))
lr := NewLibraryRepository(b).(*libraryRepository)
_ = lr.delete(adminCtx, squirrel.Eq{"id": otherLib.ID})
_ = NewUserRepository(b).Delete(adminCtx, owner.ID)
})
It("includes co-album-artist tracks the owner can access and excludes those they cannot", func() {
adminCtx := request.WithUser(ctx, adminUser)
adminRepo := NewShareRepository(GetDBXBuilder())
share, err := adminRepo.Get(adminCtx, "art-share")
Expect(err).ToNot(HaveOccurred())
Expect(share.Tracks).To(ContainElement(HaveField("ID", "art-ok")),
"a co-album-artist track (not matched by album_artist_id) must be included")
Expect(share.Tracks).ToNot(ContainElement(HaveField("ID", "art-other")),
"a track outside the owner's libraries must not appear in the share")
Expect(share.Albums).To(ContainElement(HaveField("ID", "art-album-ok")),
"a co-album-artist album must be included")
Expect(share.Albums).ToNot(ContainElement(HaveField("ID", "art-album-other")),
"an album outside the owner's libraries must not appear in the share")
})
It("excludes albums and their tracks outside the owner's libraries from an album share", func() {
share, err := NewShareRepository(GetDBXBuilder()).Get(log.NewContext(GinkgoT().Context()), "art-album-share")
Expect(err).ToNot(HaveOccurred())
Expect(share.Albums).To(ContainElement(HaveField("ID", "art-album-ok")))
Expect(share.Albums).ToNot(ContainElement(HaveField("ID", "art-album-other")))
Expect(share.Tracks).To(ContainElement(HaveField("ID", "art-ok")))
Expect(share.Tracks).ToNot(ContainElement(HaveField("ID", "art-other")))
})
It("excludes tracks outside the owner's libraries from a media file share", func() {
share, err := NewShareRepository(GetDBXBuilder()).Get(log.NewContext(GinkgoT().Context()), "art-mf-share")
Expect(err).ToNot(HaveOccurred())
Expect(share.Tracks).To(ContainElement(HaveField("ID", "art-ok")))
Expect(share.Tracks).ToNot(ContainElement(HaveField("ID", "art-other")))
})
})
Describe("Ownership Checks", func() {
var ownerUser = model.User{ID: "2222", UserName: "regular-user"}
var otherUser = model.User{ID: "3333", UserName: "third-user"}
insertShare := func(shareID, userID string) {
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": shareID,
"user": userID,
"desc": "Test Share",
"type": "media_file",
"ids": "1001",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
}
Describe("Delete", func() {
It("allows a non-admin user to delete their own share", func() {
insertShare("own-share-del", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Delete(ctx, "own-share-del")
Expect(err).ToNot(HaveOccurred())
})
It("denies a non-admin user from deleting another user's share", func() {
insertShare("other-share-del", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), otherUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Delete(ctx, "other-share-del")
Expect(err).To(Equal(rest.ErrPermissionDenied))
ownerCtx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
ownerRepo := NewShareRepository(GetDBXBuilder())
_, err = ownerRepo.Read(ownerCtx, "other-share-del")
Expect(err).ToNot(HaveOccurred())
})
It("allows an admin to delete any user's share", func() {
insertShare("admin-del-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Delete(ctx, "admin-del-share")
Expect(err).ToNot(HaveOccurred())
})
It("allows headless context (no user) to delete a share", func() {
insertShare("headless-del-share", ownerUser.ID)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Delete(GinkgoT().Context(), "headless-del-share")
Expect(err).ToNot(HaveOccurred())
})
})
Describe("Save", func() {
It("assigns the logged-in user as owner, ignoring a client-supplied UserID", func() {
ur := NewUserRepository(GetDBXBuilder())
Expect(ur.Put(ctx, &ownerUser)).To(Succeed())
Expect(ur.Put(ctx, &otherUser)).To(Succeed())
attackerCtx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
attackerRepo := NewShareRepository(GetDBXBuilder())
id, err := attackerRepo.Save(attackerCtx, &model.Share{
ID: "spoof-save-share", UserID: otherUser.ID,
ResourceType: "media_file", ResourceIDs: "1001",
})
Expect(err).ToNot(HaveOccurred())
adminCtx := request.WithUser(ctx, adminUser)
adminRepo := NewShareRepository(GetDBXBuilder())
got, err := adminRepo.Get(adminCtx, id)
Expect(err).ToNot(HaveOccurred())
Expect(got.UserID).To(Equal(ownerUser.ID))
})
})
Describe("Update", func() {
It("allows a non-admin user to update their own share", func() {
insertShare("own-share-upd", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(ctx, "own-share-upd", model.Share{Description: "Updated"}, "description")
Expect(err).ToNot(HaveOccurred())
})
It("denies a non-admin user from updating another user's share", func() {
insertShare("other-share-upd", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), otherUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(ctx, "other-share-upd", model.Share{Description: "Hacked"}, "description")
Expect(err).To(Equal(rest.ErrPermissionDenied))
})
It("allows an admin to update any user's share", func() {
insertShare("admin-upd-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(ctx, "admin-upd-share", model.Share{Description: "Admin Updated"}, "description")
Expect(err).ToNot(HaveOccurred())
})
It("allows headless context (no user) to update a share", func() {
insertShare("headless-upd-share", ownerUser.ID)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(GinkgoT().Context(), "headless-upd-share", model.Share{Description: "Headless"}, "description")
Expect(err).ToNot(HaveOccurred())
})
It("returns not found when updating a nonexistent share", func() {
ctx := request.WithUser(log.NewContext(context.TODO()), ownerUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(ctx, "does-not-exist", model.Share{Description: "Ghost"}, "description")
Expect(err).To(Equal(rest.ErrNotFound))
})
It("updates all columns when no specific columns are given", func() {
insertShare("all-cols-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(context.TODO()), ownerUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(ctx, "all-cols-share",
model.Share{Description: "All Updated", MaxBitRate: 192, ResourceType: "album", ResourceIDs: "2002"})
Expect(err).ToNot(HaveOccurred())
share, err := repo.Read(ctx, "all-cols-share")
Expect(err).ToNot(HaveOccurred())
Expect(share.Description).To(Equal("All Updated"))
Expect(share.MaxBitRate).To(Equal(192))
Expect(share.ResourceType).To(Equal("album"))
})
It("does not let an owner reassign their share to another user", func() {
insertShare("reassign-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(context.TODO()), ownerUser)
repo := NewShareRepository(GetDBXBuilder())
err := repo.Update(ctx, "reassign-share",
model.Share{UserID: otherUser.ID, Description: "Given away"}, "user_id", "description")
Expect(err).ToNot(HaveOccurred())
got, err := repo.Read(ctx, "reassign-share")
Expect(err).ToNot(HaveOccurred())
Expect(got.UserID).To(Equal(ownerUser.ID))
})
})
Describe("Read scoping", func() {
BeforeEach(func() {
ur := NewUserRepository(GetDBXBuilder())
Expect(ur.Put(ctx, &ownerUser)).To(Succeed())
Expect(ur.Put(ctx, &otherUser)).To(Succeed())
insertShare("share-owner-1", ownerUser.ID)
insertShare("share-owner-2", ownerUser.ID)
insertShare("share-other-1", otherUser.ID)
})
Context("non-admin user", func() {
var nonAdminRepo model.ShareRepository
var nonAdminCtx context.Context
BeforeEach(func() {
nonAdminCtx = request.WithUser(ctx, ownerUser)
nonAdminRepo = NewShareRepository(GetDBXBuilder())
})
It("GetAll returns only own shares", func() {
shares, err := nonAdminRepo.GetAll(nonAdminCtx)
Expect(err).ToNot(HaveOccurred())
ids := make([]string, len(shares))
for i, s := range shares {
ids[i] = s.ID
}
Expect(ids).To(ConsistOf("share-owner-1", "share-owner-2"))
})
It("ReadAll returns only own shares", func() {
shares, err := nonAdminRepo.ReadAll(nonAdminCtx)
Expect(err).ToNot(HaveOccurred())
ids := make([]string, len(shares))
for i, s := range shares {
ids[i] = s.ID
}
Expect(ids).To(ConsistOf("share-owner-1", "share-owner-2"))
})
It("Get returns own share", func() {
s, err := nonAdminRepo.Get(nonAdminCtx, "share-owner-1")
Expect(err).ToNot(HaveOccurred())
Expect(s.ID).To(Equal("share-owner-1"))
})
It("Get returns ErrNotFound for another user's share", func() {
_, err := nonAdminRepo.Get(nonAdminCtx, "share-other-1")
Expect(err).To(MatchError(model.ErrNotFound))
})
It("Read returns ErrNotFound for another user's share", func() {
_, err := nonAdminRepo.Read(nonAdminCtx, "share-other-1")
Expect(err).To(MatchError(model.ErrNotFound))
})
It("Exists returns true for own share", func() {
exists, err := nonAdminRepo.Exists(nonAdminCtx, "share-owner-1")
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
})
It("Exists returns false for another user's share", func() {
exists, err := nonAdminRepo.Exists(nonAdminCtx, "share-other-1")
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
})
It("CountAll counts only own shares", func() {
count, err := nonAdminRepo.CountAll(nonAdminCtx)
Expect(err).ToNot(HaveOccurred())
Expect(count).To(BeNumerically("==", 2))
})
It("Count (rest) counts only own shares", func() {
count, err := nonAdminRepo.Count(nonAdminCtx)
Expect(err).ToNot(HaveOccurred())
Expect(count).To(BeNumerically("==", 2))
})
})
Context("admin user", func() {
It("GetAll returns all shares", func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
adminRepo := NewShareRepository(GetDBXBuilder())
shares, err := adminRepo.GetAll(adminCtx)
Expect(err).ToNot(HaveOccurred())
ids := make([]string, len(shares))
for i, s := range shares {
ids[i] = s.ID
}
Expect(ids).To(ConsistOf("share-owner-1", "share-owner-2", "share-other-1"))
})
It("CountAll counts all shares", func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
adminRepo := NewShareRepository(GetDBXBuilder())
count, err := adminRepo.CountAll(adminCtx)
Expect(err).ToNot(HaveOccurred())
Expect(count).To(BeNumerically("==", 3))
})
})
Context("headless context (public share route)", func() {
var headlessCtx context.Context
BeforeEach(func() {
headlessCtx = GinkgoT().Context()
})
It("GetAll returns all shares", func() {
headlessRepo := NewShareRepository(GetDBXBuilder())
shares, err := headlessRepo.GetAll(headlessCtx)
Expect(err).ToNot(HaveOccurred())
Expect(shares).To(HaveLen(3))
})
It("Get returns another user's share", func() {
headlessRepo := NewShareRepository(GetDBXBuilder())
s, err := headlessRepo.Get(headlessCtx, "share-other-1")
Expect(err).ToNot(HaveOccurred())
Expect(s.ID).To(Equal("share-other-1"))
})
It("Exists returns true for any share", func() {
headlessRepo := NewShareRepository(GetDBXBuilder())
exists, err := headlessRepo.Exists(headlessCtx, "share-other-1")
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
})
})
})
})
})